diff --git a/execution_engine/converter/converter.py b/execution_engine/converter/converter.py index 01ec2948..88b186d3 100644 --- a/execution_engine/converter/converter.py +++ b/execution_engine/converter/converter.py @@ -4,7 +4,7 @@ from fhir.resources.codeableconcept import CodeableConcept from fhir.resources.element import Element from fhir.resources.extension import Extension -from fhir.resources.fhirtypes import Boolean +from fhir.resources.fhirtypes import BooleanType from fhir.resources.quantity import Quantity from fhir.resources.range import Range @@ -21,7 +21,7 @@ @staticmethod def select_value( root: Element, value_prefix: str -) -> CodeableConcept | Quantity | Range | Boolean: +) -> CodeableConcept | Quantity | Range | BooleanType: """ Selects the value of a characteristic by datatype. """ diff --git a/execution_engine/fhir/client.py b/execution_engine/fhir/client.py index 1a93444f..8ba23896 100644 --- a/execution_engine/fhir/client.py +++ b/execution_engine/fhir/client.py @@ -1,7 +1,7 @@ import logging import requests -from fhir.resources import FHIRAbstractModel, construct_fhir_element +from fhir.resources import FHIRAbstractModel, get_fhir_model_class class FHIRClient: @@ -15,7 +15,7 @@ def __init__(self, base_url: str): self.base_url = base_url def fetch_resource( - self, element_type: str, canonical_url: str, version: str + self, resource_type: str, canonical_url: str, version: str ) -> FHIRAbstractModel: """ Get a resource from the FHIR server by its canonical URL. @@ -24,7 +24,7 @@ def fetch_resource( try: r = requests.get( - f"{self.base_url}/{element_type}?url={canonical_url}&version={version}", + f"{self.base_url}/{resource_type}?url={canonical_url}&version={version}", timeout=10, ) except ConnectionRefusedError: @@ -34,4 +34,7 @@ def fetch_resource( assert ( r.status_code == 200 ), f"Could not get resource: HTTP status code {r.status_code}" - return construct_fhir_element(element_type, r.json()) + + resource = get_fhir_model_class(resource_type) + + return resource.model_validate(r.json()) diff --git a/execution_engine/omop/db/celida/views.py b/execution_engine/omop/db/celida/views.py index 0cd6e7a3..19f547da 100644 --- a/execution_engine/omop/db/celida/views.py +++ b/execution_engine/omop/db/celida/views.py @@ -14,84 +14,6 @@ from execution_engine.util.interval import IntervalType -def view_interval_coverage_slow() -> Select: - """ - Return Select statement for view to calculate the full day coverage of recommendation results. - """ - - one_day = func.cast(concat(1, "day"), INTERVAL) - rri = ResultInterval.__table__.alias("rri") - - date_series = select( - ExecutionRun.run_id, - func.generate_series( - func.date_trunc("day", ExecutionRun.observation_start_datetime), - func.date_trunc("day", ExecutionRun.observation_end_datetime), - one_day, - ) - .cast(Date) - .label("day"), - ExecutionRun.observation_start_datetime, - ExecutionRun.observation_end_datetime, - ).cte("date_series") - - interval_coverage = ( - select( - date_series.c.run_id, - rri.c.person_id, - rri.c.cohort_category, - rri.c.pi_pair_id, - rri.c.criterion_id, - date_series.c.day, - func.sum( - func.least( - date_series.c.day + one_day, - rri.c.interval_end, - date_series.c.observation_end_datetime, - ) - - func.greatest( - date_series.c.day, - rri.c.interval_start, - date_series.c.observation_start_datetime, - ) - ).label("covered_time"), - func.bool_or(rri.c.interval_type == IntervalType.POSITIVE).label( - "has_positive" - ), - func.bool_or(rri.c.interval_type == IntervalType.NO_DATA).label( - "has_no_data" - ), - func.bool_or(rri.c.interval_type == IntervalType.NEGATIVE).label( - "has_negative" - ), - ) - .select_from(date_series) - .outerjoin(rri, (date_series.c.run_id == rri.c.run_id)) - .filter( - rri.c.interval_start < date_series.c.day + one_day, - rri.c.interval_end > date_series.c.day, - ) - .group_by( - date_series.c.run_id, - date_series.c.day, - rri.c.person_id, - rri.c.cohort_category, - rri.c.pi_pair_id, - rri.c.criterion_id, - ) - ) - - return interval_coverage - - -interval_coverage_slow = view( - "interval_coverage_slow", - Base.metadata, - view_interval_coverage_slow(), - schema=SCHEMA_NAME, -) - - def view_interval_coverage() -> Select: """ Return Select statement for view to calculate the full day coverage of recommendation results. diff --git a/execution_engine/omop/db/view.py b/execution_engine/omop/db/view.py index 0ba1d01f..ec7aa85a 100644 --- a/execution_engine/omop/db/view.py +++ b/execution_engine/omop/db/view.py @@ -62,11 +62,6 @@ def view( :param schema: The schema name. :return: The table object. """ - t = table(name, schema=schema) - - t._columns._populate_separate_keys( - col._make_proxy(t) for col in selectable.selected_columns - ) def view_exists( ddl: DDLElement, target: MetaData, connection: Connection, **kw: Any @@ -85,6 +80,16 @@ def view_doesnt_exist( """ return not view_exists(ddl, target, connection, **kw) + t = table( + name, + *( + sa.Column(c.name, c.type, primary_key=c.primary_key) + for c in selectable.selected_columns + ), + schema=schema + ) + t.primary_key.update(c for c in t.c if c.primary_key) + sa.event.listen( metadata, "after_create", @@ -95,4 +100,5 @@ def view_doesnt_exist( "before_drop", DropView(name, schema).execute_if(callable_=view_exists), ) + return t diff --git a/requirements-dev.txt b/requirements-dev.txt index 1fed6eed..ddf6a871 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ pytest==8.3.4 -pytest-postgresql==6.1.1 +pytest-postgresql==7.0.0 tqdm==4.67.1 pytest-cov==6.0.0 pytest-env==1.1.5 diff --git a/requirements.txt b/requirements.txt index 9e16aaca..18453fbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ requests==2.32.3 python-dotenv==1.0.1 pyyaml==6.0.2 -fhir.resources==7.1.0 +fhir.resources==8.0.0 sympy==1.13.3 pandas==2.2.3 -psycopg[binary]==3.2.4 -sqlalchemy==2.0.37 -pydantic==2.10.5 -pydantic-settings==2.7.1 +psycopg[binary]==3.2.5 +sqlalchemy==2.0.38 +pydantic==2.10.6 +pydantic-settings==2.8.0 uvicorn[standard]==0.34.0 -fastapi==0.115.6 +fastapi==0.115.8 pendulum==3.0.0 networkx==3.4.2 -numpy==2.2.2 -pytz==2024.2 -cython==3.0.11 +numpy==2.2.3 +pytz==2025.1 +cython==3.0.12 setuptools==75.8.0 sortedcontainers==2.4.0 diff --git a/tests/execution_engine/fhir/test_recommendation.py b/tests/execution_engine/fhir/test_recommendation.py index 71a6e741..0aa496ca 100644 --- a/tests/execution_engine/fhir/test_recommendation.py +++ b/tests/execution_engine/fhir/test_recommendation.py @@ -1,7 +1,7 @@ from unittest.mock import patch import pytest -from fhir.resources import construct_fhir_element +from fhir.resources import get_fhir_model_class from execution_engine.constants import CS_PLAN_DEFINITION_TYPE from execution_engine.fhir.client import FHIRClient @@ -20,8 +20,8 @@ def mock_load(self, test_class): @pytest.fixture def mock_fetch_resource_unknown(self): - plan_definition = construct_fhir_element( - "PlanDefinition", + + plan_definition = get_fhir_model_class("PlanDefinition").model_validate( { "status": "draft", "action": [], @@ -39,8 +39,7 @@ def mock_fetch_resource_unknown(self): @pytest.fixture def mock_fetch_resource_no_partOf(self): - plan_definition = construct_fhir_element( - "PlanDefinition", + plan_definition = get_fhir_model_class("PlanDefinition").model_validate( { "status": "draft", "action": [], @@ -62,8 +61,8 @@ def test_class(self): @pytest.fixture def mock_fetch_recommendation(self, test_class): - plan_definition = construct_fhir_element( - "PlanDefinition", {"status": "draft", "action": []} + plan_definition = get_fhir_model_class("PlanDefinition").model_validate( + {"status": "draft", "action": []} ) with patch.object( test_class, "fetch_recommendation", return_value=plan_definition @@ -159,8 +158,8 @@ def test_class(self): @pytest.fixture def mock_fetch_recommendation_plan(self): - plan_definition = construct_fhir_element( - "PlanDefinition", {"status": "draft", "action": []} + plan_definition = get_fhir_model_class("PlanDefinition").model_validate( + {"status": "draft", "action": []} ) with patch.object( RecommendationPlan, @@ -171,8 +170,7 @@ def mock_fetch_recommendation_plan(self): @pytest.fixture def mock_fetch_resource_goals(self): - plan_definition = construct_fhir_element( - "PlanDefinition", + plan_definition = get_fhir_model_class("PlanDefinition").model_validate( { "status": "draft", "action": [], diff --git a/tests/recommendation/test_recommendation_base.py b/tests/recommendation/test_recommendation_base.py index 8758e127..a78af845 100644 --- a/tests/recommendation/test_recommendation_base.py +++ b/tests/recommendation/test_recommendation_base.py @@ -953,7 +953,10 @@ def get_query(t, category): t.c.cohort_category, t.c.valid_date, ) - .outerjoin(PopulationInterventionPair) + .outerjoin( + PopulationInterventionPair, + PopulationInterventionPair.pi_pair_id == t.c.pi_pair_id, + ) .where(t.c.criterion_id.is_(None)) .where(t.c.cohort_category.in_(category)) )