meds-algorithm 0.4.0a4__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 (46) hide show
  1. meds_algorithm/ARCHITECTURE.md +376 -0
  2. meds_algorithm/CONTRACT.md +567 -0
  3. meds_algorithm/__init__.py +94 -0
  4. meds_algorithm/algorithms.py +157 -0
  5. meds_algorithm/artifacts/__init__.py +1 -0
  6. meds_algorithm/artifacts/inputs.py +38 -0
  7. meds_algorithm/artifacts/manifest.py +142 -0
  8. meds_algorithm/artifacts/publication.py +68 -0
  9. meds_algorithm/artifacts/references.py +79 -0
  10. meds_algorithm/artifacts/results.py +76 -0
  11. meds_algorithm/configuration.py +64 -0
  12. meds_algorithm/conformance/__init__.py +1 -0
  13. meds_algorithm/conformance/assertions.py +62 -0
  14. meds_algorithm/conformance/end_to_end.py +150 -0
  15. meds_algorithm/conformance/meds_dev.py +127 -0
  16. meds_algorithm/conformance/reports.py +49 -0
  17. meds_algorithm/conformance/signal.py +141 -0
  18. meds_algorithm/conformance/types.py +29 -0
  19. meds_algorithm/contracts/__init__.py +1 -0
  20. meds_algorithm/contracts/commands.py +182 -0
  21. meds_algorithm/contracts/enums.py +28 -0
  22. meds_algorithm/data/__init__.py +1 -0
  23. meds_algorithm/data/index.py +56 -0
  24. meds_algorithm/data/labels.py +55 -0
  25. meds_algorithm/data/predicates.py +60 -0
  26. meds_algorithm/data/validation.py +25 -0
  27. meds_algorithm/evaluation/__init__.py +1 -0
  28. meds_algorithm/evaluation/predictions.py +56 -0
  29. meds_algorithm/integrations/__init__.py +1 -0
  30. meds_algorithm/integrations/meds_dev.py +51 -0
  31. meds_algorithm/requests.py +262 -0
  32. meds_algorithm/runner.py +232 -0
  33. meds_algorithm/runtime/__init__.py +1 -0
  34. meds_algorithm/runtime/process.py +50 -0
  35. meds_algorithm/runtime/provenance.py +31 -0
  36. meds_algorithm/runtime/resources.py +97 -0
  37. meds_algorithm/serialization.py +122 -0
  38. meds_algorithm/spec.py +23 -0
  39. meds_algorithm/tasks/__init__.py +1 -0
  40. meds_algorithm/tasks/spec.py +136 -0
  41. meds_algorithm/version.py +3 -0
  42. meds_algorithm-0.4.0a4.dist-info/METADATA +212 -0
  43. meds_algorithm-0.4.0a4.dist-info/RECORD +46 -0
  44. meds_algorithm-0.4.0a4.dist-info/WHEEL +4 -0
  45. meds_algorithm-0.4.0a4.dist-info/entry_points.txt +4 -0
  46. meds_algorithm-0.4.0a4.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,122 @@
1
+ """Strict, explicit-class serialization for immutable configuration and requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import math
7
+ import types
8
+ from dataclasses import fields, is_dataclass
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from typing import ClassVar, Union, get_args, get_origin, get_type_hints
12
+
13
+
14
+ def encode(value):
15
+ """JSON values only; never turn unsupported Python objects into arbitrary strings."""
16
+ if is_dataclass(value) and not isinstance(value, type):
17
+ if not value.__dataclass_params__.frozen:
18
+ raise TypeError("Serializable dataclasses must be frozen")
19
+ return {field.name: encode(getattr(value, field.name)) for field in fields(value)}
20
+ if isinstance(value, Enum):
21
+ return value.value
22
+ if isinstance(value, Path):
23
+ return str(value)
24
+ if isinstance(value, tuple):
25
+ return [encode(item) for item in value]
26
+ if value is None or type(value) in (str, bool, int):
27
+ return value
28
+ if type(value) is float and math.isfinite(value):
29
+ return value
30
+ raise TypeError(f"Unsupported serialization value: {type(value).__name__}")
31
+
32
+
33
+ def decode(annotation, value, *, base: Path):
34
+ origin = get_origin(annotation)
35
+ args = get_args(annotation)
36
+ if origin in (Union, types.UnionType):
37
+ for option in args:
38
+ try:
39
+ return decode(option, value, base=base)
40
+ except (TypeError, ValueError):
41
+ pass
42
+ raise TypeError(f"Value does not match {annotation}")
43
+ if annotation is type(None) and value is None:
44
+ return None
45
+ if annotation is Path:
46
+ if not isinstance(value, (str, Path)) or not str(value).strip():
47
+ raise TypeError("Expected a nonempty path")
48
+ path = Path(value)
49
+ return (base / path).resolve() if not path.is_absolute() else path.resolve()
50
+ if origin is tuple:
51
+ if not isinstance(value, (tuple, list)):
52
+ raise TypeError("Expected an array")
53
+ if len(args) == 2 and args[1] is Ellipsis:
54
+ return tuple(decode(args[0], item, base=base) for item in value)
55
+ if len(args) != len(value):
56
+ raise TypeError("Wrong tuple length")
57
+ return tuple(decode(kind, item, base=base) for kind, item in zip(args, value, strict=True))
58
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
59
+ return annotation(value)
60
+ if is_dataclass(annotation):
61
+ if isinstance(value, annotation):
62
+ value = encode(value)
63
+ if not isinstance(value, dict):
64
+ raise TypeError(f"Expected an object for {annotation.__name__}")
65
+ names = {field.name for field in fields(annotation)}
66
+ if unknown := value.keys() - names:
67
+ raise ValueError(f"Unknown fields for {annotation.__name__}: {sorted(unknown)}")
68
+ hints = get_type_hints(annotation)
69
+ return annotation(**{key: decode(hints[key], item, base=base) for key, item in value.items()})
70
+ if annotation is float and type(value) in (int, float) and math.isfinite(value):
71
+ return float(value)
72
+ if annotation in (str, int, bool) and type(value) is annotation:
73
+ return value
74
+ raise TypeError(f"Expected {annotation}, got {type(value).__name__}")
75
+
76
+
77
+ class Serializable:
78
+ """Subclass with a frozen dataclass. Loading never imports a class named by a file."""
79
+
80
+ schema_version: ClassVar[int] = 1
81
+
82
+ def __post_init__(self):
83
+ hints = get_type_hints(type(self))
84
+ for field in fields(self):
85
+ object.__setattr__(
86
+ self, field.name, decode(hints[field.name], getattr(self, field.name), base=Path.cwd())
87
+ )
88
+ encode(self)
89
+ self.validate()
90
+
91
+ def validate(self) -> None:
92
+ """Override to enforce algorithm-specific value constraints."""
93
+
94
+ def to_dict(self) -> dict:
95
+ return encode(self)
96
+
97
+ @classmethod
98
+ def from_dict(cls, values: dict, *, base: Path | None = None):
99
+ return decode(cls, values, base=Path.cwd() if base is None else Path(base).resolve())
100
+
101
+ def save(self, path: Path | str) -> Path:
102
+ path = Path(path)
103
+ envelope = {
104
+ "schema_version": self.schema_version,
105
+ "type": f"{type(self).__module__}.{type(self).__qualname__}",
106
+ "values": self.to_dict(),
107
+ }
108
+ path.write_text(json.dumps(envelope, sort_keys=True, indent=2, allow_nan=False), encoding="utf-8")
109
+ return path
110
+
111
+ @classmethod
112
+ def load(cls, path: Path | str):
113
+ path = Path(path).resolve()
114
+ envelope = json.loads(path.read_text(encoding="utf-8"))
115
+ if not isinstance(envelope, dict) or set(envelope) != {"schema_version", "type", "values"}:
116
+ raise ValueError("Invalid serialized envelope")
117
+ if (
118
+ envelope["schema_version"] != cls.schema_version
119
+ or envelope["type"] != f"{cls.__module__}.{cls.__qualname__}"
120
+ ):
121
+ raise ValueError("Serialized type or schema version mismatch")
122
+ return cls.from_dict(envelope["values"], base=path.parent)
meds_algorithm/spec.py ADDED
@@ -0,0 +1,23 @@
1
+ """Read the one normative specification shipped with the installed core version."""
2
+
3
+ import argparse
4
+ from importlib.resources import files
5
+
6
+
7
+ def specification() -> str:
8
+ return files("meds_algorithm").joinpath("CONTRACT.md").read_text(encoding="utf-8")
9
+
10
+
11
+ def architecture() -> str:
12
+ """Read the bundled API/UML guide maintained as ARCHITECTURE.md in the repository."""
13
+ return files("meds_algorithm").joinpath("ARCHITECTURE.md").read_text(encoding="utf-8")
14
+
15
+
16
+ def main() -> None:
17
+ parser = argparse.ArgumentParser(description=__doc__)
18
+ parser.parse_args()
19
+ print(specification())
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1 @@
1
+ """Task identity and declared semantic relaxations."""
@@ -0,0 +1,136 @@
1
+ """The zero-shot task boundary: task specification, label-blind index, and relaxation ledger.
2
+
3
+ A supervised algorithm learns *what* to predict from labels. A zero-shot algorithm is never shown labels,
4
+ so the task definition itself must cross the contract boundary. The task language is ACES: a criteria
5
+ file plus, optionally, a dataset predicates file.
6
+
7
+ The contract fixes only the inputs (task, index, MEDS data), the final predictions, the semantics digest
8
+ that ties every artifact to the exact task it answers, and a minimal ledger of how the answered task
9
+ differs from the requested one. How a algorithm turns the task into predictions is its own; the
10
+ implementation guide recommends an encode -> infer -> decode shape.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ from collections.abc import Mapping, Sequence
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ import yaml
22
+
23
+ INDEX_COLUMNS = ("subject_id", "prediction_time")
24
+ RELAXATIONS_FILENAME = "relaxations.yaml"
25
+ PREDICTED_PROBABILITY = "predicted_boolean_probability"
26
+
27
+
28
+ def _digest_file(path: Path) -> str:
29
+ return f"sha256:{hashlib.sha256(path.read_bytes()).hexdigest()}"
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class TaskSpec:
34
+ """A task in the overall task language: an ACES criteria file and its optional predicates file."""
35
+
36
+ criteria_file: Path
37
+ predicates_file: Path | None = None
38
+ name: str | None = None
39
+
40
+ def __post_init__(self) -> None:
41
+ object.__setattr__(self, "criteria_file", Path(self.criteria_file))
42
+ if self.predicates_file is not None:
43
+ object.__setattr__(self, "predicates_file", Path(self.predicates_file))
44
+ for path in (self.criteria_file, self.predicates_file):
45
+ if path is not None and not path.is_file():
46
+ raise FileNotFoundError(f"Task file {path} does not exist")
47
+
48
+ @classmethod
49
+ def from_arguments(cls, arguments: Mapping[str, Any]) -> TaskSpec:
50
+ """Read the standard ``external_task_criteria_file`` / ``external_predicates_file`` arguments."""
51
+ criteria = arguments.get("external_task_criteria_file")
52
+ if not criteria:
53
+ raise ValueError(
54
+ "zero-shot commands require external_task_criteria_file=<ACES task YAML>; without it the "
55
+ "algorithm cannot know what to predict"
56
+ )
57
+ return cls(
58
+ criteria_file=criteria,
59
+ predicates_file=arguments.get("external_predicates_file") or None,
60
+ name=arguments.get("task_name") or None,
61
+ )
62
+
63
+ def semantics_digest(self) -> str:
64
+ """A digest of exactly what was asked: the criteria bytes and the predicates bytes.
65
+
66
+ Artifacts may be combined only when their semantics digests agree. The digest changes when either
67
+ file changes, including when the same criteria are bound to another dataset's predicates.
68
+ """
69
+ digest = hashlib.sha256()
70
+ for label, path in (("criteria", self.criteria_file), ("predicates", self.predicates_file)):
71
+ digest.update(label.encode())
72
+ digest.update(b"\0" if path is None else hashlib.sha256(path.read_bytes()).digest())
73
+ return f"sha256:{digest.hexdigest()}"
74
+
75
+ def provenance(self) -> dict[str, Any]:
76
+ """The manifest ``inputs`` entry recording which task was answered."""
77
+
78
+ def entry(path: Path | None) -> dict[str, str] | None:
79
+ return None if path is None else {"path": str(path.resolve()), "digest": _digest_file(path)}
80
+
81
+ return {
82
+ "name": self.name,
83
+ "criteria_file": entry(self.criteria_file),
84
+ "predicates_file": entry(self.predicates_file),
85
+ }
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class RelaxationLedger:
90
+ """What a zero-shot prediction actually estimates, written as ``relaxations.yaml``.
91
+
92
+ Only ``query_language``, ``decoded_quantity``, and ``exact`` are required. ``steps`` is free-form: one
93
+ mapping per rewrite or assumption between the requested and the answered task. The implementation
94
+ guide recommends ``name``, ``kind``, ``direction``, and ``detail`` keys; the contract does not validate
95
+ them. An exact algorithm writes ``exact: true`` and no steps.
96
+ """
97
+
98
+ query_language: str
99
+ decoded_quantity: str
100
+ exact: bool
101
+ steps: Sequence[Mapping[str, Any]] = field(default_factory=tuple)
102
+
103
+ def __post_init__(self) -> None:
104
+ for name in ("query_language", "decoded_quantity"):
105
+ if not isinstance(getattr(self, name), str) or not getattr(self, name).strip():
106
+ raise ValueError(f"{RELAXATIONS_FILENAME} requires a non-empty {name}")
107
+ if not isinstance(self.exact, bool):
108
+ raise ValueError(f"{RELAXATIONS_FILENAME} requires exact: true or false")
109
+ object.__setattr__(self, "steps", tuple(dict(step) for step in self.steps))
110
+
111
+ def write(self, directory: Path | str) -> Path:
112
+ path = Path(directory) / RELAXATIONS_FILENAME
113
+ value = {
114
+ "query_language": self.query_language,
115
+ "decoded_quantity": self.decoded_quantity,
116
+ "exact": self.exact,
117
+ "steps": [dict(step) for step in self.steps],
118
+ }
119
+ path.write_text(yaml.safe_dump(value, sort_keys=False))
120
+ return path
121
+
122
+ @classmethod
123
+ def read(cls, directory: Path | str) -> RelaxationLedger:
124
+ path = Path(directory) / RELAXATIONS_FILENAME
125
+ if not path.is_file():
126
+ raise FileNotFoundError(f"{directory} has no {RELAXATIONS_FILENAME}")
127
+ value = yaml.safe_load(path.read_text()) or {}
128
+ missing = {"query_language", "decoded_quantity", "exact"} - set(value)
129
+ if missing:
130
+ raise ValueError(f"{path} lacks required fields {sorted(missing)}")
131
+ return cls(
132
+ query_language=value["query_language"],
133
+ decoded_quantity=value["decoded_quantity"],
134
+ exact=value["exact"],
135
+ steps=value.get("steps") or (),
136
+ )
@@ -0,0 +1,3 @@
1
+ """Artifact contract version, independent of the package release."""
2
+
3
+ CONTRACT_VERSION = "4"
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.5
2
+ Name: meds-algorithm
3
+ Version: 0.4.0a4
4
+ Summary: Shared MEDS algorithm contracts and conformance boundaries.
5
+ Project-URL: Repository, https://github.com/McDermottHealthAI/meds-algorithm
6
+ Project-URL: Documentation, https://github.com/McDermottHealthAI/meds-algorithm/blob/main/ARCHITECTURE.md
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: pyyaml>=6
11
+ Provides-Extra: conformance
12
+ Requires-Dist: meds-evaluation==0.0.3; extra == 'conformance'
13
+ Requires-Dist: polars>=1; extra == 'conformance'
14
+ Requires-Dist: pyarrow>=15; extra == 'conformance'
15
+ Requires-Dist: pytest>=8; extra == 'conformance'
16
+ Provides-Extra: data
17
+ Requires-Dist: polars>=1; extra == 'data'
18
+ Provides-Extra: test
19
+ Requires-Dist: meds-evaluation==0.0.3; extra == 'test'
20
+ Requires-Dist: polars>=1; extra == 'test'
21
+ Requires-Dist: pyarrow>=15; extra == 'test'
22
+ Requires-Dist: pytest>=8; extra == 'test'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # meds-algorithm
26
+
27
+ [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue?logo=python&logoColor=white)](https://www.python.org/downloads/)
28
+ [![PyPI: unreleased](https://img.shields.io/badge/PyPI-unreleased-lightgrey)](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/RELEASING.md)
29
+ [![Tests and code quality](https://github.com/McDermottHealthAI/meds-algorithm/actions/workflows/ci.yml/badge.svg)](https://github.com/McDermottHealthAI/meds-algorithm/actions/workflows/ci.yml)
30
+ [![Documentation](https://img.shields.io/badge/docs-architecture-blue)](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/ARCHITECTURE.md)
31
+ [![Contributors](https://img.shields.io/github/contributors/McDermottHealthAI/meds-algorithm)](https://github.com/McDermottHealthAI/meds-algorithm/graphs/contributors)
32
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/McDermottHealthAI/meds-algorithm/pulls)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green)](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/LICENSE)
34
+
35
+ Shared infrastructure for implementing and validating MEDS algorithm repositories. The package defines
36
+ algorithm profiles and command contracts, manages artifacts and run provenance, reads prediction indices
37
+ and training targets, validates prediction outputs, and provides reusable conformance tests.
38
+
39
+ Algorithm repositories import these utilities so fixes can be distributed through a package update.
40
+ Algorithm architectures, tokenizers, training objectives and task-specific logic stay in the algorithm repository.
41
+
42
+ See the [architecture and UML diagrams](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/ARCHITECTURE.md) for the profile hierarchy, typed requests,
43
+ algorithm-owned configuration, artifact references and shared execution path.
44
+
45
+ ## Package layout
46
+
47
+ ```text
48
+ src/meds_algorithm/
49
+ ├── CONTRACT.md # canonical normative specification, included in the wheel
50
+ ├── spec.py # installed specification reader
51
+ ├── algorithms.py # Algorithm and profile ABCs, lifecycle order
52
+ ├── requests.py # frozen per-step requests and CLI field mappings
53
+ ├── configuration.py # AlgorithmConfig, NNConfig and execution options
54
+ ├── serialization.py # strict, versioned config/request JSON serialization
55
+ ├── runner.py # validate, execute, measure, publish; CLI adapter
56
+ ├── contracts/commands.py # derived compatibility views and legacy dispatch
57
+ ├── artifacts/
58
+ │ ├── references.py # lazy typed artifact references and payload metadata
59
+ │ ├── manifest.py # measured artifacts, envelope reading and digests
60
+ │ ├── publication.py # unique staging, exclusive writers and overwrite rollback
61
+ │ ├── inputs.py # resolve recorded upstream artifact paths
62
+ │ └── results.py # structured run evidence
63
+ ├── runtime/
64
+ │ ├── provenance.py # explicitly supplied algorithm checkout
65
+ │ ├── resources.py # process-tree memory sampling and platform fallback
66
+ │ └── process.py # bounded subprocess execution
67
+ ├── data/ # split selection, key-only index reads, context targets
68
+ ├── tasks/spec.py # TaskSpec and RelaxationLedger
69
+ ├── evaluation/predictions.py # keyed probabilities and post-score truth attachment
70
+ ├── integrations/meds_dev.py # per-profile registration and CLI
71
+ └── conformance/ # assertions, synthetic fixtures, full-lifecycle checks,
72
+ # opt-in MEDS-DEV provisioning and algorithm adapter result types
73
+ ```
74
+
75
+ ## Installation and dependencies
76
+
77
+ Install a reviewed revision with
78
+ `pip install 'meds-algorithm @ git+https://github.com/McDermottHealthAI/meds-algorithm.git@<full-commit-sha>'`.
79
+ The template currently records the exact SHA. For package-index release checks and publishing setup,
80
+ see the [release guide](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/RELEASING.md).
81
+
82
+ - Base package: PyYAML; no dataframe engine, test runner, GPU or algorithm framework is imported by base APIs.
83
+ - `data` extra: Polars for index/target reads and prediction validation.
84
+ - `conformance` extra: pytest, Polars, PyArrow and the pinned MEDS evaluation schema implementation.
85
+ - `test` extra: dependencies for developing and testing this package.
86
+
87
+ The installed CLI provides `meds-algorithm-spec`, `meds-algorithm-resolve-input`, and
88
+ `meds-algorithm-add-to-meds-dev`. Algorithm repositories provide their own `meds-algorithm` entry point.
89
+
90
+ New algorithm repositories implement `Algorithm` profile subclasses, receive frozen typed requests, and
91
+ execute through `AlgorithmRepository`/`StepRunner`. Their own frozen `AlgorithmConfig` subclass defines
92
+ hyperparameters, defaults and validation. Use `config_file=...` for a JSON/YAML values mapping or a
93
+ previously saved `config.json`. Typed prediction execution requires the `data` extra. See the
94
+ [architecture guide](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/ARCHITECTURE.md) for the complete ownership and serialization algorithm.
95
+
96
+ Neural algorithms can extend `NNConfig` for a capacity multiplier, learning rate, batch size,
97
+ weight decay and dropout. Concrete algorithms define the multiplier-to-architecture mapping and training
98
+ defaults. See [neural configuration and scaling](https://github.com/McDermottHealthAI/meds-algorithm/blob/main/ARCHITECTURE.md#neural-network-configuration-and-scaling-experiments)
99
+ for an extensible example and the recommended upstream ownership of data scaling.
100
+
101
+ ## Ownership
102
+
103
+ The template imports this package directly; it does not generate a `meds_algorithm_contract` package or
104
+ copy these implementations. Generated files contain algorithm command skeletons, selected profile YAMLs,
105
+ execution adapters in `tests/e2e.py`, algorithm-specific tests, and project/cluster configuration.
106
+ Family sharing belongs inside one algorithm-family repository with several profiles. Tokenizers,
107
+ architectures, objectives, payload layouts and task translators remain algorithm-owned.
108
+
109
+ `CONTRACT.md` is the only normative document. Template documentation renders it from the installed
110
+ package. Algorithm `SPEC.md` files point to it; algorithm implementation reports record local decisions.
111
+
112
+ MEDS-DEV's upstream interface still uses `model.yaml`, `src/MEDS_DEV/models`, `meds-dev-model`,
113
+ `model=`, and `{model_initialization_dir}`. Registration translates the local `algorithms/<profile>/`
114
+ layout into that interface; these upstream names are not part of this package's rename.
115
+
116
+ ## Publication, provenance and measurement
117
+
118
+ Pass the algorithm checkout explicitly to `StepRunner(repo=algorithm_root)` or, for legacy implementations,
119
+ `measured_artifact(..., repo=algorithm_root)`. The package never
120
+ infers the algorithm from its own installation path or the working directory. Without a supplied checkout,
121
+ commit and dirty state are unknown (`null`); wheel deployments must retain algorithm build provenance
122
+ separately rather than inventing a checkout revision.
123
+
124
+ Publication uses a unique sibling staging directory and an exclusive destination lock. A new artifact
125
+ appears in one rename. Overwrite retains the old directory as a backup until the new directory is
126
+ published, and restores it if publication fails. Replacement uses two renames, so concurrent readers
127
+ can briefly observe an absent destination. Prefer immutable output paths for concurrent readers.
128
+ This is not a power-loss transaction: after a process crash, inspect the `.lock`, `.staging-*` and
129
+ `.backup-*` siblings before manually recovering. If restoration itself fails, the exception names the
130
+ preserved backup; it is never silently deleted. Locks apply to cooperating core writers.
131
+
132
+ Linux sampling visits the children of every process thread, including subprocesses launched by worker
133
+ threads. Sampling can miss short-lived peaks; inaccessible descendants are not a complete measurement.
134
+ On platforms without `/proc`, use process-only `getrusage` when available; otherwise report unavailable
135
+ values as `null`. GPU instrumentation remains the algorithm/framework's responsibility.
136
+
137
+ ## Validation
138
+
139
+ ```bash
140
+ uv sync --extra test --group dev
141
+ uv run ruff check .
142
+ uv run ruff format --check .
143
+ uv run pytest -q
144
+ uv build
145
+ ```
146
+
147
+ Tests inject publication/restoration failures, launch a child from a worker thread, check explicit
148
+ algorithm provenance, reject missing/mixed/overlapping label inputs, and exercise all six lifecycles with
149
+ synthetic command implementations. Full-lifecycle conformance reruns algorithm adapters with forbidden
150
+ outcomes removed and fresh artifacts; algorithm-specific cutoff, frozen-weight, compatibility and primary
151
+ behavior tests remain required. Package tests do not establish scientific validity of a downstream algorithm.
152
+
153
+ MEDS-DEV provisioning requires explicit `-m meds_dev` or `-m real_data` selection; setting
154
+ `MEDS_DEV_DIR` only supplies a source checkout. Unit tests do not download a real dataset or launch
155
+ training. Keep each algorithm's data-access and cluster approval rules in its own repository.
156
+
157
+ ## Changes
158
+
159
+ ### 0.4.0a4
160
+
161
+ - Rename the repository/distribution from `meds-algorithm-core` to `meds-algorithm`, the Python package
162
+ from `meds_algorithm_core` to `meds_algorithm`, and the contract command to `meds-algorithm-spec`.
163
+ Update imports and dependency pins together. No old-name aliases are installed; serialized envelopes
164
+ with old package-qualified type names require an explicit migration before loading.
165
+
166
+ ### 0.4.0a3
167
+
168
+ - Add optional `NNConfig(AlgorithmConfig)` with a scalar capacity multiplier and strict immutable settings,
169
+ inherited serialization, validation and runner provenance, without neural-framework dependencies.
170
+ - Document capacity semantics, configuration sweeps and the distinction between implemented configuration
171
+ and future upstream data-scaling/consumption-reporting integrations.
172
+
173
+ ### 0.4.0a2
174
+
175
+ - Add optional `AlgorithmConfig.additional_predicates_file` for dataset-specific feature predicates.
176
+ Preprocessing composes it with the base, rejects duplicate names/keys, preserves the resolved file
177
+ and source digests, and keeps task predicates unchanged. Later steps do not reopen temporary sources.
178
+
179
+ ### 0.4.0a1
180
+
181
+ - Add profile ABCs, typed requests/artifact references and a shared runner that owns validation,
182
+ measured publication and post-score truth attachment.
183
+ - Add strict immutable algorithm-owned configuration and versioned config/request serialization; record
184
+ complete resolved values, upstream manifest identities and key-only index identities.
185
+ - Derive profile/step metadata from the new classes. Rename the package, Python API, CLI and artifact
186
+ types/arguments to algorithm terminology under contract version 4. No old-name aliases are installed;
187
+ migrations remain separate. Mapping-based execution is available as `LegacyAlgorithmRepository`.
188
+ - Include the canonical API/UML architecture guide in the wheel for downstream documentation.
189
+
190
+ ### 0.3.0a3
191
+
192
+ - Compare seeded full-lifecycle predictions with and without forbidden labels, preserving separate
193
+ training, index and evaluation inputs.
194
+ - Validate original and rerun probabilities across every prediction shard, and accept a separate
195
+ evaluation index when checking context-label overlap.
196
+ - Terminate subprocess trees on timeout or interruption; deliberately detached services are unsupported.
197
+
198
+ ### 0.3.0a2
199
+
200
+ - Complete the extraction of shared artifact, runtime, data, task, evaluation, MEDS-DEV and
201
+ conformance utilities from the template; replace the reserved placeholder modules with implementations.
202
+ - Add exclusive artifact writers, overwrite rollback, explicit algorithm-checkout provenance and
203
+ process-tree memory sampling with platform fallbacks.
204
+ - Reject ambiguous splits, overlapping context/evaluation keys, invalid probabilities and malformed
205
+ artifact manifests; rerun complete profile lifecycles to check for outcome-label leakage.
206
+ - Make the core package the source of the contract and shared tests, removing generated copies from
207
+ the template. Migration of existing algorithm repositories is separate.
208
+
209
+ ### 0.3.0a1
210
+
211
+ - Introduce contract version 3, profile/command definitions, the installed specification reader and
212
+ a key-only prediction index reader.
@@ -0,0 +1,46 @@
1
+ meds_algorithm/CONTRACT.md,sha256=xntPBoiX20BrgKIwINrMIuGfZ69jWSXzD8xhgTg1rTc,42152
2
+ meds_algorithm/__init__.py,sha256=Mu7qfI8R8ErHNkvhalMsxa6G12cGt-5YPtZyzLKlt5A,2526
3
+ meds_algorithm/algorithms.py,sha256=4C9r9qHSBDK3RldbL2oSQ-MsQuCSpYp7eMxbFdelypU,5653
4
+ meds_algorithm/configuration.py,sha256=JeCl14sHTTso2PQNJ3x4YKlT_CtF2UPTiV8w9mY2xqE,2163
5
+ meds_algorithm/requests.py,sha256=Jhouni-ifB698j007WowPPjP36ZDGGlQd2Zrzty16R0,10196
6
+ meds_algorithm/runner.py,sha256=1o3eQ7Zq79YYXPVG8-Ur_-eTf7KzfRWOZ33acTcPJtM,10988
7
+ meds_algorithm/serialization.py,sha256=A2Kw3eIqgKv5T1iWzc8m3DiK_beob-33lip_PtD2BJM,5059
8
+ meds_algorithm/spec.py,sha256=905AEeaHiltB0WpjZnzKWcmmEn6xuaLWYabh7UinbhE,637
9
+ meds_algorithm/version.py,sha256=ZZ9-aff2JbQFi-0cPGgZlE0q46rN6lNLQIrLTrd68lU,93
10
+ meds_algorithm/artifacts/__init__.py,sha256=U0LxpaI6dp7bUj-6UwyNgONOeSYLjC1l4j7SMZYbUvQ,74
11
+ meds_algorithm/artifacts/inputs.py,sha256=ReLGWzdicazWw25pXXKKgbP2UvVz8STyonW9Yw_gO8A,1561
12
+ meds_algorithm/artifacts/manifest.py,sha256=ogw27Mki-P4J9ZGpGbSV4rM2u9YzkG17cUmJ1s-1tck,5914
13
+ meds_algorithm/artifacts/publication.py,sha256=hPS96rGzmkORz3x7-oVSsaD9z8gjw9zWwgPHOIx8bUk,2922
14
+ meds_algorithm/artifacts/references.py,sha256=GBMsBrm9INRtjnaHNbPe8thKyZe_f0jJW_L610Gnw8Q,2139
15
+ meds_algorithm/artifacts/results.py,sha256=ov00gdQzldVv0igd47gkS-UJy7S8IXPNoW9WPyNxC_A,2786
16
+ meds_algorithm/conformance/__init__.py,sha256=6yj79ojbhg2QuUCQW6f3TtZWHDN4bffTrfuAcpkQ0_Q,96
17
+ meds_algorithm/conformance/assertions.py,sha256=sT90qNusDYxN5K1GMv-lN7aVbaWYqNiHjcV-CAORneU,2975
18
+ meds_algorithm/conformance/end_to_end.py,sha256=odQ--nDDPLjrmgzAdseBkfmxzUHq0mtIiJ-IC13ilt4,7207
19
+ meds_algorithm/conformance/meds_dev.py,sha256=ap1vpHiA_4ONZWhB2m0_U6VSVg-lip0syKa4j8T9dTE,5649
20
+ meds_algorithm/conformance/reports.py,sha256=DfY90OZiVsh9EmhglGXKKKnD6wKD5aIu3hyKlKaEyd4,1907
21
+ meds_algorithm/conformance/signal.py,sha256=l8Q0dbGTuKLvLU9sHB6FogH-61cdCoBa9ccspya4NL0,5919
22
+ meds_algorithm/conformance/types.py,sha256=LpZp4R2nQSDa3EHjzkHOK_-HTuOIQBTNhmQjhG3JcEs,1017
23
+ meds_algorithm/contracts/__init__.py,sha256=MIWkD45lGJ4LpQ9TAaY3hQkyRVuE3kFTPipof8dik8M,61
24
+ meds_algorithm/contracts/commands.py,sha256=h4UPFAF_R1ac7NTxXZvSeUEhUNDj-AKgLpL2sXuBSpc,7390
25
+ meds_algorithm/contracts/enums.py,sha256=Ln7qqjTeiuPVO6oPh-7SL8Do9wWOB_i8T3V2s64JS7o,948
26
+ meds_algorithm/data/__init__.py,sha256=Ln7Tf2l5OX_pOFUOo84ZAHmaNCBqaeeWyRNitsRk7k0,84
27
+ meds_algorithm/data/index.py,sha256=EXuJDsiir0UWSyz7WQEPhs_pkELa-OSFghww-mQRJ7I,2580
28
+ meds_algorithm/data/labels.py,sha256=G2131UJL-g2XEVm6i4iMrQ-hzDudtagc1ig0l_x_FmE,2316
29
+ meds_algorithm/data/predicates.py,sha256=n7Gx3nofly8Nbx7GKDvm21rpHRkDlUNrGiEZhKk9TYw,2623
30
+ meds_algorithm/data/validation.py,sha256=qTbhiaiT-cIaqPsoDuksajRakz561IZx3WpIv3i_RoQ,1002
31
+ meds_algorithm/evaluation/__init__.py,sha256=m40EM0hLh7S7jFydZ1QpPU7L_gUjLbCqg4nclO_xV48,67
32
+ meds_algorithm/evaluation/predictions.py,sha256=LIyu_VUMG_k00FacKS_ahMlqQjFMraotKye2iU06ZWk,2437
33
+ meds_algorithm/integrations/__init__.py,sha256=P0aiVlwgV1CC1EdgW_TXOw652yZL62Xly81seP9FGgE,60
34
+ meds_algorithm/integrations/meds_dev.py,sha256=z5e7UVrg8AjOqq0GGNCJmUxbhEEvyzl2AlzmNVj8FoI,2190
35
+ meds_algorithm/runtime/__init__.py,sha256=tbgzQJiN1yQ9TmgTwLsyUshHjkMlRsLhVdu25ilW0K0,99
36
+ meds_algorithm/runtime/process.py,sha256=1v1PIcNyw1RJ0_lWrmKM1k2jcMLhRMwoKxZOVl69A0s,1726
37
+ meds_algorithm/runtime/provenance.py,sha256=uDaA9xMfpEDwf7hLuYNs9vI0LsMRGctdw_2YUk4nDWY,1019
38
+ meds_algorithm/runtime/resources.py,sha256=K9rPREOL2x1uU0k6Sk_7_aaP72zD0QrROhO2r31JqG8,3623
39
+ meds_algorithm/tasks/__init__.py,sha256=I8JslXTkVYHfQc19k_voL7CqxAHBliNarTQnl4brJE4,55
40
+ meds_algorithm/tasks/spec.py,sha256=59AB902iPIcaxRGSNKdnG50UxhIBkGHZVttc1TfnRqw,5961
41
+ meds_algorithm/ARCHITECTURE.md,sha256=X7ytGqAEdZMA2YNEMlyI7AQVdvFnQlsYk85cV7uBwwo,18026
42
+ meds_algorithm-0.4.0a4.dist-info/METADATA,sha256=ZTIEi0SALEx93WbH8eFfFPlKujFSMGdpCb949LETjpg,13444
43
+ meds_algorithm-0.4.0a4.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
44
+ meds_algorithm-0.4.0a4.dist-info/entry_points.txt,sha256=mmxSsbl65W6_GFEIiMYlYeH6ulSGA-iITYQP3IQRYfs,208
45
+ meds_algorithm-0.4.0a4.dist-info/licenses/LICENSE,sha256=k0wqQurs6KfFG-mjx2fUCVlEqpUSNyiyO2sGtU8U-Ac,1119
46
+ meds_algorithm-0.4.0a4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ meds-algorithm-add-to-meds-dev = meds_algorithm.integrations.meds_dev:main
3
+ meds-algorithm-resolve-input = meds_algorithm.artifacts.inputs:main
4
+ meds-algorithm-spec = meds_algorithm.spec:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew McDermott and the MEDS_algorithm_template contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.