Skip to content

Commit b35196e

Browse files
committed
feat: add ValueScalar
1 parent a7f6831 commit b35196e

File tree

4 files changed

+68
-21
lines changed

4 files changed

+68
-21
lines changed

execution_engine/converter/criterion.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from execution_engine.omop.vocabulary import standard_vocabulary
1919
from execution_engine.util.value import Value, ValueConcept, ValueNumber
20+
from execution_engine.util.value.value import ValueScalar
2021

2122

2223
def select_value(
@@ -45,12 +46,12 @@ def parse_code_value(
4546
return parse_code(code), parse_value(value_parent, value_prefix)
4647

4748

48-
def parse_code(code: CodeableConcept) -> Concept:
49+
def parse_code(code: CodeableConcept, standard: bool = True) -> Concept:
4950
"""
5051
Parses a FHIR code into a standard OMOP concept.
5152
"""
5253
cc = get_coding(code)
53-
return standard_vocabulary.get_standard_concept(cc.system, cc.code)
54+
return standard_vocabulary.get_concept(cc.system, cc.code, standard=standard)
5455

5556

5657
def code_display(code: CodeableConcept) -> str:
@@ -85,28 +86,44 @@ def parse_value(
8586
)
8687
value_obj = ValueConcept(value=value_omop_concept)
8788
elif isinstance(value, Quantity):
88-
value_obj = ValueNumber(
89-
value=value.value,
90-
unit=standard_vocabulary.get_standard_unit_concept(value.code),
91-
)
89+
# Check if there's a code to determine if we use ValueNumber or ValueScalar
90+
if value.code is None:
91+
value_obj = ValueScalar(value=value.value)
92+
else:
93+
value_obj = ValueNumber(
94+
value=value.value,
95+
unit=standard_vocabulary.get_standard_unit_concept(value.code),
96+
)
9297
elif isinstance(value, Range):
93-
if value.low is not None and value.high is not None:
94-
assert (
95-
value.low.code == value.high.code
96-
), "Range low and high unit must be the same"
97-
98-
unit_code = value.low.code if value.low is not None else value.high.code
99-
98+
# Handle the case where range has low/high with or without code
10099
def value_or_none(x: Quantity | None) -> float | None:
101100
if x is None:
102101
return None
103102
return x.value
104103

105-
value_obj = ValueNumber(
106-
unit=standard_vocabulary.get_standard_unit_concept(unit_code),
107-
value_min=value_or_none(value.low),
108-
value_max=value_or_none(value.high),
109-
)
104+
low_code = value.low.code if value.low is not None else None
105+
high_code = value.high.code if value.high is not None else None
106+
107+
# If both low/high exist, ensure they share the same unit
108+
if value.low is not None and value.high is not None:
109+
assert low_code == high_code, "Range low and high unit must be the same"
110+
111+
# Decide if we have a code at all
112+
unit_code = low_code if low_code is not None else high_code
113+
114+
if unit_code is None:
115+
# No code => ValueScalar
116+
value_obj = ValueScalar(
117+
value_min=value_or_none(value.low),
118+
value_max=value_or_none(value.high),
119+
)
120+
else:
121+
value_obj = ValueNumber(
122+
unit=standard_vocabulary.get_standard_unit_concept(unit_code),
123+
value_min=value_or_none(value.low),
124+
value_max=value_or_none(value.high),
125+
)
126+
110127
else:
111128
raise NotImplementedError(f"Value type {type(value)} not implemented")
112129

@@ -178,7 +195,8 @@ def get(self, fhir: Element) -> CriterionConverter:
178195
return converter.from_fhir(fhir)
179196

180197
message = f"Cannot find a converter for the given FHIR element: {fhir.__class__.__name__}"
181-
if fhir.id is not None:
198+
199+
if getattr(fhir, "id", None) is not None:
182200
message += f' (id="{fhir.id}")'
183201

184202
message += f"\nFHIR element details: {json.dumps(fhir.model_dump(), indent=2)}"

execution_engine/converter/time_from_event/abstract.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ def from_fhir(cls, fhir: Element) -> "TemporalIndicator":
100100
), f"Expected timeFromEvent type, got {fhir.__class__.__name__}"
101101

102102
tfe: EvidenceVariableCharacteristicTimeFromEvent = fhir
103-
value = cast(ValueNumeric, parse_value(tfe.range))
103+
104+
value = None
105+
106+
if tfe.range is not None:
107+
value = cast(ValueNumeric, parse_value(tfe.range))
104108

105109
return cls(value)
106110

execution_engine/util/value/time.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ class ValueCount(ValueNumeric[NonNegativeInt, None]):
6464
_validate_value_max = field_validator("value_max", mode="before")(check_int)
6565
_validate_no_unit = field_validator("unit", mode="before")(check_unit_none)
6666

67+
def supports_units(self) -> bool:
68+
"""
69+
Returns true if this type of value supports units.
70+
"""
71+
return False
72+
6773

6874
class ValueDuration(ValueNumeric[float, TimeUnit]):
6975
"""

execution_engine/util/value/value.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from abc import ABC, abstractmethod
22
from typing import Any, Generic, Type, TypeVar, cast
33

4-
from pydantic import BaseModel, model_validator
4+
from pydantic import BaseModel, field_validator, model_validator
55
from sqlalchemy import (
66
ColumnClause,
77
ColumnElement,
@@ -307,6 +307,25 @@ def _get_unit_clause(self, table: TableClause | None) -> ColumnClause:
307307
return c_unit == self.unit.concept_id
308308

309309

310+
class ValueScalar(ValueNumeric[float, None]):
311+
"""
312+
A numeric value without a unit.
313+
"""
314+
315+
unit: None = None
316+
317+
_validate_value = field_validator("value", mode="before")(check_int)
318+
_validate_value_min = field_validator("value_min", mode="before")(check_int)
319+
_validate_value_max = field_validator("value_max", mode="before")(check_int)
320+
_validate_no_unit = field_validator("unit", mode="before")(check_unit_none)
321+
322+
def supports_units(self) -> bool:
323+
"""
324+
Returns true if this type of value supports units.
325+
"""
326+
return False
327+
328+
310329
class ValueConcept(Value):
311330
"""
312331
A value of type concept.

0 commit comments

Comments
 (0)