diff --git a/src/api/endpoints/annotate/_shared/extract.py b/src/api/endpoints/annotate/_shared/extract.py index c0459e04..3fb7770b 100644 --- a/src/api/endpoints/annotate/_shared/extract.py +++ b/src/api/endpoints/annotate/_shared/extract.py @@ -3,8 +3,8 @@ from src.api.endpoints.annotate._shared.queries.get_annotation_batch_info import GetAnnotationBatchInfoQueryBuilder from src.api.endpoints.annotate.all.get.models.agency import AgencyAnnotationResponseOuterInfo from src.api.endpoints.annotate.all.get.models.location import LocationAnnotationResponseOuterInfo -from src.api.endpoints.annotate.all.get.models.name import NameAnnotationSuggestion -from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationSuggestion +from src.api.endpoints.annotate.all.get.models.name import NameAnnotationResponseOuterInfo +from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationResponseOuterInfo from src.api.endpoints.annotate.all.get.models.response import GetNextURLForAllAnnotationResponse, \ GetNextURLForAllAnnotationInnerResponse from src.api.endpoints.annotate.all.get.models.url_type import URLTypeAnnotationSuggestion @@ -32,7 +32,7 @@ async def extract_and_format_get_annotation_result( convert_user_url_type_suggestion_to_url_type_annotation_suggestion( url.user_relevant_suggestions ) - record_type_suggestions: list[RecordTypeAnnotationSuggestion] = \ + record_type_suggestions: RecordTypeAnnotationResponseOuterInfo = \ convert_user_record_type_suggestion_to_record_type_annotation_suggestion( url.user_record_type_suggestions ) @@ -40,7 +40,7 @@ async def extract_and_format_get_annotation_result( await GetAgencySuggestionsQueryBuilder(url_id=url.id).run(session) location_suggestions: LocationAnnotationResponseOuterInfo = \ await GetLocationSuggestionsQueryBuilder(url_id=url.id).run(session) - name_suggestions: list[NameAnnotationSuggestion] = \ + name_suggestions: NameAnnotationResponseOuterInfo = \ await GetNameSuggestionsQueryBuilder(url_id=url.id).run(session) return GetNextURLForAllAnnotationResponse( next_annotation=GetNextURLForAllAnnotationInnerResponse( diff --git a/src/api/endpoints/annotate/all/get/models/name.py b/src/api/endpoints/annotate/all/get/models/name.py index 80857305..386b11de 100644 --- a/src/api/endpoints/annotate/all/get/models/name.py +++ b/src/api/endpoints/annotate/all/get/models/name.py @@ -1,7 +1,10 @@ from pydantic import BaseModel - class NameAnnotationSuggestion(BaseModel): - name: str - suggestion_id: int - endorsement_count: int \ No newline at end of file + id: int + display_name: str + user_count: int + robo_count: int + +class NameAnnotationResponseOuterInfo(BaseModel): + suggestions: list[NameAnnotationSuggestion] \ No newline at end of file diff --git a/src/api/endpoints/annotate/all/get/models/record_type.py b/src/api/endpoints/annotate/all/get/models/record_type.py index a1c24911..a99dfd7b 100644 --- a/src/api/endpoints/annotate/all/get/models/record_type.py +++ b/src/api/endpoints/annotate/all/get/models/record_type.py @@ -1,11 +1,17 @@ -from pydantic import BaseModel +from pydantic import BaseModel, Field +from src.api.endpoints.annotate.all.get.models.suggestion import SuggestionModel from src.core.enums import RecordType - - -class RecordTypeAnnotationSuggestion(BaseModel): +class RecordTypeSuggestionModel(BaseModel): record_type: RecordType - endorsement_count: int + user_count: int + robo_confidence: int | None = Field( + description="The robo labeler's given confidence for its suggestion. Null if no robo-label occurred.", + ge=0, + le=100, + ) +class RecordTypeAnnotationResponseOuterInfo(BaseModel): + suggestions: list[RecordTypeSuggestionModel] diff --git a/src/api/endpoints/annotate/all/get/models/response.py b/src/api/endpoints/annotate/all/get/models/response.py index 989dbf8d..7f924e3f 100644 --- a/src/api/endpoints/annotate/all/get/models/response.py +++ b/src/api/endpoints/annotate/all/get/models/response.py @@ -1,16 +1,11 @@ -from typing import Optional - from pydantic import Field, BaseModel -from src.api.endpoints.annotate.agency.get.dto import GetNextURLForAgencyAgencyInfo from src.api.endpoints.annotate.all.get.models.agency import AgencyAnnotationResponseOuterInfo from src.api.endpoints.annotate.all.get.models.location import LocationAnnotationResponseOuterInfo -from src.api.endpoints.annotate.all.get.models.name import NameAnnotationSuggestion -from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationSuggestion +from src.api.endpoints.annotate.all.get.models.name import NameAnnotationResponseOuterInfo +from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationResponseOuterInfo from src.api.endpoints.annotate.all.get.models.url_type import URLTypeAnnotationSuggestion from src.api.endpoints.annotate.dtos.shared.base.response import AnnotationInnerResponseInfoBase -from src.api.endpoints.annotate.relevance.get.dto import RelevanceAnnotationResponseInfo -from src.core.enums import RecordType class GetNextURLForAllAnnotationInnerResponse(AnnotationInnerResponseInfoBase): @@ -23,10 +18,10 @@ class GetNextURLForAllAnnotationInnerResponse(AnnotationInnerResponseInfoBase): url_type_suggestions: list[URLTypeAnnotationSuggestion] = Field( title="Whether the auto-labeler identified the URL as relevant or not" ) - record_type_suggestions: list[RecordTypeAnnotationSuggestion] = Field( + record_type_suggestions: RecordTypeAnnotationResponseOuterInfo = Field( title="What record type, if any, user and the auto-labeler identified the URL as" ) - name_suggestions: list[NameAnnotationSuggestion] | None = Field( + name_suggestions: NameAnnotationResponseOuterInfo = Field( title="User and Auto-Suggestions for names" ) diff --git a/src/api/endpoints/annotate/all/get/queries/convert.py b/src/api/endpoints/annotate/all/get/queries/convert.py index 386389a5..fe9b0777 100644 --- a/src/api/endpoints/annotate/all/get/queries/convert.py +++ b/src/api/endpoints/annotate/all/get/queries/convert.py @@ -1,6 +1,7 @@ from collections import Counter -from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationSuggestion +from src.api.endpoints.annotate.all.get.models.record_type import RecordTypeAnnotationResponseOuterInfo, \ + RecordTypeSuggestionModel from src.api.endpoints.annotate.all.get.models.url_type import URLTypeAnnotationSuggestion from src.core.enums import RecordType from src.db.models.impl.flag.url_validated.enums import URLType @@ -26,18 +27,20 @@ def convert_user_url_type_suggestion_to_url_type_annotation_suggestion( def convert_user_record_type_suggestion_to_record_type_annotation_suggestion( db_suggestions: list[UserRecordTypeSuggestion] -) -> list[RecordTypeAnnotationSuggestion]: +) -> RecordTypeAnnotationResponseOuterInfo: counter: Counter[RecordType] = Counter() for suggestion in db_suggestions: counter[suggestion.record_type] += 1 - anno_suggestions: list[RecordTypeAnnotationSuggestion] = [] + suggestions: list[RecordTypeSuggestionModel] = [] for record_type, endorsement_count in counter.most_common(3): - anno_suggestions.append( - RecordTypeAnnotationSuggestion( + suggestions.append( + RecordTypeSuggestionModel( record_type=record_type, - endorsement_count=endorsement_count, + user_count=endorsement_count, + robo_confidence=0, ) ) - - return anno_suggestions \ No newline at end of file + return RecordTypeAnnotationResponseOuterInfo( + suggestions=suggestions + ) diff --git a/src/api/endpoints/annotate/all/get/queries/name/core.py b/src/api/endpoints/annotate/all/get/queries/name/core.py index b048cb2c..9438f14e 100644 --- a/src/api/endpoints/annotate/all/get/queries/name/core.py +++ b/src/api/endpoints/annotate/all/get/queries/name/core.py @@ -1,11 +1,12 @@ from typing import Sequence -from sqlalchemy import select, func, RowMapping +from sqlalchemy import select, func, RowMapping, case from sqlalchemy.ext.asyncio import AsyncSession -from src.api.endpoints.annotate.all.get.models.name import NameAnnotationSuggestion +from src.api.endpoints.annotate.all.get.models.name import NameAnnotationSuggestion, NameAnnotationResponseOuterInfo from src.db.helpers.session import session_helper as sh from src.db.models.impl.link.user_name_suggestion.sqlalchemy import LinkUserNameSuggestion +from src.db.models.impl.url.suggestion.name.enums import NameSuggestionSource from src.db.models.impl.url.suggestion.name.sqlalchemy import URLNameSuggestion from src.db.queries.base.builder import QueryBuilderBase @@ -19,14 +20,18 @@ def __init__( super().__init__() self.url_id = url_id - async def run(self, session: AsyncSession) -> list[NameAnnotationSuggestion]: + async def run(self, session: AsyncSession) -> NameAnnotationResponseOuterInfo: query = ( select( - URLNameSuggestion.id.label('suggestion_id'), - URLNameSuggestion.suggestion.label('name'), + URLNameSuggestion.id.label('id'), + URLNameSuggestion.suggestion.label('display_name'), func.count( LinkUserNameSuggestion.user_id - ).label('endorsement_count'), + ).label('user_count'), + case( + (URLNameSuggestion.source == NameSuggestionSource.HTML_METADATA_TITLE, 1), + else_=0 + ).label("robo_count") ) .outerjoin( LinkUserNameSuggestion, @@ -47,12 +52,15 @@ async def run(self, session: AsyncSession) -> list[NameAnnotationSuggestion]: ) mappings: Sequence[RowMapping] = await sh.mappings(session, query=query) - return [ + suggestions = [ NameAnnotationSuggestion( **mapping ) for mapping in mappings ] + return NameAnnotationResponseOuterInfo( + suggestions=suggestions + ) diff --git a/tests/automated/integration/api/annotate/all/test_happy_path.py b/tests/automated/integration/api/annotate/all/test_happy_path.py index 47db2a09..49d8bd97 100644 --- a/tests/automated/integration/api/annotate/all/test_happy_path.py +++ b/tests/automated/integration/api/annotate/all/test_happy_path.py @@ -48,10 +48,10 @@ async def test_annotate_all( # Get a valid URL to annotate get_response_1 = await ath.request_validator.get_next_url_for_all_annotations() assert get_response_1.next_annotation is not None - assert len(get_response_1.next_annotation.name_suggestions) == 1 - name_suggestion = get_response_1.next_annotation.name_suggestions[0] - assert name_suggestion.name is not None - assert name_suggestion.endorsement_count == 0 + assert len(get_response_1.next_annotation.name_suggestions.suggestions) == 1 + name_suggestion = get_response_1.next_annotation.name_suggestions.suggestions[0] + assert name_suggestion.display_name is not None + assert name_suggestion.user_count == 0 # Apply the second batch id as a filter and see that a different URL is returned get_response_2 = await ath.request_validator.get_next_url_for_all_annotations( diff --git a/tests/automated/integration/api/annotate/anonymous/test_core.py b/tests/automated/integration/api/annotate/anonymous/test_core.py index b6fb93fa..26516b16 100644 --- a/tests/automated/integration/api/annotate/anonymous/test_core.py +++ b/tests/automated/integration/api/annotate/anonymous/test_core.py @@ -48,10 +48,10 @@ async def test_annotate_anonymous( session_id: UUID = get_response_1.session_id assert session_id is not None assert get_response_1.next_annotation is not None - assert len(get_response_1.next_annotation.name_suggestions) == 1 - name_suggestion: NameAnnotationSuggestion = get_response_1.next_annotation.name_suggestions[0] - assert name_suggestion.name is not None - assert name_suggestion.endorsement_count == 0 + assert len(get_response_1.next_annotation.name_suggestions.suggestions) == 1 + name_suggestion: NameAnnotationSuggestion = get_response_1.next_annotation.name_suggestions.suggestions[0] + assert name_suggestion.display_name is not None + assert name_suggestion.user_count == 0 agency_id: int = await ddc.agency()