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.
- cql_sdk/__init__.py +10 -0
- cql_sdk/abstractions/__init__.py +21 -0
- cql_sdk/abstractions/data_source.py +27 -0
- cql_sdk/abstractions/invocation.py +20 -0
- cql_sdk/abstractions/operators.py +31 -0
- cql_sdk/abstractions/packaging.py +14 -0
- cql_sdk/abstractions/terminology.py +31 -0
- cql_sdk/abstractions/type_conversion.py +16 -0
- cql_sdk/api.py +80 -0
- cql_sdk/cli/__init__.py +5 -0
- cql_sdk/cli/commands/__init__.py +1 -0
- cql_sdk/cli/commands/inspect.py +32 -0
- cql_sdk/cli/commands/package.py +20 -0
- cql_sdk/cli/commands/run.py +47 -0
- cql_sdk/cli/commands/validate.py +28 -0
- cql_sdk/cli/main.py +38 -0
- cql_sdk/compiler/__init__.py +7 -0
- cql_sdk/compiler/bindings.py +14 -0
- cql_sdk/compiler/expression_builder.py +23 -0
- cql_sdk/compiler/planner.py +37 -0
- cql_sdk/compiler/type_manager.py +18 -0
- cql_sdk/elm/__init__.py +5 -0
- cql_sdk/elm/models/__init__.py +1 -0
- cql_sdk/elm/models/base.py +35 -0
- cql_sdk/elm/models/expressions.py +17 -0
- cql_sdk/elm/models/library.py +52 -0
- cql_sdk/elm/models/types.py +17 -0
- cql_sdk/elm/serialization/__init__.py +8 -0
- cql_sdk/elm/serialization/compatibility.py +45 -0
- cql_sdk/elm/serialization/json_codec.py +30 -0
- cql_sdk/elm/serialization/loader.py +89 -0
- cql_sdk/fhir/__init__.py +6 -0
- cql_sdk/fhir/adapters.py +18 -0
- cql_sdk/fhir/context.py +15 -0
- cql_sdk/fhir/retrieve.py +34 -0
- cql_sdk/fhir/terminology.py +18 -0
- cql_sdk/fhir/type_conversion.py +18 -0
- cql_sdk/invocation/__init__.py +5 -0
- cql_sdk/invocation/cache.py +25 -0
- cql_sdk/invocation/invoker.py +24 -0
- cql_sdk/invocation/library_registry.py +38 -0
- cql_sdk/invocation/toolkit.py +91 -0
- cql_sdk/packaging/__init__.py +6 -0
- cql_sdk/packaging/bundle_writer.py +17 -0
- cql_sdk/packaging/library_package.py +53 -0
- cql_sdk/packaging/manifest.py +24 -0
- cql_sdk/packaging/resource_writer.py +12 -0
- cql_sdk/runtime/__init__.py +1 -0
- cql_sdk/runtime/comparers.py +27 -0
- cql_sdk/runtime/context.py +79 -0
- cql_sdk/runtime/datetime_types.py +15 -0
- cql_sdk/runtime/definitions.py +13 -0
- cql_sdk/runtime/intervals.py +19 -0
- cql_sdk/runtime/operators.py +250 -0
- cql_sdk/runtime/quantities.py +15 -0
- cql_sdk/spark/__init__.py +12 -0
- cql_sdk/spark/adapters.py +12 -0
- cql_sdk/spark/execution.py +36 -0
- cql_sdk/spark/session.py +51 -0
- cql_sdk/spark/udf.py +35 -0
- cql_sdk/version.py +5 -0
- ms_cql_sdk-0.1.0.dist-info/METADATA +185 -0
- ms_cql_sdk-0.1.0.dist-info/RECORD +66 -0
- ms_cql_sdk-0.1.0.dist-info/WHEEL +4 -0
- ms_cql_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|
cql_sdk/cli/__init__.py
ADDED
|
@@ -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)
|
cql_sdk/elm/__init__.py
ADDED
|
@@ -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
|