Skip to content

Commit be468fc

Browse files
authored
Merge pull request #554 from Police-Data-Accessibility-Project/mc_434_anonymous_name_annotations
mc_434_anonymous_name_annotations
2 parents b776754 + f9aa64b commit be468fc

File tree

7 files changed

+176
-15
lines changed

7 files changed

+176
-15
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Create anonymous_annotation_name
2+
3+
Revision ID: dfb64594049f
4+
Revises: 1d3398f9cd8a
5+
Create Date: 2025-12-05 17:21:35.134935
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects.postgresql import UUID
13+
14+
from src.util.alembic_helpers import created_at_column
15+
16+
# revision identifiers, used by Alembic.
17+
revision: str = 'dfb64594049f'
18+
down_revision: Union[str, None] = '1d3398f9cd8a'
19+
branch_labels: Union[str, Sequence[str], None] = None
20+
depends_on: Union[str, Sequence[str], None] = None
21+
22+
23+
def upgrade() -> None:
24+
op.create_table(
25+
"link__anonymous_sessions__name_suggestions",
26+
sa.Column(
27+
"session_id",
28+
UUID,
29+
sa.ForeignKey("anonymous_sessions.id"),
30+
nullable=False
31+
),
32+
sa.Column(
33+
"suggestion_id",
34+
sa.Integer(),
35+
sa.ForeignKey("url_name_suggestions.id"),
36+
nullable=False,
37+
),
38+
created_at_column(),
39+
sa.PrimaryKeyConstraint(
40+
"session_id",
41+
"suggestion_id"
42+
)
43+
)
44+
45+
46+
def downgrade() -> None:
47+
pass

src/api/endpoints/annotate/anonymous/post/query.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
from sqlalchemy.ext.asyncio import AsyncSession
44

55
from src.api.endpoints.annotate.all.post.models.request import AllAnnotationPostInfo
6+
from src.db.models.impl.link.anonymous_sessions__name_suggestion import LinkAnonymousSessionNameSuggestion
67
from src.db.models.impl.url.suggestion.anonymous.agency.sqlalchemy import AnonymousAnnotationAgency
78
from src.db.models.impl.url.suggestion.anonymous.location.sqlalchemy import AnonymousAnnotationLocation
89
from src.db.models.impl.url.suggestion.anonymous.record_type.sqlalchemy import AnonymousAnnotationRecordType
910
from src.db.models.impl.url.suggestion.anonymous.url_type.sqlalchemy import AnonymousAnnotationURLType
11+
from src.db.models.impl.url.suggestion.name.enums import NameSuggestionSource
12+
from src.db.models.impl.url.suggestion.name.sqlalchemy import URLNameSuggestion
1013
from src.db.queries.base.builder import QueryBuilderBase
1114

1215

@@ -31,6 +34,28 @@ async def run(self, session: AsyncSession) -> None:
3134
)
3235
session.add(url_type_suggestion)
3336

37+
name_id: int | None
38+
if self.post_info.name_info.new_name is not None:
39+
name_suggestion = URLNameSuggestion(
40+
url_id=self.url_id,
41+
suggestion=self.post_info.name_info.new_name,
42+
source=NameSuggestionSource.USER
43+
)
44+
session.add(name_suggestion)
45+
await session.flush()
46+
name_id = name_suggestion.id
47+
elif self.post_info.name_info.existing_name_id is not None:
48+
name_id = self.post_info.name_info.existing_name_id
49+
else:
50+
name_id = None
51+
52+
if name_id is not None:
53+
name_suggestion = LinkAnonymousSessionNameSuggestion(
54+
suggestion_id=name_id,
55+
session_id=self.session_id
56+
)
57+
session.add(name_suggestion)
58+
3459
if self.post_info.record_type is not None:
3560
record_type_suggestion = AnonymousAnnotationRecordType(
3661
url_id=self.url_id,
Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,76 @@
11
from sqlalchemy import select, func
22

33
from src.core.tasks.url.operators.validate.queries.ctes.counts.core import ValidatedCountsCTEContainer
4+
from src.db.models.impl.link.anonymous_sessions__name_suggestion import LinkAnonymousSessionNameSuggestion
45
from src.db.models.impl.link.user_name_suggestion.sqlalchemy import LinkUserNameSuggestion
56
from src.db.models.impl.url.suggestion.name.sqlalchemy import URLNameSuggestion
67
from src.db.models.views.unvalidated_url import UnvalidatedURL
78

9+
_user_counts = (
10+
select(
11+
URLNameSuggestion.url_id,
12+
URLNameSuggestion.suggestion.label("entity"),
13+
func.count().label("votes")
14+
)
15+
.join(
16+
LinkUserNameSuggestion,
17+
LinkUserNameSuggestion.suggestion_id == URLNameSuggestion.id
18+
)
19+
.group_by(
20+
URLNameSuggestion.url_id,
21+
URLNameSuggestion.suggestion
22+
)
23+
.cte("user_counts")
24+
)
25+
26+
_anon_counts = (
27+
select(
28+
URLNameSuggestion.url_id,
29+
URLNameSuggestion.suggestion.label("entity"),
30+
func.count().label("votes")
31+
)
32+
.join(
33+
LinkAnonymousSessionNameSuggestion,
34+
LinkAnonymousSessionNameSuggestion.suggestion_id == URLNameSuggestion.id
35+
)
36+
.group_by(
37+
URLNameSuggestion.url_id,
38+
URLNameSuggestion.suggestion
39+
)
40+
.cte("anon_counts")
41+
)
42+
43+
_union_counts = (
44+
select(
45+
_user_counts.c.url_id,
46+
_user_counts.c.entity,
47+
_user_counts.c.votes
48+
)
49+
.union_all(
50+
select(
51+
_anon_counts.c.url_id,
52+
_anon_counts.c.entity,
53+
_anon_counts.c.votes
54+
)
55+
)
56+
.cte("counts_name_union")
57+
)
58+
59+
860
NAME_VALIDATION_COUNTS_CTE = ValidatedCountsCTEContainer(
961
(
1062
select(
11-
URLNameSuggestion.url_id,
12-
URLNameSuggestion.suggestion.label("entity"),
13-
func.count().label("votes")
63+
_union_counts.c.url_id,
64+
_union_counts.c.entity,
65+
func.sum(_union_counts.c.votes).label("votes")
1466
)
1567
.join(
1668
UnvalidatedURL,
17-
URLNameSuggestion.url_id == UnvalidatedURL.url_id
18-
)
19-
.join(
20-
LinkUserNameSuggestion,
21-
LinkUserNameSuggestion.suggestion_id == URLNameSuggestion.id
69+
_union_counts.c.url_id == UnvalidatedURL.url_id
2270
)
2371
.group_by(
24-
URLNameSuggestion.url_id,
25-
URLNameSuggestion.suggestion
72+
_union_counts.c.url_id,
73+
_union_counts.c.entity,
2674
)
2775
).cte("counts_name")
2876
)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from sqlalchemy import PrimaryKeyConstraint, ForeignKey, Integer, Column
2+
3+
from src.db.models.mixins import CreatedAtMixin, AnonymousSessionMixin
4+
from src.db.models.templates_.base import Base
5+
6+
7+
class LinkAnonymousSessionNameSuggestion(
8+
Base,
9+
AnonymousSessionMixin,
10+
CreatedAtMixin
11+
):
12+
__tablename__ = "link__anonymous_sessions__name_suggestions"
13+
suggestion_id = Column(
14+
Integer,
15+
ForeignKey("url_name_suggestions.id"),
16+
primary_key=True,
17+
nullable=False,
18+
)
19+
__table_args__ = (
20+
PrimaryKeyConstraint(
21+
"session_id",
22+
"suggestion_id"
23+
),
24+
)

tests/automated/integration/api/annotate/anonymous/test_core.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import pytest
44

55
from src.api.endpoints.annotate.all.get.models.name import NameAnnotationSuggestion
6-
from src.api.endpoints.annotate.all.get.models.response import GetNextURLForAllAnnotationResponse
76
from src.api.endpoints.annotate.all.post.models.agency import AnnotationPostAgencyInfo
87
from src.api.endpoints.annotate.all.post.models.location import AnnotationPostLocationInfo
98
from src.api.endpoints.annotate.all.post.models.name import AnnotationPostNameInfo
@@ -12,10 +11,12 @@
1211
from src.core.enums import RecordType
1312
from src.db.dtos.url.mapping_.simple import SimpleURLMapping
1413
from src.db.models.impl.flag.url_validated.enums import URLType
14+
from src.db.models.impl.link.anonymous_sessions__name_suggestion import LinkAnonymousSessionNameSuggestion
1515
from src.db.models.impl.url.suggestion.anonymous.agency.sqlalchemy import AnonymousAnnotationAgency
1616
from src.db.models.impl.url.suggestion.anonymous.location.sqlalchemy import AnonymousAnnotationLocation
1717
from src.db.models.impl.url.suggestion.anonymous.record_type.sqlalchemy import AnonymousAnnotationRecordType
1818
from src.db.models.impl.url.suggestion.anonymous.url_type.sqlalchemy import AnonymousAnnotationURLType
19+
from src.db.models.impl.url.suggestion.name.sqlalchemy import URLNameSuggestion
1920
from src.db.models.mixins import URLDependentMixin
2021
from tests.automated.integration.api.annotate.anonymous.helper import get_next_url_for_anonymous_annotation, \
2122
post_and_get_next_url_for_anonymous_annotation
@@ -90,6 +91,16 @@ async def test_annotate_anonymous(
9091
instance: model = instances[0]
9192
assert instance.url_id == get_response_1.next_annotation.url_info.url_id
9293

94+
# Check for existence of name suggestion (2 were added by setup)
95+
name_suggestions: list[URLNameSuggestion] = await ddc.adb_client.get_all(URLNameSuggestion)
96+
assert len(name_suggestions) == 3
97+
98+
# Check for existence of link
99+
link_instances: list[LinkAnonymousSessionNameSuggestion] = await ddc.adb_client.get_all(LinkAnonymousSessionNameSuggestion)
100+
assert len(link_instances) == 1
101+
link_instance: LinkAnonymousSessionNameSuggestion = link_instances[0]
102+
assert link_instance.session_id == session_id
103+
93104
# Run again without giving session ID, confirm original URL returned
94105
get_response_2: GetNextURLForAnonymousAnnotationResponse = await get_next_url_for_anonymous_annotation(rv)
95106
assert get_response_2.session_id != session_id

tests/automated/integration/tasks/url/impl/validate/helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ async def add_record_type_suggestions(
132132
async def add_name_suggestion(
133133
self,
134134
count: int = 1,
135-
) -> str:
135+
) -> int:
136136
name = f"Test Validate Task Name"
137137
suggestion_id: int = await self.db_data_creator.name_suggestion(
138138
url_id=self.url_id,
@@ -144,7 +144,7 @@ async def add_name_suggestion(
144144
suggestion_id=suggestion_id,
145145
user_id=next_int(),
146146
)
147-
return name
147+
return suggestion_id
148148

149149
async def check_name(self) -> None:
150150
urls: list[URL] = await self.adb_client.get_all(URL)

tests/automated/integration/tasks/url/impl/validate/test_data_source.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from src.core.enums import RecordType
1414
from src.core.tasks.url.operators.validate.core import AutoValidateURLTaskOperator
1515
from src.db.models.impl.flag.url_validated.enums import URLType
16+
from src.db.models.impl.link.anonymous_sessions__name_suggestion import LinkAnonymousSessionNameSuggestion
1617
from src.db.models.impl.url.suggestion.anonymous.agency.sqlalchemy import AnonymousAnnotationAgency
1718
from src.db.models.impl.url.suggestion.anonymous.location.sqlalchemy import AnonymousAnnotationLocation
1819
from src.db.models.impl.url.suggestion.anonymous.record_type.sqlalchemy import AnonymousAnnotationRecordType
@@ -45,7 +46,7 @@ async def test_data_source(
4546

4647
assert not await operator.meets_task_prerequisites()
4748

48-
await helper.add_name_suggestion(count=2)
49+
suggestion_id: int = await helper.add_name_suggestion(count=1)
4950

5051
assert not await operator.meets_task_prerequisites()
5152

@@ -74,11 +75,16 @@ async def test_data_source(
7475
session_id=session_id,
7576
url_id=helper.url_id
7677
)
78+
anon_name_link = LinkAnonymousSessionNameSuggestion(
79+
suggestion_id=suggestion_id,
80+
session_id=session_id
81+
)
7782
for model in [
7883
anon_url_type,
7984
anon_record_type,
8085
anon_location,
81-
anon_agency
86+
anon_agency,
87+
anon_name_link
8288
]:
8389
await helper.adb_client.add(model)
8490

0 commit comments

Comments
 (0)