Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Add dependent locations

Revision ID: 7b955c783e27
Revises: 3687026267fc
Create Date: 2025-09-26 07:18:37.916841

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

Check warning on line 11 in alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py#L11 <401>

'sqlalchemy as sa' imported but unused
Raw output
./alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py:11:1: F401 'sqlalchemy as sa' imported but unused


# revision identifiers, used by Alembic.
revision: str = '7b955c783e27'
down_revision: Union[str, None] = '3687026267fc'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:

Check warning on line 21 in alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py#L21 <103>

Missing docstring in public function
Raw output
./alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py:21:1: D103 Missing docstring in public function
op.execute("""
create view dependent_locations(parent_location_id, dependent_location_id) as
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.state_id = lp.state_id AND ld.type = 'County'::location_type AND lp.type = 'State'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.county_id = lp.county_id AND ld.type = 'Locality'::location_type AND lp.type = 'County'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.state_id = lp.state_id AND ld.type = 'Locality'::location_type AND lp.type = 'State'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON lp.type = 'National'::location_type AND (ld.type = ANY
(ARRAY ['State'::location_type, 'County'::location_type, 'Locality'::location_type]))
""")


def downgrade() -> None:

Check warning on line 55 in alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py#L55 <103>

Missing docstring in public function
Raw output
./alembic/versions/2025_09_26_0718-7b955c783e27_add_dependent_locations.py:55:1: D103 Missing docstring in public function
pass
Empty file.
48 changes: 48 additions & 0 deletions src/api/endpoints/search/agency/ctes/with_location_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from sqlalchemy import select, literal, CTE, Column

Check warning on line 1 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L1 <100>

Missing docstring in public module
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:1:1: D100 Missing docstring in public module

from src.db.models.impl.link.agency_location.sqlalchemy import LinkAgencyLocation
from src.db.models.views.dependent_locations import DependentLocationView


class WithLocationIdCTEContainer:

Check warning on line 7 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L7 <101>

Missing docstring in public class
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:7:1: D101 Missing docstring in public class

def __init__(self, location_id: int):

Check warning on line 9 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L9 <107>

Missing docstring in __init__
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:9:1: D107 Missing docstring in __init__

target_locations_cte = (
select(
literal(location_id).label("location_id")
)
.union(
select(
DependentLocationView.dependent_location_id
)
.where(
DependentLocationView.parent_location_id == location_id
)
)
.cte("target_locations")
)

self._cte = (
select(
LinkAgencyLocation.agency_id,
LinkAgencyLocation.location_id
)
.join(
target_locations_cte,
target_locations_cte.c.location_id == LinkAgencyLocation.location_id
)
.cte("with_location_id")
)

@property
def cte(self) -> CTE:

Check warning on line 39 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L39 <102>

Missing docstring in public method
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:39:1: D102 Missing docstring in public method
return self._cte

@property
def agency_id(self) -> Column:

Check warning on line 43 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L43 <102>

Missing docstring in public method
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:43:1: D102 Missing docstring in public method
return self._cte.c.agency_id

@property
def location_id(self) -> Column:

Check warning on line 47 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L47 <102>

Missing docstring in public method
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:47:1: D102 Missing docstring in public method
return self._cte.c.location_id

Check warning on line 48 in src/api/endpoints/search/agency/ctes/with_location_id.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/ctes/with_location_id.py#L48 <292>

no newline at end of file
Raw output
./src/api/endpoints/search/agency/ctes/with_location_id.py:48:39: W292 no newline at end of file
25 changes: 16 additions & 9 deletions src/api/endpoints/search/agency/query.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Sequence

from sqlalchemy import select, func, RowMapping
from sqlalchemy import select, func, RowMapping, or_

Check warning on line 3 in src/api/endpoints/search/agency/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/query.py#L3 <401>

'sqlalchemy.or_' imported but unused
Raw output
./src/api/endpoints/search/agency/query.py:3:1: F401 'sqlalchemy.or_' imported but unused
from sqlalchemy.ext.asyncio import AsyncSession

from src.api.endpoints.search.agency.ctes.with_location_id import WithLocationIdCTEContainer
from src.api.endpoints.search.agency.models.response import AgencySearchResponse
from src.db.helpers.session import session_helper as sh
from src.db.models.impl.agency.sqlalchemy import Agency
from src.db.models.impl.link.agency_location.sqlalchemy import LinkAgencyLocation
from src.db.models.views.dependent_locations import DependentLocationView

Check warning on line 11 in src/api/endpoints/search/agency/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/search/agency/query.py#L11 <401>

'src.db.models.views.dependent_locations.DependentLocationView' imported but unused
Raw output
./src/api/endpoints/search/agency/query.py:11:1: F401 'src.db.models.views.dependent_locations.DependentLocationView' imported but unused
from src.db.models.views.location_expanded import LocationExpandedView
from src.db.queries.base.builder import QueryBuilderBase

Expand All @@ -30,20 +32,25 @@
Agency.name.label("agency_name"),
LocationExpandedView.display_name.label("location_display_name")
)
.join(
)
if self.location_id is None:
query = query.join(
LinkAgencyLocation,
LinkAgencyLocation.agency_id == Agency.agency_id
)
.join(
).join(
LocationExpandedView,
LocationExpandedView.id == LinkAgencyLocation.location_id
)
)

if self.location_id is not None:
query = query.where(
LocationExpandedView.id == self.location_id
else:
with_location_id_cte_container = WithLocationIdCTEContainer(self.location_id)
query = query.join(
with_location_id_cte_container.cte,
with_location_id_cte_container.agency_id == Agency.agency_id
).join(
LocationExpandedView,
LocationExpandedView.id == with_location_id_cte_container.location_id
)

if self.query is not None:
query = query.order_by(
func.similarity(
Expand Down
54 changes: 54 additions & 0 deletions src/db/models/views/dependent_locations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
create view dependent_locations(parent_location_id, dependent_location_id) as
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.state_id = lp.state_id AND ld.type = 'County'::location_type AND lp.type = 'State'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.county_id = lp.county_id AND ld.type = 'Locality'::location_type AND lp.type = 'County'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON ld.state_id = lp.state_id AND ld.type = 'Locality'::location_type AND lp.type = 'State'::location_type
UNION ALL
SELECT
lp.id AS parent_location_id,
ld.id AS dependent_location_id
FROM
locations lp
JOIN locations ld ON lp.type = 'National'::location_type AND (ld.type = ANY
(ARRAY ['State'::location_type, 'County'::location_type, 'Locality'::location_type]));
"""
from sqlalchemy import Column, Integer, ForeignKey

from src.db.models.mixins import ViewMixin
from src.db.models.templates_.base import Base


class DependentLocationView(Base, ViewMixin):

Check warning on line 38 in src/db/models/views/dependent_locations.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/db/models/views/dependent_locations.py#L38 <101>

Missing docstring in public class
Raw output
./src/db/models/views/dependent_locations.py:38:1: D101 Missing docstring in public class

__tablename__ = "dependent_locations"
__table_args__ = (
{"info": "view"}
)

parent_location_id = Column(
Integer,
ForeignKey("locations.id"),
primary_key=True,
)
dependent_location_id = Column(
Integer,
ForeignKey("locations.id"),
primary_key=True
)
12 changes: 11 additions & 1 deletion tests/automated/integration/api/search/agency/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ async def test_search_agency(
assert responses[1]["agency_id"] == agency_b_id
assert responses[2]["agency_id"] == agency_c_id

# Filter based on location ID
responses = api_test_helper.request_validator.get_v2(
url="/search/agency",
params={
Expand All @@ -50,4 +51,13 @@ async def test_search_agency(
assert responses[0]["agency_id"] == agency_a_id
assert responses[1]["agency_id"] == agency_c_id


# Filter again based on location ID but with Allegheny County
# Confirm pittsburgh agencies are picked up
responses = api_test_helper.request_validator.get_v2(
url="/search/agency",
params={
"query": "A Agency",
"location_id": allegheny_county.location_id
}
)
assert len(responses) == 3