ms-cql-sdk 0.1.0__py3-none-any.whl

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 (66) hide show
  1. cql_sdk/__init__.py +10 -0
  2. cql_sdk/abstractions/__init__.py +21 -0
  3. cql_sdk/abstractions/data_source.py +27 -0
  4. cql_sdk/abstractions/invocation.py +20 -0
  5. cql_sdk/abstractions/operators.py +31 -0
  6. cql_sdk/abstractions/packaging.py +14 -0
  7. cql_sdk/abstractions/terminology.py +31 -0
  8. cql_sdk/abstractions/type_conversion.py +16 -0
  9. cql_sdk/api.py +80 -0
  10. cql_sdk/cli/__init__.py +5 -0
  11. cql_sdk/cli/commands/__init__.py +1 -0
  12. cql_sdk/cli/commands/inspect.py +32 -0
  13. cql_sdk/cli/commands/package.py +20 -0
  14. cql_sdk/cli/commands/run.py +47 -0
  15. cql_sdk/cli/commands/validate.py +28 -0
  16. cql_sdk/cli/main.py +38 -0
  17. cql_sdk/compiler/__init__.py +7 -0
  18. cql_sdk/compiler/bindings.py +14 -0
  19. cql_sdk/compiler/expression_builder.py +23 -0
  20. cql_sdk/compiler/planner.py +37 -0
  21. cql_sdk/compiler/type_manager.py +18 -0
  22. cql_sdk/elm/__init__.py +5 -0
  23. cql_sdk/elm/models/__init__.py +1 -0
  24. cql_sdk/elm/models/base.py +35 -0
  25. cql_sdk/elm/models/expressions.py +17 -0
  26. cql_sdk/elm/models/library.py +52 -0
  27. cql_sdk/elm/models/types.py +17 -0
  28. cql_sdk/elm/serialization/__init__.py +8 -0
  29. cql_sdk/elm/serialization/compatibility.py +45 -0
  30. cql_sdk/elm/serialization/json_codec.py +30 -0
  31. cql_sdk/elm/serialization/loader.py +89 -0
  32. cql_sdk/fhir/__init__.py +6 -0
  33. cql_sdk/fhir/adapters.py +18 -0
  34. cql_sdk/fhir/context.py +15 -0
  35. cql_sdk/fhir/retrieve.py +34 -0
  36. cql_sdk/fhir/terminology.py +18 -0
  37. cql_sdk/fhir/type_conversion.py +18 -0
  38. cql_sdk/invocation/__init__.py +5 -0
  39. cql_sdk/invocation/cache.py +25 -0
  40. cql_sdk/invocation/invoker.py +24 -0
  41. cql_sdk/invocation/library_registry.py +38 -0
  42. cql_sdk/invocation/toolkit.py +91 -0
  43. cql_sdk/packaging/__init__.py +6 -0
  44. cql_sdk/packaging/bundle_writer.py +17 -0
  45. cql_sdk/packaging/library_package.py +53 -0
  46. cql_sdk/packaging/manifest.py +24 -0
  47. cql_sdk/packaging/resource_writer.py +12 -0
  48. cql_sdk/runtime/__init__.py +1 -0
  49. cql_sdk/runtime/comparers.py +27 -0
  50. cql_sdk/runtime/context.py +79 -0
  51. cql_sdk/runtime/datetime_types.py +15 -0
  52. cql_sdk/runtime/definitions.py +13 -0
  53. cql_sdk/runtime/intervals.py +19 -0
  54. cql_sdk/runtime/operators.py +250 -0
  55. cql_sdk/runtime/quantities.py +15 -0
  56. cql_sdk/spark/__init__.py +12 -0
  57. cql_sdk/spark/adapters.py +12 -0
  58. cql_sdk/spark/execution.py +36 -0
  59. cql_sdk/spark/session.py +51 -0
  60. cql_sdk/spark/udf.py +35 -0
  61. cql_sdk/version.py +5 -0
  62. ms_cql_sdk-0.1.0.dist-info/METADATA +185 -0
  63. ms_cql_sdk-0.1.0.dist-info/RECORD +66 -0
  64. ms_cql_sdk-0.1.0.dist-info/WHEEL +4 -0
  65. ms_cql_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  66. ms_cql_sdk-0.1.0.dist-info/licenses/LICENSE +17 -0
cql_sdk/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """cql-sdk: a modular Python SDK for CQL/ELM execution.
2
+
3
+ Public entry points live in :mod:`cql_sdk.api` and
4
+ :mod:`cql_sdk.invocation.toolkit`. Everything else is considered internal
5
+ and may evolve without notice.
6
+ """
7
+
8
+ from cql_sdk.version import __version__
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,21 @@
1
+ """Abstractions: protocols and ABCs describing stable SDK seams.
2
+
3
+ Concrete implementations live under :mod:`cql_sdk.runtime`,
4
+ :mod:`cql_sdk.fhir`, :mod:`cql_sdk.spark`, and :mod:`cql_sdk.packaging`.
5
+ """
6
+
7
+ from cql_sdk.abstractions.data_source import DataSource
8
+ from cql_sdk.abstractions.invocation import Invoker
9
+ from cql_sdk.abstractions.operators import OperatorRegistry
10
+ from cql_sdk.abstractions.packaging import PackageWriter
11
+ from cql_sdk.abstractions.terminology import TerminologyProvider
12
+ from cql_sdk.abstractions.type_conversion import TypeConverter
13
+
14
+ __all__ = [
15
+ "DataSource",
16
+ "Invoker",
17
+ "OperatorRegistry",
18
+ "PackageWriter",
19
+ "TerminologyProvider",
20
+ "TypeConverter",
21
+ ]
@@ -0,0 +1,27 @@
1
+ """Data source protocol used by retrieve-style ELM nodes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import Any, Protocol, runtime_checkable
7
+
8
+
9
+ @runtime_checkable
10
+ class DataSource(Protocol):
11
+ """Abstract source of clinical/patient data.
12
+
13
+ Implementations may be backed by in-memory FHIR bundles, file-based
14
+ fixtures, REST FHIR servers, or Spark DataFrames.
15
+ """
16
+
17
+ def retrieve(
18
+ self,
19
+ *,
20
+ data_type: str,
21
+ code_property: str | None = None,
22
+ codes: Iterable[Any] | None = None,
23
+ date_property: str | None = None,
24
+ date_range: Any | None = None,
25
+ context: Any | None = None,
26
+ ) -> Iterable[Any]:
27
+ """Retrieve resources/records matching the given criteria."""
@@ -0,0 +1,20 @@
1
+ """Invocation protocol — the top-level user-facing seam."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Protocol, runtime_checkable
6
+
7
+
8
+ @runtime_checkable
9
+ class Invoker(Protocol):
10
+ """Executes a named definition on a registered library."""
11
+
12
+ def invoke(
13
+ self,
14
+ *,
15
+ library_identifier: str,
16
+ definition: str,
17
+ parameters: dict[str, Any] | None = None,
18
+ context: Any | None = None,
19
+ ) -> Any:
20
+ """Evaluate ``definition`` in the given library and return its value."""
@@ -0,0 +1,31 @@
1
+ """Operator registry protocol.
2
+
3
+ An operator registry maps ELM operator/expression node *type names* to
4
+ executable Python callables. Keeping this behind a Protocol lets the runtime,
5
+ FHIR, and Spark layers swap or extend operator behavior without touching
6
+ core planner/invoker code.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Protocol, runtime_checkable
12
+
13
+ OperatorFn = Any # Callable[[RuntimeContext, ElmNode, ...], Any] — kept loose to avoid cycles
14
+
15
+
16
+ @runtime_checkable
17
+ class OperatorRegistry(Protocol):
18
+ """Maps ELM node type names to executable operator callables."""
19
+
20
+ def register(self, elm_type: str, fn: OperatorFn) -> None:
21
+ """Register (or replace) an operator implementation."""
22
+
23
+ def get(self, elm_type: str) -> OperatorFn:
24
+ """Return the operator callable for ``elm_type``.
25
+
26
+ Raises:
27
+ KeyError: if no operator is registered for ``elm_type``.
28
+ """
29
+
30
+ def has(self, elm_type: str) -> bool:
31
+ """Return whether an operator is registered for ``elm_type``."""
@@ -0,0 +1,14 @@
1
+ """Packaging seam for emitting library/manifest/resource artifacts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Protocol, runtime_checkable
7
+
8
+
9
+ @runtime_checkable
10
+ class PackageWriter(Protocol):
11
+ """Writes a logical package (ELM + resources + manifest) to disk."""
12
+
13
+ def write(self, *, output_dir: Path, manifest: Any, resources: list[Any]) -> Path:
14
+ """Persist the package and return the root output path."""
@@ -0,0 +1,31 @@
1
+ """Terminology provider protocol (value sets, code systems, concepts)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Protocol, runtime_checkable
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class Code:
11
+ code: str
12
+ system: str | None = None
13
+ display: str | None = None
14
+ version: str | None = None
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ValueSetRef:
19
+ id: str
20
+ version: str | None = None
21
+
22
+
23
+ @runtime_checkable
24
+ class TerminologyProvider(Protocol):
25
+ """Resolves value set membership and code system lookups."""
26
+
27
+ def expand(self, value_set: ValueSetRef) -> list[Code]:
28
+ """Return the expansion (list of codes) for a value set."""
29
+
30
+ def in_value_set(self, code: Code, value_set: ValueSetRef) -> bool:
31
+ """Return whether ``code`` is a member of ``value_set``."""
@@ -0,0 +1,16 @@
1
+ """Type conversion seam between ELM/CQL types and host Python values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Protocol, runtime_checkable
6
+
7
+
8
+ @runtime_checkable
9
+ class TypeConverter(Protocol):
10
+ """Bidirectional conversion between CQL/ELM values and Python values."""
11
+
12
+ def to_python(self, cql_value: Any, *, cql_type: str | None = None) -> Any:
13
+ """Convert a CQL/ELM value to an idiomatic Python value."""
14
+
15
+ def to_cql(self, py_value: Any, *, cql_type: str | None = None) -> Any:
16
+ """Convert a Python value into the representation expected by ELM."""
cql_sdk/api.py ADDED
@@ -0,0 +1,80 @@
1
+ """High-level convenience facade for the cql-sdk package.
2
+
3
+ This module is the preferred public entry point for most users. It delegates
4
+ to :mod:`cql_sdk.invocation.toolkit` but offers a minimal, explicit surface
5
+ so consumers never have to touch lower-level modules such as
6
+ :mod:`cql_sdk.compiler` or :mod:`cql_sdk.runtime`.
7
+
8
+ Typical workflow::
9
+
10
+ from cql_sdk.api import load_library, invoke
11
+
12
+ lib = load_library("HelloWorld.elm.json")
13
+ result = invoke(lib, definition="Greeting")
14
+
15
+ For repeated invocations or more advanced wiring (custom terminology, FHIR
16
+ retrieval, caching), use :class:`cql_sdk.invocation.toolkit.InvocationToolkit`
17
+ directly.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from cql_sdk.elm.models.library import Library
26
+ from cql_sdk.elm.serialization.loader import load_library_from_path, load_library_from_string
27
+ from cql_sdk.invocation.toolkit import InvocationToolkit
28
+ from cql_sdk.runtime.context import RuntimeContext
29
+
30
+ __all__ = [
31
+ "create_context",
32
+ "invoke",
33
+ "load_library",
34
+ "load_library_from_text",
35
+ ]
36
+
37
+
38
+ def load_library(path: str | Path) -> Library:
39
+ """Load an ELM library from a JSON file on disk."""
40
+ return load_library_from_path(Path(path))
41
+
42
+
43
+ def load_library_from_text(text: str) -> Library:
44
+ """Load an ELM library from a JSON string."""
45
+ return load_library_from_string(text)
46
+
47
+
48
+ def create_context(**overrides: Any) -> RuntimeContext:
49
+ """Build a default :class:`RuntimeContext` with optional overrides."""
50
+ return RuntimeContext.default(**overrides)
51
+
52
+
53
+ def invoke(
54
+ library: Library,
55
+ *,
56
+ definition: str,
57
+ parameters: dict[str, Any] | None = None,
58
+ context: RuntimeContext | None = None,
59
+ ) -> Any:
60
+ """Invoke a named definition on a loaded library.
61
+
62
+ Args:
63
+ library: A previously loaded ELM library.
64
+ definition: Name of the ``def`` (or function) to evaluate.
65
+ parameters: Optional parameter bindings.
66
+ context: Optional pre-built runtime context; a default one is
67
+ created if omitted.
68
+
69
+ Returns:
70
+ The evaluated result. For scalar definitions this is typically a
71
+ Python primitive; for list-valued definitions a list is returned.
72
+ """
73
+ toolkit = InvocationToolkit()
74
+ toolkit.register(library)
75
+ return toolkit.invoke(
76
+ library_identifier=library.identifier,
77
+ definition=definition,
78
+ parameters=parameters,
79
+ context=context,
80
+ )
@@ -0,0 +1,5 @@
1
+ """Typer-based CLI, exposed as the ``cql-sdk`` console script."""
2
+
3
+ from cql_sdk.cli.main import app
4
+
5
+ __all__ = ["app"]
@@ -0,0 +1 @@
1
+ """CLI subcommands."""
@@ -0,0 +1,32 @@
1
+ """``cql-sdk inspect`` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from cql_sdk.elm.serialization.loader import load_library_from_path
12
+
13
+
14
+ def inspect(elm_file: Path = typer.Argument(..., exists=True, readable=True)) -> None:
15
+ library = load_library_from_path(elm_file)
16
+ console = Console()
17
+
18
+ console.print(f"[bold]Library:[/bold] {library.identifier}")
19
+ if library.includes:
20
+ console.print(f"[bold]Includes:[/bold] {', '.join(str(i) for i in library.includes)}")
21
+
22
+ table = Table(title="Definitions", show_lines=False)
23
+ table.add_column("Name")
24
+ table.add_column("Access")
25
+ table.add_column("Context")
26
+ table.add_column("Expression type")
27
+ for name, d in library.definitions.items():
28
+ table.add_row(name, d.access_level, d.context or "-", d.expression.type or "-")
29
+ console.print(table)
30
+
31
+ if library.parameters:
32
+ console.print(f"[bold]Parameters:[/bold] {', '.join(library.parameters)}")
@@ -0,0 +1,20 @@
1
+ """``cql-sdk package`` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from cql_sdk.packaging.library_package import LibraryPackage
11
+
12
+
13
+ def package(
14
+ input_dir: Path = typer.Argument(..., exists=True, file_okay=False, readable=True),
15
+ output: Path = typer.Option(..., "--output", "-o", help="Output directory for the package."),
16
+ name: str | None = typer.Option(None, "--name", help="Override the package name."),
17
+ ) -> None:
18
+ pkg = LibraryPackage.discover(input_dir, name=name)
19
+ target = pkg.write(output)
20
+ Console().print(f"[green]Packaged[/green] {pkg.manifest.name} -> {target}")
@@ -0,0 +1,47 @@
1
+ """``cql-sdk run`` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import typer
9
+ from rich.console import Console
10
+
11
+ from cql_sdk.elm.serialization.loader import load_library_from_path
12
+ from cql_sdk.fhir.context import context_from_bundle
13
+ from cql_sdk.invocation.toolkit import InvocationToolkit
14
+
15
+
16
+ def run(
17
+ elm_file: Path = typer.Argument(..., exists=True, readable=True),
18
+ definition: str = typer.Option(..., "--definition", "-d", help="Definition name to evaluate."),
19
+ bundle: Path | None = typer.Option(
20
+ None, "--bundle", help="Optional FHIR Bundle JSON to bind as the data source."
21
+ ),
22
+ ) -> None:
23
+ library = load_library_from_path(elm_file)
24
+ toolkit = InvocationToolkit()
25
+ toolkit.register(library)
26
+
27
+ context = None
28
+ if bundle is not None:
29
+ bundle_obj = json.loads(bundle.read_text(encoding="utf-8"))
30
+ context = context_from_bundle(bundle_obj)
31
+
32
+ result = toolkit.invoke(
33
+ library_identifier=library.identifier,
34
+ definition=definition,
35
+ context=context,
36
+ )
37
+ Console().print_json(data={"definition": definition, "result": _json_safe(result)})
38
+
39
+
40
+ def _json_safe(value: object) -> object:
41
+ if isinstance(value, (str, int, float, bool)) or value is None:
42
+ return value
43
+ if isinstance(value, list):
44
+ return [_json_safe(v) for v in value]
45
+ if isinstance(value, dict):
46
+ return {k: _json_safe(v) for k, v in value.items()}
47
+ return repr(value)
@@ -0,0 +1,28 @@
1
+ """``cql-sdk validate`` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from cql_sdk.elm.serialization.loader import load_library_from_path
11
+ from cql_sdk.invocation.toolkit import InvocationToolkit
12
+
13
+
14
+ def validate(elm_file: Path = typer.Argument(..., exists=True, readable=True)) -> None:
15
+ library = load_library_from_path(elm_file)
16
+ toolkit = InvocationToolkit()
17
+ toolkit.register(library)
18
+ missing = toolkit.validate(library.identifier)
19
+
20
+ console = Console()
21
+ if not missing:
22
+ console.print(f"[green]OK[/green] - all ELM nodes in {library.identifier} are executable.")
23
+ raise typer.Exit(code=0)
24
+
25
+ console.print(f"[yellow]Warning[/yellow] - {len(missing)} unsupported ELM node type(s):")
26
+ for t in sorted(missing):
27
+ console.print(f" - {t}")
28
+ raise typer.Exit(code=1)
cql_sdk/cli/main.py ADDED
@@ -0,0 +1,38 @@
1
+ """Top-level ``cql-sdk`` Typer application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from cql_sdk.cli.commands import inspect as inspect_cmd
8
+ from cql_sdk.cli.commands import package as package_cmd
9
+ from cql_sdk.cli.commands import run as run_cmd
10
+ from cql_sdk.cli.commands import validate as validate_cmd
11
+ from cql_sdk.version import __version__
12
+
13
+ app = typer.Typer(
14
+ name="cql-sdk",
15
+ help="Command-line tools for the cql-sdk Python package.",
16
+ no_args_is_help=True,
17
+ add_completion=False,
18
+ )
19
+
20
+ app.command("inspect", help="Print a human-readable summary of an ELM file.")(inspect_cmd.inspect)
21
+ app.command("validate", help="Validate operator coverage for an ELM file.")(validate_cmd.validate)
22
+ app.command("run", help="Evaluate a named definition in an ELM file.")(run_cmd.run)
23
+ app.command("package", help="Package an input directory of ELM/resources.")(package_cmd.package)
24
+
25
+
26
+ @app.callback()
27
+ def _root(
28
+ version: bool = typer.Option(
29
+ False, "--version", help="Show the cql-sdk version and exit.", is_eager=True
30
+ ),
31
+ ) -> None:
32
+ if version:
33
+ typer.echo(__version__)
34
+ raise typer.Exit()
35
+
36
+
37
+ if __name__ == "__main__": # pragma: no cover
38
+ app()
@@ -0,0 +1,7 @@
1
+ """Compiler / planning layer.
2
+
3
+ This layer bridges ELM nodes to executable runtime behavior. In the initial
4
+ scaffold the runtime interprets ELM directly; the planner is kept as a
5
+ seam so a future version can pre-bake expression trees into compiled
6
+ callables without changing the public API.
7
+ """
@@ -0,0 +1,14 @@
1
+ """Bindings between ELM nodes and operator callables (future expansion)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class Binding:
11
+ """Pairs an ELM node type name with a compiled callable."""
12
+
13
+ elm_type: str
14
+ callable_: Callable[..., object]
@@ -0,0 +1,23 @@
1
+ """Placeholder for an expression builder that compiles ELM into Python callables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ from cql_sdk.elm.models.base import ElmNode
10
+ from cql_sdk.runtime.context import RuntimeContext
11
+
12
+
13
+ def build(expression: ElmNode) -> Callable[[RuntimeContext], Any]:
14
+ """Return a callable that evaluates ``expression`` against a context.
15
+
16
+ The current implementation simply wraps :meth:`RuntimeContext.evaluate`;
17
+ a future version may inline/specialize expression trees here.
18
+ """
19
+
20
+ def _run(ctx: RuntimeContext) -> Any:
21
+ return ctx.evaluate(expression)
22
+
23
+ return _run
@@ -0,0 +1,37 @@
1
+ """Expression planner: walks ELM and verifies operator coverage up front."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from cql_sdk.elm.models.base import ElmNode
8
+
9
+ if TYPE_CHECKING:
10
+ from cql_sdk.abstractions.operators import OperatorRegistry
11
+ from cql_sdk.elm.models.library import Library
12
+
13
+
14
+ def missing_operators(library: Library, registry: OperatorRegistry) -> set[str]:
15
+ """Return ELM node types referenced by ``library`` but absent from ``registry``.
16
+
17
+ Useful as a fail-fast validation step before attempting execution.
18
+ """
19
+ missing: set[str] = set()
20
+ for definition in library.definitions.values():
21
+ _walk(definition.expression, registry, missing)
22
+ return missing
23
+
24
+
25
+ def _walk(node: ElmNode, registry: OperatorRegistry, missing: set[str]) -> None:
26
+ if node.type and not registry.has(node.type):
27
+ missing.add(node.type)
28
+ for value in node.payload.values():
29
+ _walk_value(value, registry, missing)
30
+
31
+
32
+ def _walk_value(value: object, registry: OperatorRegistry, missing: set[str]) -> None:
33
+ if isinstance(value, dict) and "type" in value:
34
+ _walk(ElmNode.from_json(value), registry, missing)
35
+ elif isinstance(value, list):
36
+ for item in value:
37
+ _walk_value(item, registry, missing)
@@ -0,0 +1,18 @@
1
+ """ELM type-name -> Python type management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ @dataclass(slots=True)
9
+ class TypeManager:
10
+ """Very small registry mapping ELM type names to Python constructors."""
11
+
12
+ mapping: dict[str, type] = field(default_factory=dict)
13
+
14
+ def register(self, elm_type: str, py_type: type) -> None:
15
+ self.mapping[elm_type] = py_type
16
+
17
+ def resolve(self, elm_type: str) -> type | None:
18
+ return self.mapping.get(elm_type)
@@ -0,0 +1,5 @@
1
+ """ELM (Expression Logical Model) data types and serialization."""
2
+
3
+ from cql_sdk.elm.models.library import Library
4
+
5
+ __all__ = ["Library"]
@@ -0,0 +1 @@
1
+ """ELM model classes."""
@@ -0,0 +1,35 @@
1
+ """Common base types for ELM model classes.
2
+
3
+ The scaffold intentionally uses a loose representation: every ELM expression
4
+ is captured as an :class:`ElmNode` holding its discriminator ``type`` plus
5
+ the raw payload. This preserves fidelity with the JSON representation and
6
+ lets the planner grow rich, typed models over time without requiring a full
7
+ schema port up front.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import Any
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class ElmNode:
18
+ """A raw ELM expression node.
19
+
20
+ Attributes:
21
+ type: The ELM discriminator (e.g. ``"Literal"``, ``"FunctionRef"``).
22
+ payload: The full JSON object for this node, as produced by the
23
+ CQL-to-ELM translator.
24
+ """
25
+
26
+ type: str
27
+ payload: dict[str, Any] = field(default_factory=dict)
28
+
29
+ @classmethod
30
+ def from_json(cls, obj: dict[str, Any]) -> ElmNode:
31
+ # ELM JSON uses "type" to encode the node discriminator.
32
+ return cls(type=str(obj.get("type", "")), payload=obj)
33
+
34
+ def get(self, key: str, default: Any = None) -> Any:
35
+ return self.payload.get(key, default)
@@ -0,0 +1,17 @@
1
+ """Typed helpers around common ELM expression shapes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from cql_sdk.elm.models.base import ElmNode
6
+
7
+
8
+ def is_literal(node: ElmNode) -> bool:
9
+ return node.type == "Literal"
10
+
11
+
12
+ def literal_value(node: ElmNode) -> str | None:
13
+ return node.get("value")
14
+
15
+
16
+ def literal_value_type(node: ElmNode) -> str | None:
17
+ return node.get("valueType")
@@ -0,0 +1,52 @@
1
+ """ELM :class:`Library` model — the top-level artifact produced by CQL-to-ELM."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from cql_sdk.elm.models.base import ElmNode
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class LibraryIdentifier:
13
+ id: str
14
+ version: str | None = None
15
+
16
+ def __str__(self) -> str:
17
+ return f"{self.id}" + (f"|{self.version}" if self.version else "")
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class LibraryDefinition:
22
+ """A single named `def` or `function` inside a library."""
23
+
24
+ name: str
25
+ expression: ElmNode
26
+ access_level: str = "Public"
27
+ context: str | None = None
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class Library:
32
+ """An ELM library: the loaded, parsed form of a ``.elm.json`` file."""
33
+
34
+ identifier: LibraryIdentifier
35
+ definitions: dict[str, LibraryDefinition] = field(default_factory=dict)
36
+ parameters: dict[str, ElmNode] = field(default_factory=dict)
37
+ includes: list[LibraryIdentifier] = field(default_factory=list)
38
+ value_sets: dict[str, dict[str, Any]] = field(default_factory=dict)
39
+ code_systems: dict[str, dict[str, Any]] = field(default_factory=dict)
40
+ raw: dict[str, Any] = field(default_factory=dict)
41
+ """The original parsed JSON, preserved for pass-through scenarios."""
42
+
43
+ def has_definition(self, name: str) -> bool:
44
+ return name in self.definitions
45
+
46
+ def get_definition(self, name: str) -> LibraryDefinition:
47
+ try:
48
+ return self.definitions[name]
49
+ except KeyError as exc:
50
+ raise KeyError(
51
+ f"Definition '{name}' not found in library '{self.identifier}'."
52
+ ) from exc