omop-semantics 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/PKG-INFO +2 -2
  2. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/pyproject.toml +2 -2
  3. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/__init__.py +36 -0
  4. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/instance_loader.py +7 -0
  5. omop_semantics-0.3.0/src/omop_semantics/runtime/output_definitions.py +469 -0
  6. omop_semantics-0.3.0/src/omop_semantics/runtime/projection.py +211 -0
  7. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/resolver.py +31 -0
  8. omop_semantics-0.3.0/src/omop_semantics/schema/configuration/core/omop_profiles.yaml +81 -0
  9. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/generated_models/omop_semantic_registry.py +13 -0
  10. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/profile_groups.yaml +44 -1
  11. omop_semantics-0.3.0/src/omop_semantics/schema/instances/profiles.yaml +102 -0
  12. omop_semantics-0.2.2/src/omop_semantics/schema/configuration/core/omop_profiles.yaml +0 -43
  13. omop_semantics-0.2.2/src/omop_semantics/schema/instances/profiles.yaml +0 -39
  14. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/README.md +0 -0
  15. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/__init__.py +0 -0
  16. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/base.py +0 -0
  17. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/default_valuesets.py +0 -0
  18. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/renderers.py +0 -0
  19. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/unknown_handlers.py +0 -0
  20. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/runtime/value_sets.py +0 -0
  21. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/__init__.py +0 -0
  22. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/codegen.py +0 -0
  23. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/core/omop_base.yaml +0 -0
  24. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/core/omop_named_sets.yaml +0 -0
  25. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/core/omop_templates.yaml +0 -0
  26. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/core/omop_unknowns.yaml +0 -0
  27. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/profiles/omop_episodes.yaml +0 -0
  28. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/profiles/omop_modifiers.yaml +0 -0
  29. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/profiles/omop_staging.yaml +0 -0
  30. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/configuration/registry/omop_semantic_registry.yaml +0 -0
  31. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/dump.py +0 -0
  32. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/generated_models/__init__.py +0 -0
  33. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/generated_models/omop_named_sets.py +0 -0
  34. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/generated_models/template_set.py +0 -0
  35. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/demographic.yaml +0 -0
  36. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/enumerators.yaml +0 -0
  37. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/genomic.yaml +0 -0
  38. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/provider_specialty.yaml +0 -0
  39. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/schema/instances/valuesets.yaml +0 -0
  40. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/unknowns.py +0 -0
  41. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/utils/__init__.py +0 -0
  42. {omop_semantics-0.2.2 → omop_semantics-0.3.0}/src/omop_semantics/utils/paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: omop-semantics
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Define, validate, and use schema-backed semantic conventions for OMOP CDM
5
5
  Author: gkennos
6
6
  Author-email: gkennos <georgina.kennedy@unsw.edu.au>
@@ -17,7 +17,7 @@ Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
17
17
  Requires-Dist: mypy>=1.8 ; extra == 'dev'
18
18
  Requires-Dist: ruff>=0.4 ; extra == 'dev'
19
19
  Requires-Dist: rich>=13.0 ; extra == 'dev'
20
- Requires-Dist: tornado>=6.5.5 ; extra == 'dev'
20
+ Requires-Dist: tornado>=6.5.7 ; extra == 'dev'
21
21
  Requires-Dist: pygments>=2.20.0 ; extra == 'dev'
22
22
  Requires-Dist: mkdocs-material>=9.7.6 ; extra == 'docs'
23
23
  Requires-Dist: mkdocstrings-python>=2.0.1 ; extra == 'docs'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "omop-semantics"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Define, validate, and use schema-backed semantic conventions for OMOP CDM"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -28,7 +28,7 @@ dev = [
28
28
  "mypy>=1.8",
29
29
  "ruff>=0.4",
30
30
  "rich>=13.0",
31
- "tornado>=6.5.5",
31
+ "tornado>=6.5.7",
32
32
  "pygments>=2.20.0"
33
33
  ]
34
34
  docs = [
@@ -20,6 +20,26 @@ from .resolver import (
20
20
  OmopSemanticObject,
21
21
  SemanticProfileRuntime
22
22
  )
23
+ from .projection import (
24
+ ProjectionProfileRuntime,
25
+ RuntimeProjectionProfile,
26
+ ProjectedOutputRow,
27
+ ProjectedOutputLink,
28
+ ProjectedOutputBundle,
29
+ SuppressedRow,
30
+ )
31
+ from .output_definitions import (
32
+ ContextFieldRef,
33
+ OutputRowProjection,
34
+ OutputLinkRule,
35
+ OutputDefinition,
36
+ CompiledOutputDefinition,
37
+ OutputDefinitionRuntime,
38
+ DerivationRule,
39
+ NO_DEFAULT,
40
+ SpecialValuePolicy,
41
+ SUPPRESSION_MODES,
42
+ )
23
43
  from typing import TYPE_CHECKING
24
44
 
25
45
 
@@ -39,4 +59,20 @@ __all__ = [
39
59
  "render_registry_fragment",
40
60
  "render_profile_groups",
41
61
  "SemanticProfileRuntime",
62
+ "ProjectionProfileRuntime",
63
+ "RuntimeProjectionProfile",
64
+ "ProjectedOutputRow",
65
+ "ProjectedOutputLink",
66
+ "ProjectedOutputBundle",
67
+ "SuppressedRow",
68
+ "ContextFieldRef",
69
+ "OutputRowProjection",
70
+ "OutputLinkRule",
71
+ "OutputDefinition",
72
+ "CompiledOutputDefinition",
73
+ "OutputDefinitionRuntime",
74
+ "DerivationRule",
75
+ "NO_DEFAULT",
76
+ "SpecialValuePolicy",
77
+ "SUPPRESSION_MODES",
42
78
  ]
@@ -45,6 +45,13 @@ def interpolate_profiles(group: dict, profiles: dict[str, OmopCdmProfile]) -> No
45
45
  "cdm_table": profile.cdm_table,
46
46
  "concept_slot": profile.concept_slot,
47
47
  "value_slot": profile.value_slot,
48
+ "unit_slot": profile.unit_slot,
49
+ "operator_slot": profile.operator_slot,
50
+ "modifier_slot": profile.modifier_slot,
51
+ "extra_concept_slots": profile.extra_concept_slots,
52
+ "numeric_slots": profile.numeric_slots,
53
+ "string_slots": profile.string_slots,
54
+ "reference_slots": profile.reference_slots,
48
55
  }
49
56
 
50
57
  member.pop("concept_slot", None)
@@ -0,0 +1,469 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Iterable, Mapping, Sequence
5
+
6
+ from .projection import (
7
+ ProjectedOutputBundle,
8
+ ProjectedOutputLink,
9
+ ProjectedOutputRow,
10
+ ProjectionProfileRuntime,
11
+ RuntimeProjectionProfile,
12
+ SuppressedRow,
13
+ )
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class ContextFieldRef:
18
+ """
19
+ Reference to a value in the projection context.
20
+
21
+ The path is dot-delimited, for example:
22
+
23
+ - `grounded.concept_id`
24
+ - `source.numeric_value`
25
+ - `modifiers.intent_concept_id`
26
+ """
27
+
28
+ path: str
29
+
30
+
31
+ BindingValue = Any | ContextFieldRef
32
+
33
+
34
+ class _NoDefault:
35
+ def __repr__(self) -> str:
36
+ return "NO_DEFAULT"
37
+
38
+
39
+ NO_DEFAULT: Any = _NoDefault()
40
+ """Sentinel distinguishing "no default configured" from a configured default of `None`."""
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class DerivationRule:
45
+ """
46
+ Resolve one row's slot value from a source field other than the one that
47
+ grounded the row's entity concept, via a source-code lookup — with an
48
+ explicit escape hatch for codes that should suppress the row entirely.
49
+
50
+ This is the n:1 fan-in counterpart to `field_bindings` (which only ever
51
+ read from the same context a row's own entity concept was grounded from).
52
+ The canonical case is a diagnosis paired with a separately-collected
53
+ role/status field (e.g. Primary/Contributing/Non-contributing): the role
54
+ field is never grounded on its own, and its raw code either resolves
55
+ `condition_status_concept_id` via `code_map` or, for "Non-contributing",
56
+ drops the record entirely via `suppress_codes`.
57
+ """
58
+
59
+ target_row: str
60
+ target_slot: str
61
+ source_field: ContextFieldRef
62
+ code_map: Mapping[str, Any] = field(default_factory=dict)
63
+ suppress_codes: frozenset[str] = frozenset()
64
+ default: Any = NO_DEFAULT
65
+
66
+
67
+ SUPPRESSION_MODES = frozenset({"drop", "fail", "keep_as_value", "keep_as_modifier"})
68
+ """Recognized `SpecialValuePolicy.suppression_mode` values.
69
+
70
+ Only `drop` and `fail` have runtime behavior today — both are the degenerate,
71
+ single-field case validated against real data (a "meets criteria for X" Yes/No
72
+ item, where the negative answer carries no positive clinical content of its
73
+ own). `keep_as_value` and `keep_as_modifier` are named here per the schema
74
+ proposal so definitions can be authored against a stable enum, but they raise
75
+ `NotImplementedError` if actually triggered — there is no validated use case
76
+ yet for what they should bind.
77
+ """
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class SpecialValuePolicy:
82
+ """
83
+ Row-level suppression (or other handling) driven by the row's *own*
84
+ source value — the counterpart to `DerivationRule`, which reads a
85
+ different source field. Attach to `OutputRowProjection.special_value_policy`.
86
+
87
+ The canonical case is a Yes/No field phrased "meets criteria for X": Yes
88
+ grounds the row normally, No means nothing should be written for it. There
89
+ is no sibling field to consult, so no `DerivationRule` is involved — the
90
+ same value used to ground the row is checked against
91
+ `allowed_special_values`.
92
+ """
93
+
94
+ source_field: ContextFieldRef
95
+ allowed_special_values: frozenset[str] = frozenset()
96
+ suppression_mode: str = "drop"
97
+
98
+
99
+ @dataclass(frozen=True)
100
+ class OutputRowProjection:
101
+ """
102
+ Declarative row projection definition.
103
+ """
104
+
105
+ row_id: str
106
+ profile_name: str
107
+ field_bindings: Mapping[str, BindingValue] = field(default_factory=dict)
108
+ defaults: Mapping[str, Any] = field(default_factory=dict)
109
+ required: bool = True
110
+ special_value_policy: SpecialValuePolicy | None = None
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class OutputLinkRule:
115
+ """
116
+ Declarative relationship between projected rows.
117
+ """
118
+
119
+ source_row: str
120
+ target_row: str
121
+ relationship_type: str
122
+ source_field: str | None = None
123
+ target_field: str | None = None
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class OutputDefinition:
128
+ """
129
+ Minimal programmatic output definition.
130
+
131
+ This is intentionally additive and runtime-only for the first execution
132
+ slice. It provides enough structure to compile and deterministically
133
+ project rows before a dedicated YAML/schema authoring surface is added.
134
+ """
135
+
136
+ name: str
137
+ role: str
138
+ template_names: tuple[str, ...] = ()
139
+ row_projections: tuple[OutputRowProjection, ...] = ()
140
+ link_rules: tuple[OutputLinkRule, ...] = ()
141
+ derivation_rules: tuple[DerivationRule, ...] = ()
142
+ notes: tuple[str, ...] = ()
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class CompiledRowProjection:
147
+ row_id: str
148
+ profile: RuntimeProjectionProfile
149
+ field_bindings: Mapping[str, BindingValue]
150
+ defaults: Mapping[str, Any]
151
+ required: bool = True
152
+ special_value_policy: SpecialValuePolicy | None = None
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class CompiledOutputDefinition:
157
+ name: str
158
+ role: str
159
+ template_names: tuple[str, ...]
160
+ row_projections: tuple[CompiledRowProjection, ...]
161
+ link_rules: tuple[OutputLinkRule, ...]
162
+ derivation_rules: tuple[DerivationRule, ...]
163
+ notes: tuple[str, ...]
164
+
165
+
166
+ class OutputDefinitionRuntime:
167
+ """
168
+ Compiler and executor for programmatic output definitions.
169
+ """
170
+
171
+ def __init__(
172
+ self,
173
+ definitions: Iterable[OutputDefinition],
174
+ projection_profiles: ProjectionProfileRuntime,
175
+ ) -> None:
176
+ self.projection_profiles = projection_profiles
177
+ self._compiled_by_name: dict[str, CompiledOutputDefinition] = {}
178
+ self._compiled_by_template: dict[str, list[CompiledOutputDefinition]] = {}
179
+ for definition in definitions:
180
+ compiled = self._compile_definition(definition)
181
+ if compiled.name in self._compiled_by_name:
182
+ raise ValueError(f"Duplicate output definition name: '{compiled.name}'")
183
+ self._compiled_by_name[compiled.name] = compiled
184
+ for template_name in compiled.template_names:
185
+ self._compiled_by_template.setdefault(template_name, []).append(compiled)
186
+
187
+ def names(self) -> tuple[str, ...]:
188
+ return tuple(sorted(self._compiled_by_name))
189
+
190
+ def get(self, name: str) -> CompiledOutputDefinition:
191
+ return self._compiled_by_name[name]
192
+
193
+ def for_template(self, template_name: str) -> list[CompiledOutputDefinition]:
194
+ return list(self._compiled_by_template.get(template_name, ()))
195
+
196
+ def project(self, definition_name: str, context: Mapping[str, Any]) -> ProjectedOutputBundle:
197
+ compiled = self.get(definition_name)
198
+ projected_rows: list[ProjectedOutputRow] = []
199
+ row_ids: set[str] = set()
200
+ unresolved_fields: list[dict[str, Any]] = []
201
+ suppressed_rows: list[SuppressedRow] = []
202
+ audit_notes = list(compiled.notes)
203
+
204
+ for row_projection in compiled.row_projections:
205
+ resolved_fields = dict(row_projection.defaults)
206
+ missing_fields: list[str] = []
207
+ row_suppressed = False
208
+
209
+ for slot, binding in row_projection.field_bindings.items():
210
+ found, value = _resolve_binding(binding, context)
211
+ if not found:
212
+ missing_fields.append(slot)
213
+ continue
214
+ resolved_fields[slot] = value
215
+
216
+ for rule in compiled.derivation_rules:
217
+ if rule.target_row != row_projection.row_id:
218
+ continue
219
+
220
+ found, raw_value = _resolve_binding(rule.source_field, context)
221
+ if not found:
222
+ missing_fields.append(rule.target_slot)
223
+ continue
224
+
225
+ code = str(raw_value)
226
+ if code in rule.suppress_codes:
227
+ suppressed_rows.append(
228
+ SuppressedRow(
229
+ row_id=row_projection.row_id,
230
+ reason=(
231
+ f"derivation rule for '{rule.target_slot}' matched a "
232
+ f"suppress code on '{rule.source_field.path}'"
233
+ ),
234
+ source_field=rule.source_field.path,
235
+ source_code=code,
236
+ )
237
+ )
238
+ row_suppressed = True
239
+ break
240
+
241
+ if code in rule.code_map:
242
+ resolved_fields[rule.target_slot] = rule.code_map[code]
243
+ elif rule.default is not NO_DEFAULT:
244
+ resolved_fields[rule.target_slot] = rule.default
245
+ else:
246
+ missing_fields.append(rule.target_slot)
247
+
248
+ policy = row_projection.special_value_policy
249
+ if policy is not None:
250
+ found, raw_value = _resolve_binding(policy.source_field, context)
251
+ if found:
252
+ code = str(raw_value)
253
+ if code in policy.allowed_special_values:
254
+ if policy.suppression_mode == "drop":
255
+ suppressed_rows.append(
256
+ SuppressedRow(
257
+ row_id=row_projection.row_id,
258
+ reason=(
259
+ "special_value_policy matched "
260
+ f"'{policy.source_field.path}' == '{code}'"
261
+ ),
262
+ source_field=policy.source_field.path,
263
+ source_code=code,
264
+ )
265
+ )
266
+ row_suppressed = True
267
+ elif policy.suppression_mode == "fail":
268
+ raise ValueError(
269
+ f"row '{row_projection.row_id}' hit special value '{code}' "
270
+ f"on '{policy.source_field.path}', configured to fail"
271
+ )
272
+ else:
273
+ raise NotImplementedError(
274
+ f"suppression_mode '{policy.suppression_mode}' has no "
275
+ "runtime behavior yet"
276
+ )
277
+
278
+ if row_suppressed:
279
+ continue
280
+
281
+ if missing_fields:
282
+ unresolved_fields.append(
283
+ {
284
+ "row_id": row_projection.row_id,
285
+ "missing_fields": missing_fields,
286
+ "required": row_projection.required,
287
+ }
288
+ )
289
+ if row_projection.required:
290
+ continue
291
+
292
+ if row_projection.profile.concept_slot not in resolved_fields:
293
+ unresolved_fields.append(
294
+ {
295
+ "row_id": row_projection.row_id,
296
+ "missing_fields": [row_projection.profile.concept_slot],
297
+ "required": row_projection.required,
298
+ }
299
+ )
300
+ if row_projection.required:
301
+ continue
302
+
303
+ row = ProjectedOutputRow(
304
+ row_id=row_projection.row_id,
305
+ profile=row_projection.profile,
306
+ fields=resolved_fields,
307
+ )
308
+ projected_rows.append(row)
309
+ row_ids.add(row.row_id)
310
+
311
+ links: list[ProjectedOutputLink] = []
312
+ for link_rule in compiled.link_rules:
313
+ if link_rule.source_row in row_ids and link_rule.target_row in row_ids:
314
+ links.append(
315
+ ProjectedOutputLink(
316
+ source_row=link_rule.source_row,
317
+ target_row=link_rule.target_row,
318
+ relationship_type=link_rule.relationship_type,
319
+ source_field=link_rule.source_field,
320
+ target_field=link_rule.target_field,
321
+ )
322
+ )
323
+ else:
324
+ unresolved_fields.append(
325
+ {
326
+ "link": {
327
+ "source_row": link_rule.source_row,
328
+ "target_row": link_rule.target_row,
329
+ "relationship_type": link_rule.relationship_type,
330
+ },
331
+ "missing_rows": [
332
+ row_id
333
+ for row_id in (link_rule.source_row, link_rule.target_row)
334
+ if row_id not in row_ids
335
+ ],
336
+ }
337
+ )
338
+
339
+ return ProjectedOutputBundle(
340
+ definition_name=compiled.name,
341
+ role=compiled.role,
342
+ rows=projected_rows,
343
+ links=links,
344
+ unresolved_fields=unresolved_fields,
345
+ suppressed_rows=suppressed_rows,
346
+ audit_notes=audit_notes,
347
+ )
348
+
349
+ def project_for_template(
350
+ self,
351
+ template_name: str,
352
+ context: Mapping[str, Any],
353
+ *,
354
+ definition_name: str | None = None,
355
+ ) -> ProjectedOutputBundle:
356
+ matches = self.for_template(template_name)
357
+ if definition_name is not None:
358
+ matches = [item for item in matches if item.name == definition_name]
359
+ if not matches:
360
+ raise KeyError(f"No output definition found for template '{template_name}'")
361
+ if len(matches) > 1:
362
+ names = ", ".join(sorted(item.name for item in matches))
363
+ raise ValueError(
364
+ f"Multiple output definitions match template '{template_name}': {names}"
365
+ )
366
+ return self.project(matches[0].name, context)
367
+
368
+ def _compile_definition(self, definition: OutputDefinition) -> CompiledOutputDefinition:
369
+ row_ids: set[str] = set()
370
+ compiled_rows: list[CompiledRowProjection] = []
371
+ profile_by_row: dict[str, RuntimeProjectionProfile] = {}
372
+ errors: list[str] = []
373
+
374
+ for row_projection in definition.row_projections:
375
+ if row_projection.row_id in row_ids:
376
+ errors.append(f"duplicate row_id '{row_projection.row_id}'")
377
+ continue
378
+ row_ids.add(row_projection.row_id)
379
+
380
+ profile = self.projection_profiles.get(row_projection.profile_name)
381
+ declared_slots = set(row_projection.defaults) | set(row_projection.field_bindings)
382
+ invalid_slots = sorted(slot for slot in declared_slots if not profile.allows_slot(slot))
383
+ if invalid_slots:
384
+ errors.append(
385
+ f"row '{row_projection.row_id}' uses slots not present in profile "
386
+ f"'{profile.name}': {invalid_slots}"
387
+ )
388
+ continue
389
+
390
+ policy = row_projection.special_value_policy
391
+ if policy is not None and policy.suppression_mode not in SUPPRESSION_MODES:
392
+ errors.append(
393
+ f"row '{row_projection.row_id}' has special_value_policy with "
394
+ f"unrecognized suppression_mode '{policy.suppression_mode}'; "
395
+ f"expected one of {sorted(SUPPRESSION_MODES)}"
396
+ )
397
+ continue
398
+
399
+ profile_by_row[row_projection.row_id] = profile
400
+ compiled_rows.append(
401
+ CompiledRowProjection(
402
+ row_id=row_projection.row_id,
403
+ profile=profile,
404
+ field_bindings=dict(row_projection.field_bindings),
405
+ defaults=dict(row_projection.defaults),
406
+ required=row_projection.required,
407
+ special_value_policy=policy,
408
+ )
409
+ )
410
+
411
+ for link_rule in definition.link_rules:
412
+ if link_rule.source_row not in row_ids:
413
+ errors.append(
414
+ f"link rule references unknown source_row '{link_rule.source_row}'"
415
+ )
416
+ if link_rule.target_row not in row_ids:
417
+ errors.append(
418
+ f"link rule references unknown target_row '{link_rule.target_row}'"
419
+ )
420
+
421
+ for derivation_rule in definition.derivation_rules:
422
+ if derivation_rule.target_row not in row_ids:
423
+ errors.append(
424
+ "derivation rule references unknown target_row "
425
+ f"'{derivation_rule.target_row}'"
426
+ )
427
+ continue
428
+ profile = profile_by_row.get(derivation_rule.target_row)
429
+ if profile is not None and not profile.allows_slot(derivation_rule.target_slot):
430
+ errors.append(
431
+ f"derivation rule for row '{derivation_rule.target_row}' targets slot "
432
+ f"'{derivation_rule.target_slot}' not present in profile '{profile.name}'"
433
+ )
434
+
435
+ if errors:
436
+ raise ValueError(
437
+ f"Output definition '{definition.name}' failed validation:\n"
438
+ + "\n".join(f" - {item}" for item in errors)
439
+ )
440
+
441
+ return CompiledOutputDefinition(
442
+ name=definition.name,
443
+ role=definition.role,
444
+ template_names=tuple(definition.template_names),
445
+ row_projections=tuple(compiled_rows),
446
+ link_rules=tuple(definition.link_rules),
447
+ derivation_rules=tuple(definition.derivation_rules),
448
+ notes=tuple(definition.notes),
449
+ )
450
+
451
+
452
+ def _resolve_binding(binding: BindingValue, context: Mapping[str, Any]) -> tuple[bool, Any]:
453
+ if isinstance(binding, ContextFieldRef):
454
+ return _resolve_context_path(context, binding.path)
455
+ return True, binding
456
+
457
+
458
+ def _resolve_context_path(context: Mapping[str, Any], path: str) -> tuple[bool, Any]:
459
+ current: Any = context
460
+ for part in path.split("."):
461
+ if isinstance(current, Mapping):
462
+ if part not in current:
463
+ return False, None
464
+ current = current[part]
465
+ continue
466
+ if not hasattr(current, part):
467
+ return False, None
468
+ current = getattr(current, part)
469
+ return True, current
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Mapping
5
+
6
+ from omop_semantics.schema.generated_models.omop_semantic_registry import OmopCdmProfile
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class RuntimeProjectionProfile:
11
+ """
12
+ Attribute-based runtime view over an `OmopCdmProfile`.
13
+
14
+ This is the structural counterpart to `RuntimeTemplate`: it exposes
15
+ the CDM row-shape information in a stable, ergonomic form for
16
+ downstream execution layers that need to reason about units,
17
+ operators, inline modifiers, or other supported slots.
18
+ """
19
+
20
+ name: str
21
+ cdm_table: str
22
+ concept_slot: str
23
+ value_slot: str | None = None
24
+ unit_slot: str | None = None
25
+ operator_slot: str | None = None
26
+ modifier_slot: str | None = None
27
+ extra_concept_slots: tuple[str, ...] = ()
28
+ numeric_slots: tuple[str, ...] = ()
29
+ string_slots: tuple[str, ...] = ()
30
+ reference_slots: tuple[str, ...] = ()
31
+
32
+ @classmethod
33
+ def from_profile(cls, profile: OmopCdmProfile) -> "RuntimeProjectionProfile":
34
+ return cls(
35
+ name=profile.name,
36
+ cdm_table=profile.cdm_table,
37
+ concept_slot=profile.concept_slot,
38
+ value_slot=profile.value_slot,
39
+ unit_slot=profile.unit_slot,
40
+ operator_slot=profile.operator_slot,
41
+ modifier_slot=profile.modifier_slot,
42
+ extra_concept_slots=tuple(profile.extra_concept_slots or ()),
43
+ numeric_slots=tuple(profile.numeric_slots or ()),
44
+ string_slots=tuple(profile.string_slots or ()),
45
+ reference_slots=tuple(profile.reference_slots or ()),
46
+ )
47
+
48
+ def concept_slots(self) -> tuple[str, ...]:
49
+ slots: list[str] = [self.concept_slot]
50
+ if self.modifier_slot is not None:
51
+ slots.append(self.modifier_slot)
52
+ slots.extend(self.extra_concept_slots)
53
+ if self.unit_slot is not None:
54
+ slots.append(self.unit_slot)
55
+ if self.operator_slot is not None:
56
+ slots.append(self.operator_slot)
57
+ return tuple(slots)
58
+
59
+ def all_slots(self) -> tuple[str, ...]:
60
+ slots: list[str] = [self.concept_slot]
61
+ if self.value_slot is not None:
62
+ slots.append(self.value_slot)
63
+ if self.unit_slot is not None:
64
+ slots.append(self.unit_slot)
65
+ if self.operator_slot is not None:
66
+ slots.append(self.operator_slot)
67
+ if self.modifier_slot is not None:
68
+ slots.append(self.modifier_slot)
69
+ slots.extend(self.extra_concept_slots)
70
+ slots.extend(self.numeric_slots)
71
+ slots.extend(self.string_slots)
72
+ slots.extend(self.reference_slots)
73
+ return tuple(slots)
74
+
75
+ def allows_slot(self, slot: str) -> bool:
76
+ return slot in self.all_slots()
77
+
78
+
79
+ class ProjectionProfileRuntime:
80
+ """
81
+ Indexed runtime access to compiled CDM projection profiles.
82
+
83
+ This keeps the structural profile catalogue available at runtime
84
+ even when execution is not yet using higher-level output definitions.
85
+ """
86
+
87
+ def __init__(self, profiles: Mapping[str, OmopCdmProfile]):
88
+ self._profiles = dict(profiles)
89
+
90
+ def names(self) -> tuple[str, ...]:
91
+ return tuple(sorted(self._profiles))
92
+
93
+ def get(self, name: str) -> RuntimeProjectionProfile:
94
+ return RuntimeProjectionProfile.from_profile(self._profiles[name])
95
+
96
+ def by_table(self, cdm_table: str) -> list[RuntimeProjectionProfile]:
97
+ return [
98
+ RuntimeProjectionProfile.from_profile(profile)
99
+ for profile in self._profiles.values()
100
+ if profile.cdm_table == cdm_table
101
+ ]
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class ProjectedOutputRow:
106
+ """
107
+ One projected row produced by a deterministic semantic execution step.
108
+ """
109
+
110
+ row_id: str
111
+ profile: RuntimeProjectionProfile
112
+ fields: dict[str, Any]
113
+
114
+ def __post_init__(self) -> None:
115
+ unknown_slots = sorted(set(self.fields) - set(self.profile.all_slots()))
116
+ if unknown_slots:
117
+ raise ValueError(
118
+ f"Projected row '{self.row_id}' contains fields not allowed by "
119
+ f"profile '{self.profile.name}': {unknown_slots}"
120
+ )
121
+ if self.profile.concept_slot not in self.fields:
122
+ raise ValueError(
123
+ f"Projected row '{self.row_id}' is missing required concept slot "
124
+ f"'{self.profile.concept_slot}' for profile '{self.profile.name}'"
125
+ )
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class SuppressedRow:
130
+ """
131
+ A row a definition would have produced, dropped deterministically by a
132
+ `DerivationRule` or row-level suppression policy rather than fabricated
133
+ with a missing or null slot value.
134
+ """
135
+
136
+ row_id: str
137
+ reason: str
138
+ source_field: str
139
+ source_code: str
140
+
141
+
142
+ @dataclass(frozen=True)
143
+ class ProjectedOutputLink:
144
+ """
145
+ Relationship between projected rows.
146
+ """
147
+
148
+ source_row: str
149
+ target_row: str
150
+ relationship_type: str
151
+ source_field: str | None = None
152
+ target_field: str | None = None
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class ProjectedOutputBundle:
157
+ """
158
+ Transport-friendly deterministic projection result.
159
+ """
160
+
161
+ definition_name: str
162
+ role: str
163
+ rows: list[ProjectedOutputRow]
164
+ links: list[ProjectedOutputLink] = field(default_factory=list)
165
+ constraint_checks: list[dict[str, Any]] = field(default_factory=list)
166
+ unresolved_fields: list[dict[str, Any]] = field(default_factory=list)
167
+ suppressed_rows: list[SuppressedRow] = field(default_factory=list)
168
+ audit_notes: list[str] = field(default_factory=list)
169
+
170
+ def row(self, row_id: str) -> ProjectedOutputRow:
171
+ for row in self.rows:
172
+ if row.row_id == row_id:
173
+ return row
174
+ raise KeyError(f"No projected row with id '{row_id}'")
175
+
176
+ def to_dict(self) -> dict[str, Any]:
177
+ return {
178
+ "definition_name": self.definition_name,
179
+ "role": self.role,
180
+ "rows": [
181
+ {
182
+ "row_id": row.row_id,
183
+ "profile": row.profile.name,
184
+ "cdm_table": row.profile.cdm_table,
185
+ "fields": dict(row.fields),
186
+ }
187
+ for row in self.rows
188
+ ],
189
+ "links": [
190
+ {
191
+ "source_row": link.source_row,
192
+ "target_row": link.target_row,
193
+ "relationship_type": link.relationship_type,
194
+ "source_field": link.source_field,
195
+ "target_field": link.target_field,
196
+ }
197
+ for link in self.links
198
+ ],
199
+ "constraint_checks": [dict(item) for item in self.constraint_checks],
200
+ "unresolved_fields": [dict(item) for item in self.unresolved_fields],
201
+ "suppressed_rows": [
202
+ {
203
+ "row_id": row.row_id,
204
+ "reason": row.reason,
205
+ "source_field": row.source_field,
206
+ "source_code": row.source_code,
207
+ }
208
+ for row in self.suppressed_rows
209
+ ],
210
+ "audit_notes": list(self.audit_notes),
211
+ }
@@ -22,6 +22,8 @@ from .instance_loader import (
22
22
  needs_profile_interpolation,
23
23
  )
24
24
  from omop_semantics.utils.paths import INSTANCE_DIR
25
+ from .projection import ProjectionProfileRuntime, RuntimeProjectionProfile
26
+ from .output_definitions import OutputDefinition, OutputDefinitionRuntime
25
27
 
26
28
 
27
29
  class CompiledTemplate(TypedDict):
@@ -203,6 +205,13 @@ class RuntimeTemplate:
203
205
  value_concept_ids=c["value_concept_ids"],
204
206
  )
205
207
 
208
+ @property
209
+ def projection_profile(self) -> RuntimeProjectionProfile:
210
+ """
211
+ Structural runtime view of the underlying CDM profile.
212
+ """
213
+ return RuntimeProjectionProfile.from_profile(self.cdm_profile)
214
+
206
215
 
207
216
  @dataclass(frozen=True)
208
217
  class RegistryRuntimeDiff:
@@ -999,6 +1008,7 @@ class OmopSemanticEngine:
999
1008
  self,
1000
1009
  registry_fragment: RegistryFragment,
1001
1010
  profile_objects: dict[str, dict] | None = None,
1011
+ cdm_profiles: dict[str, OmopCdmProfile] | None = None,
1002
1012
  ):
1003
1013
  """
1004
1014
  Construct a semantic engine from an already-loaded registry fragment.
@@ -1022,6 +1032,11 @@ class OmopSemanticEngine:
1022
1032
  if profile_objects is not None
1023
1033
  else None
1024
1034
  )
1035
+ self.projection_profiles = (
1036
+ ProjectionProfileRuntime(cdm_profiles)
1037
+ if cdm_profiles is not None
1038
+ else None
1039
+ )
1025
1040
 
1026
1041
  @classmethod
1027
1042
  def from_instances(cls, fragment: RegistryFragment) -> "OmopSemanticEngine":
@@ -1098,6 +1113,7 @@ class OmopSemanticEngine:
1098
1113
  return cls(
1099
1114
  registry_fragment=merged_fragment,
1100
1115
  profile_objects=profile_objects or None,
1116
+ cdm_profiles=cdm_profiles,
1101
1117
  )
1102
1118
 
1103
1119
  def docs_html(self) -> Html:
@@ -1122,3 +1138,18 @@ class OmopSemanticEngine:
1122
1138
  ])
1123
1139
 
1124
1140
  return Html("".join(parts))
1141
+
1142
+ def build_output_definition_runtime(
1143
+ self,
1144
+ definitions: Iterable[OutputDefinition],
1145
+ ) -> OutputDefinitionRuntime:
1146
+ """
1147
+ Compile programmatic output definitions against the loaded profile catalogue.
1148
+
1149
+ This is the additive bridge from structural CDM profiles to deterministic
1150
+ output bundles. The first implementation slice is intentionally runtime-only
1151
+ and does not require a dedicated YAML schema for output definitions.
1152
+ """
1153
+ if self.projection_profiles is None:
1154
+ raise RuntimeError("Projection profiles are not available on this semantic engine")
1155
+ return OutputDefinitionRuntime(definitions, self.projection_profiles)
@@ -0,0 +1,81 @@
1
+ id: https://example.org/omop_semantics/cdm_profiles
2
+ name: cdm_profiles
3
+
4
+ imports:
5
+ - omop_base
6
+
7
+ classes:
8
+ CDMProfiles:
9
+ slots:
10
+ - profiles
11
+ tree_root: true
12
+ OmopCdmProfile:
13
+ slots:
14
+ - name
15
+ - cdm_table
16
+ - concept_slot
17
+ - value_slot
18
+ - unit_slot
19
+ - operator_slot
20
+ - modifier_slot
21
+ - extra_concept_slots
22
+ - numeric_slots
23
+ - string_slots
24
+ - reference_slots
25
+ slots:
26
+ profiles:
27
+ range: OmopCdmProfile
28
+ multivalued: true
29
+ name:
30
+ range: string
31
+ required: true
32
+ cdm_table:
33
+ range: CdmTable
34
+ required: true
35
+ concept_slot:
36
+ range: string
37
+ required: true
38
+ value_slot:
39
+ range: string
40
+ required: false
41
+ unit_slot:
42
+ range: string
43
+ required: false
44
+ operator_slot:
45
+ range: string
46
+ required: false
47
+ modifier_slot:
48
+ range: string
49
+ required: false
50
+ extra_concept_slots:
51
+ range: string
52
+ multivalued: true
53
+ required: false
54
+ numeric_slots:
55
+ range: string
56
+ multivalued: true
57
+ required: false
58
+ string_slots:
59
+ range: string
60
+ multivalued: true
61
+ required: false
62
+ reference_slots:
63
+ range: string
64
+ multivalued: true
65
+ required: false
66
+ enums:
67
+ CdmTable:
68
+ description: OMOP CDM table to which a semantic template applies
69
+ permissible_values:
70
+ observation:
71
+ measurement:
72
+ drug_exposure:
73
+ procedure_occurrence:
74
+ condition_occurrence:
75
+ device_exposure:
76
+ visit_occurrence:
77
+ death:
78
+ specimen:
79
+ fact_relationship:
80
+ episode:
81
+ episode_event:
@@ -92,6 +92,12 @@ class CdmTable(str, Enum):
92
92
  procedure_occurrence = "procedure_occurrence"
93
93
  condition_occurrence = "condition_occurrence"
94
94
  device_exposure = "device_exposure"
95
+ visit_occurrence = "visit_occurrence"
96
+ death = "death"
97
+ specimen = "specimen"
98
+ fact_relationship = "fact_relationship"
99
+ episode = "episode"
100
+ episode_event = "episode_event"
95
101
 
96
102
 
97
103
 
@@ -212,6 +218,13 @@ class OmopCdmProfile(ConfiguredBaseModel):
212
218
  cdm_table: CdmTable = Field(default=..., json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
213
219
  concept_slot: str = Field(default=..., json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
214
220
  value_slot: Optional[str] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
221
+ unit_slot: Optional[str] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
222
+ operator_slot: Optional[str] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
223
+ modifier_slot: Optional[str] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
224
+ extra_concept_slots: Optional[list[str]] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
225
+ numeric_slots: Optional[list[str]] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
226
+ string_slots: Optional[list[str]] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
227
+ reference_slots: Optional[list[str]] = Field(default=None, json_schema_extra = { "linkml_meta": {'domain_of': ['OmopCdmProfile']} })
215
228
 
216
229
 
217
230
  class OmopTemplate(ConfiguredBaseModel):
@@ -15,6 +15,9 @@ ObservationProfiles:
15
15
  - observation_simple
16
16
  - observation_coded
17
17
  - observation_string
18
+ - observation_numeric
19
+ - observation_numeric_with_unit
20
+ - observation_with_qualifier
18
21
  notes: Valid CDM projection profiles for the OMOP OBSERVATION table
19
22
 
20
23
 
@@ -24,7 +27,10 @@ MeasurementProfiles:
24
27
  role: cdm_profile_set
25
28
  members:
26
29
  - measurement_numeric
30
+ - measurement_numeric_with_unit
31
+ - measurement_numeric_with_operator
27
32
  - measurement_coded
33
+ - measurement_simple
28
34
  notes: Valid CDM projection profiles for the OMOP MEASUREMENT table
29
35
 
30
36
 
@@ -34,6 +40,7 @@ ProcedureProfiles:
34
40
  role: cdm_profile_set
35
41
  members:
36
42
  - procedure_simple
43
+ - procedure_with_inline_modifier
37
44
 
38
45
 
39
46
  ConditionProfiles:
@@ -42,6 +49,7 @@ ConditionProfiles:
42
49
  role: cdm_profile_set
43
50
  members:
44
51
  - condition_simple
52
+ - condition_with_status
45
53
 
46
54
 
47
55
  DrugExposureProfiles:
@@ -50,4 +58,39 @@ DrugExposureProfiles:
50
58
  role: cdm_profile_set
51
59
  members:
52
60
  - drug_exposure_simple
53
- - drug_exposure_dose
61
+ - drug_exposure_dose
62
+ - drug_exposure_with_quantity_and_days_supply
63
+ - drug_exposure_with_route
64
+
65
+
66
+ DeviceProfiles:
67
+ class_uri: RegistryGroup
68
+ name: DeviceProfiles
69
+ role: cdm_profile_set
70
+ members:
71
+ - device_simple
72
+
73
+
74
+ VisitProfiles:
75
+ class_uri: RegistryGroup
76
+ name: VisitProfiles
77
+ role: cdm_profile_set
78
+ members:
79
+ - visit_simple
80
+
81
+
82
+ DeathProfiles:
83
+ class_uri: RegistryGroup
84
+ name: DeathProfiles
85
+ role: cdm_profile_set
86
+ members:
87
+ - death_simple
88
+
89
+
90
+ SpecimenProfiles:
91
+ class_uri: RegistryGroup
92
+ name: SpecimenProfiles
93
+ role: cdm_profile_set
94
+ members:
95
+ - specimen_simple
96
+ - specimen_with_site_status_quantity
@@ -0,0 +1,102 @@
1
+ profiles:
2
+ - name: observation_simple
3
+ cdm_table: observation
4
+ concept_slot: observation_concept_id
5
+ - name: observation_coded
6
+ cdm_table: observation
7
+ concept_slot: observation_concept_id
8
+ value_slot: value_as_concept_id
9
+ - name: observation_string
10
+ cdm_table: observation
11
+ concept_slot: observation_concept_id
12
+ value_slot: value_as_string
13
+ - name: observation_numeric
14
+ cdm_table: observation
15
+ concept_slot: observation_concept_id
16
+ value_slot: value_as_number
17
+ - name: observation_numeric_with_unit
18
+ cdm_table: observation
19
+ concept_slot: observation_concept_id
20
+ value_slot: value_as_number
21
+ unit_slot: unit_concept_id
22
+ - name: observation_with_qualifier
23
+ cdm_table: observation
24
+ concept_slot: observation_concept_id
25
+ extra_concept_slots:
26
+ - qualifier_concept_id
27
+ - name: measurement_numeric
28
+ cdm_table: measurement
29
+ concept_slot: measurement_concept_id
30
+ value_slot: value_as_number
31
+ - name: measurement_numeric_with_unit
32
+ cdm_table: measurement
33
+ concept_slot: measurement_concept_id
34
+ value_slot: value_as_number
35
+ unit_slot: unit_concept_id
36
+ - name: measurement_numeric_with_operator
37
+ cdm_table: measurement
38
+ concept_slot: measurement_concept_id
39
+ value_slot: value_as_number
40
+ unit_slot: unit_concept_id
41
+ operator_slot: operator_concept_id
42
+ - name: measurement_coded
43
+ cdm_table: measurement
44
+ concept_slot: measurement_concept_id
45
+ value_slot: value_as_concept_id
46
+ - name: measurement_simple
47
+ cdm_table: measurement
48
+ concept_slot: measurement_concept_id
49
+ - name: procedure_simple
50
+ cdm_table: procedure_occurrence
51
+ concept_slot: procedure_concept_id
52
+ - name: procedure_with_inline_modifier
53
+ cdm_table: procedure_occurrence
54
+ concept_slot: procedure_concept_id
55
+ modifier_slot: modifier_concept_id
56
+ - name: condition_simple
57
+ cdm_table: condition_occurrence
58
+ concept_slot: condition_concept_id
59
+ - name: condition_with_status
60
+ cdm_table: condition_occurrence
61
+ concept_slot: condition_concept_id
62
+ extra_concept_slots:
63
+ - condition_status_concept_id
64
+ - name: drug_exposure_simple
65
+ cdm_table: drug_exposure
66
+ concept_slot: drug_concept_id
67
+ - name: drug_exposure_dose
68
+ cdm_table: drug_exposure
69
+ concept_slot: drug_concept_id
70
+ value_slot: dose_value
71
+ - name: drug_exposure_with_quantity_and_days_supply
72
+ cdm_table: drug_exposure
73
+ concept_slot: drug_concept_id
74
+ numeric_slots:
75
+ - quantity
76
+ - days_supply
77
+ - name: drug_exposure_with_route
78
+ cdm_table: drug_exposure
79
+ concept_slot: drug_concept_id
80
+ extra_concept_slots:
81
+ - route_concept_id
82
+ - name: device_simple
83
+ cdm_table: device_exposure
84
+ concept_slot: device_concept_id
85
+ - name: visit_simple
86
+ cdm_table: visit_occurrence
87
+ concept_slot: visit_concept_id
88
+ - name: death_simple
89
+ cdm_table: death
90
+ concept_slot: death_type_concept_id
91
+ - name: specimen_simple
92
+ cdm_table: specimen
93
+ concept_slot: specimen_concept_id
94
+ - name: specimen_with_site_status_quantity
95
+ cdm_table: specimen
96
+ concept_slot: specimen_concept_id
97
+ extra_concept_slots:
98
+ - anatomic_site_concept_id
99
+ - disease_status_concept_id
100
+ numeric_slots:
101
+ - quantity
102
+ unit_slot: unit_concept_id
@@ -1,43 +0,0 @@
1
- id: https://example.org/omop_semantics/cdm_profiles
2
- name: cdm_profiles
3
-
4
- imports:
5
- - omop_base
6
-
7
- classes:
8
- CDMProfiles:
9
- slots:
10
- - profiles
11
- tree_root: true
12
- OmopCdmProfile:
13
- slots:
14
- - name
15
- - cdm_table
16
- - concept_slot
17
- - value_slot
18
- slots:
19
- profiles:
20
- range: OmopCdmProfile
21
- multivalued: true
22
- name:
23
- range: string
24
- required: true
25
- cdm_table:
26
- range: CdmTable
27
- required: true
28
- concept_slot:
29
- range: string
30
- required: true
31
- value_slot:
32
- range: string
33
- required: false
34
- enums:
35
- CdmTable:
36
- description: OMOP CDM table to which a semantic template applies
37
- permissible_values:
38
- observation:
39
- measurement:
40
- drug_exposure:
41
- procedure_occurrence:
42
- condition_occurrence:
43
- device_exposure:
@@ -1,39 +0,0 @@
1
- profiles:
2
- - name: observation_simple
3
- cdm_table: observation
4
- concept_slot: observation_concept_id
5
- - name: observation_coded
6
- cdm_table: observation
7
- concept_slot: observation_concept_id
8
- value_slot: value_as_concept_id
9
- - name: observation_string
10
- cdm_table: observation
11
- concept_slot: observation_concept_id
12
- value_slot: value_as_string
13
- - name: measurement_numeric
14
- cdm_table: measurement
15
- concept_slot: measurement_concept_id
16
- value_slot: value_as_number
17
- - name: measurement_coded
18
- cdm_table: measurement
19
- concept_slot: measurement_concept_id
20
- value_slot: value_as_concept_id
21
- - name: measurement_simple
22
- cdm_table: measurement
23
- concept_slot: measurement_concept_id
24
- - name: procedure_simple
25
- cdm_table: procedure_occurrence
26
- concept_slot: procedure_concept_id
27
- - name: condition_simple
28
- cdm_table: condition_occurrence
29
- concept_slot: condition_concept_id
30
- - name: drug_exposure_simple
31
- cdm_table: drug_exposure
32
- concept_slot: drug_concept_id
33
- - name: drug_exposure_dose
34
- cdm_table: drug_exposure
35
- concept_slot: drug_concept_id
36
- value_slot: dose_value
37
- - name: device_simple
38
- cdm_table: device_exposure
39
- concept_slot: device_concept_id
File without changes