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
13 changes: 13 additions & 0 deletions offsets_db_api/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ def get_bboxes_for_projects(project_ids: list[str]) -> dict[str, dict[str, float
return {pid: bbox_lookup[pid] for pid in project_ids if pid in bbox_lookup}


def get_projects_with_geometry() -> set[str]:
"""
Get the set of project IDs that have geographic boundaries.

Returns
-------
set
Set of project IDs that have boundaries
"""
bbox_lookup = load_project_bboxes()
return set(bbox_lookup.keys())


def clear_bbox_cache():
"""Clear the cached bbox data to force a reload."""
load_project_bboxes.cache_clear()
Expand Down
15 changes: 15 additions & 0 deletions offsets_db_api/routers/credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from offsets_db_api.cache import CACHE_NAMESPACE
from offsets_db_api.common import build_filters
from offsets_db_api.database import get_session
from offsets_db_api.geo import get_projects_with_geometry
from offsets_db_api.log import get_logger
from offsets_db_api.models import Credit, PaginatedCredits, Project
from offsets_db_api.schemas import (
Expand Down Expand Up @@ -36,6 +37,10 @@ async def get_credits(
project_filters: ProjectFilters = Depends(get_project_filters),
credit_filters: CreditFilters = Depends(get_credit_filters),
beneficiary_filters: BeneficiaryFilters = Depends(get_beneficiary_filters),
geography: bool | None = Query(
None,
description='Filter by geographic boundaries. True = only credits from projects with boundaries, False = only credits from projects without boundaries, None = no filter.',
),
sort: list[str] = Query(
default=['project_id'],
description='List of sorting parameters in the format `field_name` or `+field_name` for ascending order or `-field_name` for descending order.',
Expand All @@ -59,6 +64,16 @@ async def get_credits(
if project_id:
filters.insert(0, ('project_id', project_id, '==', Project))

# Filter by geographic boundaries
if geography is not None:
projects_with_geo = get_projects_with_geometry()
if geography:
# Only credits from projects WITH boundaries
statement = statement.where(col(Project.project_id).in_(projects_with_geo))
else:
# Only credits from projects WITHOUT boundaries
statement = statement.where(~col(Project.project_id).in_(projects_with_geo))

for attribute, values, operation, model in filters:
statement = apply_filters(
statement=statement,
Expand Down
24 changes: 23 additions & 1 deletion offsets_db_api/routers/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from offsets_db_api.cache import CACHE_NAMESPACE
from offsets_db_api.common import build_filters
from offsets_db_api.database import get_session
from offsets_db_api.geo import get_bbox_for_project, get_bboxes_for_projects
from offsets_db_api.geo import (
get_bbox_for_project,
get_bboxes_for_projects,
get_projects_with_geometry,
)
from offsets_db_api.log import get_logger
from offsets_db_api.models import (
Clip,
Expand Down Expand Up @@ -65,6 +69,10 @@ async def get_projects(
description='Case insensitive search string. Currently searches on `project_id` and `name` fields only.',
),
beneficiary_filters: BeneficiaryFilters = Depends(get_beneficiary_filters),
geography: bool | None = Query(
None,
description='Filter by geographic boundaries. True = only projects with boundaries, False = only projects without boundaries, None = no filter.',
),
current_page: int = Query(1, description='Page number', ge=1),
per_page: int = Query(100, description='Items per page', le=200, ge=1),
sort: list[str] = Query(
Expand All @@ -84,6 +92,20 @@ async def get_projects(

filters = build_filters(project_filters=project_filters)

# Filter by geographic boundaries
if geography is not None:
projects_with_geo = get_projects_with_geometry()
if geography:
# Only projects WITH boundaries
matching_projects = matching_projects.where(
col(Project.project_id).in_(projects_with_geo)
)
else:
# Only projects WITHOUT boundaries
matching_projects = matching_projects.where(
~col(Project.project_id).in_(projects_with_geo)
)

if search:
search_pattern = f'%{search}%'
matching_projects = matching_projects.where(
Expand Down
Loading