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,567 @@
1
+ # MEDS Algorithm Repository Contract, version 4
2
+
3
+ This document is the single authoritative contract shipped with meds-algorithm. Algorithm repositories
4
+ pin a core version and refer to its contract; they do not maintain copies of these rules. A conforming repository turns a MEDS dataset, an optional task, and an optional semantic
5
+ specification into MEDS predictions through one or more of six fixed algorithm profiles. How a command performs its
6
+ work is deliberately outside this contract.
7
+
8
+ ## 1. Principles
9
+
10
+ - The input and final output are interoperable; intermediate payloads are implementation-owned.
11
+ - Commands have stable meanings, but do not prescribe Python, batching, tensors, training frameworks,
12
+ checkpoint formats, or execution hardware.
13
+ - Every intermediate artifact has a standard manifest envelope even when its payload is opaque.
14
+ - A repository is operational: it declares itself through `algorithms/<profile>/model.yaml`, tests its real implementation end
15
+ to end, runs locally or through SLURM, and can exchange structured run results through GitHub.
16
+ - The six algorithm profiles are the supported command lifecycles. A repository declares a nonempty set; it does not invent an
17
+ undocumented profile.
18
+
19
+ ## 2. Inputs
20
+
21
+ ### 2.1 MEDS dataset
22
+
23
+ `external_meds_dir` MUST be a valid, split-sharded MEDS dataset. Event semantics come from the versioned
24
+ MEDS standard. Implementations MUST NOT reinterpret `subject_id`, `time`, `code`, value columns, static
25
+ events (`time = null`), or code metadata incompatibly. MIMIC-IV demo in MEDS form is the canonical real
26
+ data example, not an additional schema.
27
+
28
+ The `train`, `tuning`, and `held_out` splits are a boundary, not a convention. Any statistic fitted over a
29
+ population — vocabularies, code frequencies, quantile bins, normalization constants, feature selections —
30
+ MUST be fitted on the training split alone. Fitting one over all splits is *transductive* leakage: it is
31
+ not label leakage, because quantities like code frequency are label-independent, so an implementation can
32
+ satisfy every label-leakage check while carrying it. Where the fitting scope is not the implementation's
33
+ to control, because preprocessing is delegated upstream, it MUST declare which quantity was fitted over
34
+ which population rather than leave the scope unstated.
35
+
36
+ ### 2.2 Task
37
+
38
+ A task input is an index of `(subject_id, prediction_time)` and, for training/evaluation only, a value
39
+ column such as `boolean_value`. Labels of an **evaluated split** MUST never be read to produce a
40
+ prediction. Which other labels a command may read depends on the profile:
41
+
42
+ The per-step table in section 9 specifies access to **ground-truth target values**, such as
43
+ `boolean_value`. These are different from index keys `(subject_id, prediction_time)` and from the
44
+ algorithm's predicted probability. Inference and ordinary scoring receive `external_index_dir` and project
45
+ only its keys. Training receives `external_labels_dir`; in-context prediction may also receive that
46
+ argument solely for explicitly permitted train/tuning context examples. Zero-shot commands read none.
47
+
48
+ MEDS-DEV may map its existing `{labels_dir}` source to `external_index_dir`. That is a key-only view of
49
+ the same files, not permission to load their outcome columns. A physically separate keys-only index is
50
+ also supported. Neither representation rebuilds or filters the upstream cohort.
51
+
52
+ Once scores are fixed, an evaluation adapter MAY use `external_evaluation_labels_dir` to join truth onto the
53
+ predictions by key (`meds_algorithm.attach_ground_truth`), because meds-evaluation reads ground truth
54
+ from the predictions file. Nothing computed from that join may feed back into a prediction.
55
+
56
+ The cohort is built upstream, not in the algorithm repository. The index and its labels come from one
57
+ extraction run outside every algorithm: MEDS-DEV's `meds-dev-task` for the MIMIC-IV demo, and a shared, reviewed
58
+ task bundle for full-data runs, whose tasks may be clinical tasks defined in ACES or randomly sampled
59
+ code-by-horizon tasks. That extraction applies the trigger, inclusion/exclusion criteria, and splits once,
60
+ so every algorithm scores the same keys. A algorithm repository
61
+ MUST NOT re-extract, filter, or extend the index; it predicts every requested row (section 5). This holds
62
+ for zero-shot profiles too: they receive the ACES task (section 2.4) to build their *query*, never to
63
+ rebuild their cohort.
64
+
65
+ ### 2.3 Dataset predicates
66
+
67
+ Dataset-specific predicates are external inputs, supplied through the established
68
+ `external_predicates_file` argument. When MEDS-DEV provides predicates for a dataset, the repository MUST
69
+ reuse them rather than duplicating dataset knowledge in the algorithm template. In particular, the MIMIC-IV
70
+ demo MUST use MEDS-DEV's MIMIC-IV predicates. A algorithm decides whether and how to consume them: token
71
+ selection, SQL, columns, time-series extraction, direct event matching, or no use at all are equally valid.
72
+ Any command that consumes the file MUST record its digest and origin.
73
+
74
+ A algorithm that depends on particular predicate names declares and validates those requirements in its own
75
+ configuration and tests. The contract does not prescribe a predicate-derived feature representation.
76
+
77
+ `AlgorithmConfig.additional_predicates_file` MAY select additional feature predicates for the current
78
+ dataset. It defaults to `None`; relative paths in `config_file` resolve against that file's parent.
79
+ During `preprocess_data` only, the runner composes the optional base `external_predicates_file` and the
80
+ additions. Both documents MUST contain only a `predicates` mapping of names to nonempty definitions.
81
+ Duplicate YAML keys and names shared across the files MUST fail, even for identical definitions.
82
+ The helper validates structure, not expression semantics or dataset compatibility.
83
+
84
+ The preprocessing implementation and its `validate_inputs` hook receive the resolved file as
85
+ `request.predicates_file`. Core owns this file, saves it as `resolved_feature_predicates.yaml` inside
86
+ the prepared-data artifact, and records source paths/digests plus the resolved relative path/digest
87
+ under `inputs.feature_predicates`. The configuration digest includes these identities. Implementations
88
+ MUST NOT modify or delete the resolved file; the original request retains the external base path.
89
+ When composing, the compatibility hook runs inside staging after resolution and is included in measurement.
90
+
91
+ Feature additions MUST remain task-independent and respect temporal cutoffs. They MUST NOT redefine
92
+ the target/cohort or supply outcomes to preprocessing. The runner never composes additions for training,
93
+ prediction, or `TaskSpec`; task bindings and evaluated keys remain unchanged. Later stages consume
94
+ persisted feature artifacts and do not reopen the additions file. Keep the same resolved configuration
95
+ across the lifecycle. A preprocessing rerun still requires its original source files.
96
+
97
+ ### 2.4 Task specification (zero-shot profiles)
98
+
99
+ A supervised algorithm learns what to predict from labels. A zero-shot algorithm never sees labels, so the task
100
+ definition itself is an input. The task language is [ACES](https://eventstreamaces.readthedocs.io/): a task
101
+ is an ACES criteria file, supplied through `external_task_criteria_file`, bound to the dataset predicates of
102
+ section 2.3 when the criteria do not define their own. The pair is the *task specification*
103
+ (`meds_algorithm.TaskSpec`).
104
+
105
+ - Every command of a `zero_shot_*` profile whose output depends on the task (`ZERO_SHOT_TASK_COMMANDS`:
106
+ `predict_zero_shot_direct` and `predict_zero_shot_materialized`) MUST receive `external_task_criteria_file`, MUST
107
+ validate it before expensive work, and MUST record `TaskSpec.provenance()` under `inputs.task` and
108
+ `TaskSpec.semantics_digest()` as the manifest `semantics_digest`.
109
+ - The predicates bound to the task can define the label itself (a task-suite layer defining, say, `aki`).
110
+ Such a file MUST reach only task-conditioned commands, never a command that builds algorithm inputs:
111
+ `preprocess_data` receives dataset-level predicates only. Through SLURM the two are `PREDICATES_FILE` and
112
+ `TASK_PREDICATES_FILE`. Through MEDS-DEV there is no conflict, because its task YAMLs carry their label
113
+ predicates and `{predicates_path}` holds dataset facts only.
114
+ - Those commands receive the task index through `external_index_dir` for its keys only. They MUST read it
115
+ through `meds_algorithm.read_index`, or an equivalent that never selects a label column.
116
+ - Through MEDS-DEV, the criteria path reaches `algorithms/<profile>/model.yaml` as `{task_criteria_path}` (MEDS-DEV PR #330):
117
+ the `task_criteria_path` argument, else the registered criteria file of `task_name`. MEDS-DEV forwards
118
+ the original task; relaxing it is the algorithm's job.
119
+
120
+ `predict_only` MAY accept the same argument as provenance without requiring it. Other non-zero-shot
121
+ commands reject it. Only zero-shot prediction commands bind `semantics_digest` to a task.
122
+
123
+ ## 3. Commands and algorithm profiles
124
+
125
+ A repository registers the union of its declared profiles once. Only `preprocess_data` and `pretrain`
126
+ are shared step names. Every other step has a distinct meaning and name. Unsupported steps fail; the
127
+ old `infer`, `supervised_train`, and `predict` names are not aliases.
128
+
129
+ | Profile | Commands in dependency order |
130
+ |---|---|
131
+ | `supervised` | `preprocess_data → train_supervised → predict_supervised` |
132
+ | `finetune` | `preprocess_data → pretrain → train_finetune → predict_finetune` |
133
+ | `probe` | `preprocess_data → pretrain → infer_embeddings → train_probe → predict_probe` |
134
+ | `zero_shot_direct` | `preprocess_data → pretrain → predict_zero_shot_direct` |
135
+ | `zero_shot_materialized` | `preprocess_data → pretrain → infer_trajectories → predict_zero_shot_materialized` |
136
+ | `predict_only` | `preprocess_data → predict_only` |
137
+
138
+ The CLI is `meds-algorithm <step> --profile <profile> key=value ...`. `meds-algorithm profiles` lists declared
139
+ profiles; `meds-algorithm commands --profile <profile>` lists their steps in dependency order. Profile
140
+ selection is mandatory when more than one is declared, including for shared commands. The dispatcher
141
+ MUST reject a step belonging to another profile before invoking any algorithm code.
142
+
143
+ `predict_only` preserves the former packaged lifecycle: preprocessing followed by prediction, with no
144
+ training command. Fixed published algorithms and in-context learners can both use it. In-context examples
145
+ MUST come only from explicitly declared train/tuning splits disjoint from evaluated splits; there is no
146
+ persistent task-head training hidden in this profile.
147
+
148
+ The required Python boundary consists of `Command`, `CommandName`, `ArtifactType`, `ArtifactInput`,
149
+ `StepSpec`, `STEP_SPECS`, and `LegacyAlgorithmRepository`. Their signatures and the detailed per-step software
150
+ requirements are specified in section 9 below. Implementations MUST meet
151
+ those requirements even when their backend is an external process. Framework internals are algorithm-owned.
152
+
153
+ ### 3.1 Zero-shot prediction
154
+
155
+ A zero-shot algorithm maps *(task specification, label-blind index, MEDS dataset)* to *predictions* with no
156
+ task-specific training. The two zero-shot profiles differ in **how the algorithm answers**, not in how the
157
+ code is organized:
158
+
159
+ | Profile | The algorithm... | Materialization | Prediction | Examples |
160
+ |---|---|---|---|---|
161
+ | `zero_shot_direct` | answers a query derived from the task directly (a probability, hazard, or score per query) | none | translates the task into the algorithm's queries, runs the algorithm, decodes probabilities | MOTOR (time-to-event hazards), EveryQuery (code-in-window queries) |
162
+ | `zero_shot_materialized` | generates (*materializes*) possible futures, and the task is evaluated on them | task-independent: futures at the index's prediction times, reusable across tasks | applies the task to the materialized futures (e.g. ACES over each sampled trajectory) and aggregates | autoregressive EHR generators |
163
+
164
+ In `zero_shot_materialized`, `infer_trajectories` never receives the task and records `semantics_digest: null`. Its `predict_zero_shot_materialized`
165
+ receives `external_meds_dir`, because the task is evaluated on the real history plus each generated future. Its
166
+ output serves every task over the same index. Only the prediction step is task-conditioned. A materialized algorithm
167
+ can usually evaluate the task exactly, past criteria included, because ACES runs on the real history plus
168
+ the generated future. Its ledger then lists only the sampling approximation. A direct algorithm reduces the
169
+ task to its query language, and its ledger lists every reduction. Intermediate payloads are
170
+ implementation-defined, as in section 4.
171
+
172
+ **Relaxations.** Most zero-shot algorithms cannot answer an arbitrary ACES task exactly; they answer a related
173
+ question their query language can express. Every zero-shot run MUST therefore publish `relaxations.yaml`
174
+ (`meds_algorithm.RelaxationLedger`) in a task-conditioned artifact. Three fields are required:
175
+
176
+ ```yaml
177
+ query_language: MOTOR first-occurrence time-to-event over task-head codes # required
178
+ decoded_quantity: P(any of {MEDS_DEATH} first occurs before the first of {ICU_DISCHARGE, MEDS_DEATH}) # required
179
+ exact: false # required
180
+ steps: # free-form, recommended
181
+ - {name: normalize, kind: label_preserving, direction: exact, detail: re-rooted at the prediction time}
182
+ - {name: label_only, kind: algorithm_assumption, direction: unknown, detail: dropped 2 criteria branches}
183
+ ```
184
+
185
+ `exact: true` is a claim that the decoded quantity is the requested label's probability under the algorithm;
186
+ an exact algorithm lists no steps. Each entry of `steps` describes one rewrite or assumption; its keys are not
187
+ validated, and the implementation guide recommends a vocabulary for them. A algorithm MUST raise rather than
188
+ answer a task it cannot pose (for example, a direct algorithm given a task whose label is not future-only):
189
+ the command fails and writes no predictions. A algorithm MUST NOT omit a step because it is expected to be
190
+ small.
191
+
192
+ ## 4. Intermediate artifacts
193
+
194
+ Every artifact directory MUST contain `manifest.yaml`, written last in staging before publication. Required
195
+ envelope fields are:
196
+
197
+ ```yaml
198
+ contract_version: "4"
199
+ artifact:
200
+ type: patients | pretrained_algorithm | embeddings | trajectories | supervised_algorithm | finetuned_algorithm | probe_algorithm | predictions
201
+ payload_format: implementation-defined/version
202
+ producer: importable-or-external-implementation-id
203
+ command:
204
+ name: <one exact CommandName from the selected profile>
205
+ config_digest: sha256:... # digest_config(arguments + resolved algorithm-owned settings)
206
+ code:
207
+ git_commit: <sha> | null # null outside a git checkout
208
+ dirty: true | false | null # whether the working tree had uncommitted changes
209
+ inputs: {}
210
+ semantics_digest: null | sha256:... # TaskSpec digest for task-conditioned artifacts (section 2.4)
211
+ resources: {}
212
+ ```
213
+
214
+ `config_digest` is `meds_algorithm.digest_config` over the command's arguments plus any algorithm-owned
215
+ settings it resolved (defaults, configuration files), so two artifacts made with the same configuration
216
+ share it. `code` is recorded by `measured_artifact(..., repo=algorithm_root)` from the explicitly supplied algorithm
217
+ checkout. The core installation path and current directory MUST NOT be used to infer algorithm identity.
218
+ Without a checkout, revision and dirty state remain null; deployed algorithms must retain their build provenance. Inputs MUST include stable identifiers or digests, including an external predicates
219
+ digest when the command consumes one. Consumers MUST reject incompatible artifact type, contract version,
220
+ or payload format before doing expensive work.
221
+
222
+ Each artifact is its own directory. New outputs are published by one rename from unique staging.
223
+ Overwriting an existing output MUST preserve or restore the previous artifact if publication fails.
224
+ The core serializes cooperating writers with an exclusive destination lock and retains an old backup
225
+ until replacement succeeds. Its two-rename overwrite can briefly hide the destination from readers;
226
+ use immutable output paths when concurrent readers require uninterrupted access. A process crash can
227
+ leave a lock/staging/backup for explicit recovery; this is not a power-loss transaction. Artifacts MUST NOT
228
+ be nested inside one another: `infer_embeddings` writes to `output_embeddings_dir` and `infer_trajectories` to `output_trajectories_dir`, a sibling of the data and
229
+ predictions directories, never to a subdirectory of the shared `patients` artifact, so runs over different
230
+ indexes or splits cannot overwrite each other. In a MEDS-DEV run directory the layout is `data/`,
231
+ `pretrained/`, `algorithm/`, `embeddings/` or `trajectories/`, `predictions/`, next to MEDS-DEV's own files.
232
+
233
+ ## 5. Final predictions
234
+
235
+ Predictions MUST conform to the prediction schema of the meds-evaluation version MEDS-DEV evaluates with
236
+ (currently `meds-evaluation==0.0.3`, `validate_binary_classification_schema`). MEDS-DEV evaluates every
237
+ `**/*.parquet` under the predictions directory, so every parquet file in the predictions artifact MUST be a
238
+ predictions file; supporting tables (for example a zero-shot query bundle) use another format, such as
239
+ Arrow IPC. Predictions MUST contain exactly one
240
+ row for every requested `(subject_id, prediction_time)` unless an explicitly declared exclusion policy
241
+ permits otherwise. Keys MUST be unique. Row order is not meaningful and MUST never be used to associate
242
+ outputs with keys. Extra output columns are permitted only when documented by the selected task/output
243
+ kind. Conformance tests MUST compare actual prediction keys directly with the requested evaluation index;
244
+ self-reported coverage counts are not proof of coverage. Manifests MAY include coverage counts as diagnostic
245
+ metadata. Any exclusion policy MUST identify its excluded keys so tests can validate the resulting index.
246
+
247
+ Zero-shot predictions are evaluated against the labels of the *requested* task, never the relaxed one: the
248
+ relaxation is part of the algorithm. The prediction artifact MUST carry or reference the `relaxations.yaml` that
249
+ produced it, so a reader can tell algorithm error from relaxation error.
250
+
251
+ ## 6. Measurements
252
+
253
+ Every command invocation MUST record measurements, whether run locally, in tests, through MEDS-DEV, or on
254
+ SLURM:
255
+
256
+ ```yaml
257
+ resources:
258
+ wall_seconds: 1.25
259
+ memory:
260
+ peak_process_tree_pss_bytes: 123456
261
+ peak_process_tree_rss_bytes: 234567
262
+ sampling_interval_seconds: 0.25
263
+ source: linux_proc_smaps_rollup
264
+ includes_descendants: true
265
+ gpu:
266
+ peak_allocated_bytes: null
267
+ peak_reserved_bytes: null
268
+ source: null
269
+ measurement:
270
+ schema_version: "1"
271
+ ```
272
+
273
+ Wall time uses a monotonic clock. On Linux, memory is sampled over the live process tree and PSS is the
274
+ primary comparison value because it apportions shared pages. RSS is retained as a diagnostic. Scheduler
275
+ `MaxRSS`, when available, is a separate value with different semantics. `null` means not measured or not
276
+ applicable; zero means measured with no allocation. Manifests MAY also report meaningful work units such
277
+ as subjects, events, rows, timepoints, or optimizer steps. Resource values are observations, never
278
+ portable pass/fail thresholds.
279
+
280
+ ## 7. Required repository interfaces
281
+
282
+ - Every declared profile MUST have `algorithms/<profile>/model.yaml`, valid for MEDS-DEV. Its external
283
+ `unsupervised`/`supervised` and `train`/`predict` slots are unchanged; each command selects its profile.
284
+ Registration uses `<algorithm-name>__<profile>` so separate descriptors coexist in MEDS-DEV.
285
+ - `tests/test_end_to_end.py` MUST execute each declared profile independently and validate final
286
+ prediction keys, schema, manifests, and resource blocks. A stub MAY skip explicitly during initial
287
+ implementation; a completed port MUST have zero stub skips.
288
+ - The algorithm-owned end-to-end adapters MUST identify the labels/evaluation index and evaluated splits so
289
+ generic tests can compare requested and written prediction keys directly. `run_e2e` MUST also return the
290
+ external inputs and integer fixture seed in `E2EResult.inputs`, and MUST apply
291
+ `meds_algorithm.conformance.signal.add_split_canaries` to its fixture and
292
+ return the canaries. `fitted_codes` MUST report every code the algorithm fitted a population statistic for,
293
+ read from its artifacts.
294
+ - `meds-algorithm-add-to-meds-dev` (`meds_algorithm.integrations.meds_dev.register`) installs `algorithms/<profile>/model.yaml` and
295
+ `requirements.txt`, with the repository as an editable requirement, into a MEDS-DEV checkout. A
296
+ top-level `predicates.yaml` is copied as well when the repository has one. MEDS-DEV's own dataset
297
+ predicates remain the default (section 2.3).
298
+ - `slurm/submit.sh --dry-run` MUST print the algorithm-profile submission without mutation. `slurm/submit.sh`
299
+ MUST submit the selected algorithm profile with dependencies, per-stage resources, durable results, and no interactive-shell
300
+ dependency.
301
+ - Cluster runs MUST record the exact Git commit, dirty state, environment, machine, scheduler job id, and
302
+ references to command manifests.
303
+ - GitHub synchronization MUST use a run branch, never push results directly to the default branch, commit
304
+ structured specifications/results/manifests rather than data, checkpoints, credentials, or raw logs,
305
+ and leave results recoverable on disk if authentication or push fails.
306
+ - `tests/test_meds_dev_e2e.py` and `tests/test_mimic_demo_e2e.py` MUST exercise the completed algorithm through
307
+ MEDS-DEV and the MIMIC-IV demo respectively. Both skip only the profiles in `STUB_PROFILES`. The MIMIC
308
+ test runs the full algorithm profile on MEDS-DEV's `mortality/in_icu/first_24h` task and validates only standard
309
+ boundaries; preprocessing payloads remain opaque.
310
+
311
+ ## 8. Conformance
312
+
313
+ The generated repository MUST test command discovery, selected-algorithm-profile consistency, artifact compatibility,
314
+ publication and rollback, predicate provenance, prediction key coverage/uniqueness, absence of label
315
+ leakage and of transductive fitting (section 2.1), resource measurement presence, `algorithms/<profile>/model.yaml`, SLURM
316
+ dry-run, and its real end-to-end chain. The rendered `tests/test_end_to_end.py` checks the original run and a fresh rerun:
317
+
318
+ - **Label leakage:** the complete lifecycle runs again with evaluated outcome columns removed (all
319
+ outcomes for zero-shot profiles). Each external input root is copied separately, preserving permitted
320
+ train/tuning outcomes. Both runs MUST cover every key, and their probabilities MUST agree by key
321
+ (`rtol=1e-6`, `atol=1e-8`); optional Boolean decisions agree exactly. `run_e2e` MUST honor and record
322
+ the integer `seed` override, seed every RNG/framework/subprocess and use deterministic fixture settings.
323
+ `attach_ground_truth` returns predictions unchanged on an unlabelled index. This tests outcome
324
+ independence on the exercised fixture, not every possible code path.
325
+ - **Prediction validity:** validate every Parquet shard in both runs, including unique/non-null keys and
326
+ finite probabilities in [0, 1]. No particular shard filename is required.
327
+ - **Transductive fitting:** every tuning and held-out subject carries a canary code that occurs in no
328
+ training subject. None of the canaries may appear in `fitted_codes`.
329
+ - **Configuration:** every manifest carries a `config_digest`.
330
+
331
+ A zero-shot repository additionally tests that task-conditioned manifests carry the task's semantics
332
+ digest and that a valid `relaxations.yaml` is published.
333
+ MIMIC-IV demo conformance is opt-in through a `real_data` pytest marker; a normal test run MUST NOT
334
+ download it.
335
+
336
+ ## 9. Software interfaces
337
+
338
+ ### Public classes and ownership
339
+
340
+ New implementations use the typed algorithm API. Each profile class owns its ordered lifecycle and
341
+ abstract typed method signatures. Each request class owns its fields, CLI names, output argument and
342
+ result artifact type. Existing `ALGORITHM_PROFILES` and `STEP_SPECS` are derived read-only views, not another
343
+ hand-maintained specification.
344
+
345
+ | Symbol | Responsibility |
346
+ |---|---|
347
+ | `Algorithm` | Common algorithm identity/configuration, abstract `preprocess_data`, compatibility hook. |
348
+ | `SupervisedAlgorithm`, `FinetuneAlgorithm`, `ProbeAlgorithm`, `ZeroShotDirectAlgorithm`, `ZeroShotMaterializedAlgorithm`, `PredictOnlyAlgorithm` | Require exactly the operations of each profile. Pretrained profiles share `PretrainedAlgorithm.pretrain`. |
349
+ | `AlgorithmConfig` | Base for a algorithm-owned frozen dataclass: typed fields, defaults, `validate`, strict serialization. |
350
+ | `ExecutionOptions` | Nonnegative integer seed and boolean overwrite policy. Algorithms apply the seed to their RNGs. |
351
+ | `StepRequest` subclasses | Frozen, typed inputs plus output destination and execution options for each operation. |
352
+ | `ArtifactReference` subclasses | Lazy references to prepared data, pretrained/fitted algorithms, embeddings, trajectories and predictions. |
353
+ | `PredictionIndex`, `TrainingLabels`, `PredictionContext` | Distinct key-only, train/tuning target and explicit disjoint-context boundaries. |
354
+ | `ArtifactDestination`, `PayloadMetadata` | Staging destination supplied to algorithm code; algorithm-returned payload format/version. |
355
+ | `AlgorithmRepository` | Profile-to-concrete-class association; fresh instance per dispatch, no duplicate step registry. |
356
+ | `StepRunner` | Validate, call compatibility hook, stage/measure algorithm work, validate predictions, attach optional truth, save configuration/request and publish. Returns a typed artifact. |
357
+ | `TaskSpec`, `RelaxationLedger` | Bind task-conditioned predictions and record semantic approximations. |
358
+
359
+ Concrete algorithm methods MUST write payloads only to the staging destination supplied by the runner and
360
+ return `PayloadMetadata`. The runner alone writes `manifest.yaml`, `config.json`, `request.json` and
361
+ publishes the destination. Adapters and the CLI MUST use the same runner path; directly calling a algorithm
362
+ method bypasses validation and publication. Algorithm methods MUST NOT emit `boolean_value`; evaluation
363
+ truth is attached by the runner after all scores have been validated, using a runner-only label path.
364
+ Zero-shot algorithm methods write `relaxations.yaml` at the prediction artifact root.
365
+
366
+ `validate_inputs(request)` is the algorithm's explicit compatibility hook, invoked before algorithm computation.
367
+ Its default rejects fitted-algorithm inputs with a different algorithm name or resolved configuration.
368
+ Extend it for vocabulary, payload format, shape and scientific compatibility. A deliberate override for
369
+ external pretrained algorithms or compatible configurations MUST be documented and tested. Existing
370
+ artifact-type checks cannot establish these scientific properties automatically.
371
+
372
+ Algorithm configuration MUST be a frozen `AlgorithmConfig` dataclass with algorithm-owned fields and validation.
373
+ Use immutable nested dataclasses/tuples, supported scalar types and paths. Reject unknown settings,
374
+ wrong types, invalid values and unsupported serialized objects. Persist all resolved defaults, schema
375
+ version and concrete configuration type. A serialized request/configuration MUST round-trip through its
376
+ explicit class; loaders MUST NOT import a class named by a document. Relative paths in configuration
377
+ files resolve against that file's parent. Methods reopen persisted inputs and MUST NOT depend on prior
378
+ calls on the same Python object. Algorithm tests MUST exercise fresh-process reconstruction and execution.
379
+
380
+ Neural algorithms MAY extend `NNConfig`, a child of `AlgorithmConfig`, for shared `capacity`,
381
+ `learning_rate`, `batch_size`, `weight_decay` and `dropout` settings. Capacity is a positive finite multiplier
382
+ relative to a documented reference architecture, defaulting to `1.0`. Implementations MUST document and
383
+ test its mapping to architecture dimensions, rounding and minimums; it is not a measured parameter count
384
+ or a universal linear scaling of parameters/compute. Batch size means
385
+ examples per device per microbatch before accumulation. Learning rate is the base rate before scheduling;
386
+ dropout is a drop probability, and weight decay is the optimizer's decay coefficient. Implementations
387
+ MUST document and test how these settings control the actual architecture/training code. Subclasses that
388
+ override `validate()` MUST call `super().validate()` to preserve inherited constraints. Reference dimensions
389
+ and architecture-specific settings belong in the concrete algorithm configuration.
390
+ The shared configuration does not select training cohorts or implement a scaling-law experiment runner.
391
+
392
+ `Command.run(mapping) -> Path` and `LegacyAlgorithmRepository` provide mapping-based dispatch after an
393
+ explicit naming migration. Their validation is limited to interface shapes and routing; they do not gain the
394
+ typed runner's guarantees automatically. New generated algorithms use typed profile classes. The external
395
+ CLI step names remain unchanged, but contract version 4 renames package imports, entry points, artifact
396
+ types and input/output arguments to algorithm terminology. This does not migrate existing repositories.
397
+
398
+ ### Step arguments and artifacts
399
+
400
+ Every step accepts optional boolean `do_overwrite` (default false), integer `seed` (default 0), and
401
+ `config_file` (a JSON/YAML algorithm-configuration mapping or saved JSON envelope). The typed CLI rejects
402
+ unknown step arguments; algorithm hyperparameters belong in the typed configuration, not arbitrary extra
403
+ step keys. `predict_only` context requires explicit `context_splits` with train and/or tuning. Every step
404
+ has a typed request carrying the arguments below. Dataset predicates may be supplied to
405
+ preprocessing; task predicates may be supplied to zero-shot prediction. Inference and prediction require
406
+ an explicit nonempty `splits` list. `external_index_dir` identifies the upstream keys-only view; `external_labels_dir` supplies permitted
407
+ training/context outcomes. Optional `external_evaluation_labels_dir` is evaluation-only, after scoring.
408
+
409
+ | Step | Required inputs | Output argument → type | Ground-truth target values the algorithm may use |
410
+ |---|---|---|---|
411
+ | `preprocess_data` | `external_meds_dir` | `output_data_dir` → `patients` | None |
412
+ | `pretrain` | `input_data_dir` | `output_pretrained_algorithm_dir` → `pretrained_algorithm` | None |
413
+ | `train_supervised` | `input_data_dir`, `external_labels_dir` | `output_supervised_algorithm_dir` → `supervised_algorithm` | Training for fitting; tuning for selection |
414
+ | `predict_supervised` | `input_supervised_algorithm_dir`, `input_data_dir`, `external_index_dir`, `splits` | `output_predictions_dir` → `predictions` | None |
415
+ | `train_finetune` | `input_data_dir`, `input_pretrained_algorithm_dir`, `external_labels_dir` | `output_finetuned_algorithm_dir` → `finetuned_algorithm` | Training for fitting; tuning for selection |
416
+ | `predict_finetune` | `input_finetuned_algorithm_dir`, `input_data_dir`, `external_index_dir`, `splits` | `output_predictions_dir` → `predictions` | None |
417
+ | `infer_embeddings` | `input_data_dir`, `input_pretrained_algorithm_dir`, `external_index_dir`, `splits` | `output_embeddings_dir` → `embeddings` | None; read keys only |
418
+ | `train_probe` | `input_embeddings_dir`, `external_labels_dir` | `output_probe_algorithm_dir` → `probe_algorithm` | Training for fitting; tuning for selection |
419
+ | `predict_probe` | `input_probe_algorithm_dir`, `input_embeddings_dir`, `external_index_dir`, `splits` | `output_predictions_dir` → `predictions` | None |
420
+ | `infer_trajectories` | `input_data_dir`, `input_pretrained_algorithm_dir`, `external_index_dir`, `splits` | `output_trajectories_dir` → `trajectories` | None; read keys only |
421
+ | `predict_zero_shot_direct` | `input_data_dir`, `input_pretrained_algorithm_dir`, `external_index_dir`, `external_task_criteria_file`, `splits` | `output_predictions_dir` → `predictions` | None |
422
+ | `predict_zero_shot_materialized` | `input_data_dir`, `input_trajectories_dir`, `external_meds_dir`, `external_index_dir`, `external_task_criteria_file`, `splits` | `output_predictions_dir` → `predictions` | None |
423
+ | `predict_only` | `input_data_dir`, `external_index_dir`, `splits` | `output_predictions_dir` → `predictions` | None for fixed algorithms; explicitly selected disjoint train/tuning context for in-context algorithms |
424
+
425
+ All prediction commands may attach evaluation truth by key **after** probabilities are final. That
426
+ evaluation-only join cannot influence scores, algorithm selection, preprocessing, or query generation.
427
+
428
+ ### Preprocessing and pretraining
429
+
430
+ `PreprocessData` MUST separate fitting from transformation internally. Fit vocabularies, bins,
431
+ normalization and feature selection on training subjects only; freeze them before transforming tuning
432
+ or held-out subjects. No task labels or label-defining task predicates are inputs to this shared step.
433
+ The artifact records the fitted state, training population identity, code/predicate semantics and
434
+ transformation configuration. Transforming a new dataset must reuse that state, not refit it.
435
+
436
+ `Pretrain` learns a task-independent algorithm from the allowed training population. Self-supervised
437
+ targets derived from permitted training histories are allowed; benchmark task labels are not. Tuning
438
+ histories may support unsupervised early stopping when explicitly documented. Imported weights are
439
+ allowed only as a documented materialization mode with source revision, weight digest and compatibility
440
+ checks; this is not a silent no-op. Store everything needed to reconstruct the algorithm.
441
+
442
+ ### Training and prediction bundles
443
+
444
+ `TrainSupervised` fits the task algorithm from scratch. `TrainFinetune` initializes from the specified
445
+ pretrained algorithm and explicitly records which parameters are updated or frozen. `TrainProbe` MUST
446
+ leave encoder and preprocessing weights unchanged; only the head and training-fitted head transforms
447
+ may change. All three fit on training labels only and use tuning labels for algorithm selection, never
448
+ for gradient updates or population statistics.
449
+
450
+ Task-algorithm artifacts carry fitted weights/head state, preprocessing/code-map identities, output
451
+ semantics and upstream provenance. They MUST NOT be assumed to contain the patients or evaluated features.
452
+ `PredictSupervised` and `PredictFinetune` explicitly consume `input_data_dir`; `PredictProbe` consumes
453
+ `input_embeddings_dir` and its fitted probe. The probe does not rerun an encoder or require a GPU simply
454
+ to apply its head. Frozen feature materialization belongs to `InferEmbeddings`.
455
+
456
+ Every producer records each upstream artifact as `inputs.<argument>.path` plus its content/compatibility
457
+ identity. MEDS-DEV's previous-stage-only placeholder can use `meds-algorithm-resolve-input` to resolve that
458
+ record into an explicit CLI argument. This checks the envelope type; the consumer must additionally
459
+ validate content, feature schema, vocabulary and algorithm identity. Other orchestrators can pass paths
460
+ directly. No reliance on current working directories or unrecorded global state is permitted.
461
+
462
+ ### Embeddings and trajectories
463
+
464
+ `InferEmbeddings` performs frozen inference. Each representation is associated explicitly with
465
+ `(subject_id, prediction_time)`; row order cannot establish that association. Record encoder and
466
+ preprocessing identities, representation dimension/dtype or opaque schema version, pooling rule and
467
+ temporal cutoff convention. Histories must stop at the prediction boundary; boundary inclusion must
468
+ match the task protocol. Probe training requires train/tuning coverage; probe prediction requires evaluated-key coverage. The
469
+ generated wiring materializes `splits=[train,tuning,held_out]` without reading any label values. Separate
470
+ train and evaluation materializations may instead be supplied explicitly, with the same frozen encoder
471
+ and feature schema. Set `EMBEDDING_SPLITS` for another requested split set.
472
+
473
+ `InferTrajectories` generates futures from each requested history without receiving the ACES task.
474
+ Record key-to-sample mapping, sample identifiers, seed/sampling policy, generation horizon, time units,
475
+ termination/truncation rules, code mapping, and generating-algorithm identity. Reject a later task whose
476
+ requested window cannot be answered by the artifact, or explicitly record the approximation; do not
477
+ silently treat ungenerated time as event-free. An embedding artifact cannot substitute for trajectories.
478
+
479
+ ### Zero-shot and predict-only
480
+
481
+ `PredictZeroShotDirect` validates `TaskSpec`, translates the task to supported algorithm queries, runs the
482
+ algorithm and decodes one probability per key. Publish the query, decoder settings, task digest and
483
+ relaxation ledger. Internal encode/infer/decode methods are not extra CLI stages.
484
+
485
+ `PredictZeroShotMaterialized` validates task and trajectory coverage, evaluates each requested task on
486
+ real history plus the generated futures, and aggregates. Keep the future artifact unchanged. Tasks
487
+ with the same key set can reuse it only when horizon and generation semantics also match.
488
+
489
+ `PredictOnly` is a lifecycle, not a algorithm family: preprocess then predict. Fixed weights/coefficients
490
+ must be supplied and identified. In-context learners explicitly declare context selection and can read
491
+ only train/tuning labels disjoint from evaluated splits. They do not publish or train a persistent
492
+ task-specific algorithm. Optional task criteria may be recorded as provenance.
493
+ `read_context_labels(labels_dir, context_splits, evaluated_splits, evaluation_index_dir=...)`
494
+ checks overlap against the supplied evaluation index; omitting it uses `labels_dir` for a combined layout.
495
+
496
+ ### Additional compatibility requirements
497
+
498
+ Before consuming an artifact, check version 4, expected `ArtifactType`, supported `payload_format`,
499
+ required files, preprocessing/code-map identity and relevant upstream content digests. For task algorithms,
500
+ check task/output identity; for indexed inference, check keys, split selection and temporal cutoff.
501
+ Record the checked identities under manifest `inputs`. A filename/size fingerprint is a layout hint,
502
+ not a content digest. A configuration hash alone does not establish data or weights identity.
503
+
504
+ Shared `preprocess_data` and `pretrain` names do not authorize automatic reuse. Profiles with different
505
+ tokenization, normalization, populations, algorithm configurations or weights require separate artifacts.
506
+ Default SLURM paths include the profile; `SHARED_DIR` is an explicit reuse request that still requires
507
+ the same validation. MEDS-DEV descriptors use independent registered algorithm names.
508
+
509
+ Missing split files, duplicate/conflicting keys, incompatible formats, unsupported tasks and partial
510
+ coverage are errors. Failed commands must not publish success manifests. Output directories are
511
+ siblings, never nested within an upstream artifact or MEDS-DEV's run-control directory.
512
+
513
+ ### Algorithm-owned internal interfaces
514
+
515
+ The following is a recommended class decomposition, not a mandatory framework. Document equivalent
516
+ responsibilities when the upstream algorithm already has suitable classes:
517
+
518
+ ```python
519
+ class Preprocessor:
520
+ def fit(self, training_events, config): ... # returns immutable fitted state
521
+ def transform(self, events, index, state): ... # label-blind, cutoff-aware
522
+
523
+
524
+ class FrozenEncoder:
525
+ def encode(self, features, index): ... # keyed representations, no updates
526
+
527
+
528
+ class TaskTrainer:
529
+ def fit(self, training_features, training_labels, tuning, config): ...
530
+ def save_algorithm(self, fitted, destination): ... # fitted state and upstream identities
531
+
532
+
533
+ class TaskPredictor:
534
+ def load_bundle(self, source): ... # validates compatibility first
535
+ def predict(self, prepared_data_or_embeddings, index): ... # keyed probabilities, never labels
536
+
537
+
538
+ class TrajectoryGenerator:
539
+ def sample(self, histories, index, sampling_config): ...
540
+ ```
541
+
542
+ Keep labels out of `transform`, `encode`, `sample`, and ordinary `predict` method signatures.
543
+ An in-context predictor should have a separate explicit `context` argument. Any types represented by
544
+ ellipses above must be concretely documented in the algorithm repository: key schema, feature schema,
545
+ batching ownership, missing-data semantics, save/load compatibility and exception behavior.
546
+
547
+ ### Completion evidence per profile
548
+
549
+ `PROFILES` and `IMPLEMENTATIONS` declare capability; `STUB_PROFILES` records unfinished implementations.
550
+ Acceptance tests are parametrized over every declared profile. Removing one stub flag must not hide
551
+ another profile's missing implementation. `run_e2e(tmp_path, *, profile, **inputs)` and
552
+ `prepare_meds_dev_run(meds_dev, tmp_path, *, profile)` must honor profile selection and external overrides.
553
+ Report validation checks shared sections and each completed profile even when another profile remains
554
+ unfinished. Only unfinished profile sections and their profile-specific zero-shot sections may retain TODOs.
555
+
556
+ Each completed profile needs full lifecycle, schema/key coverage, manifest/compatibility, temporal
557
+ cutoff, train-only preprocessing, primary behavior and negative-control evidence. Probe tests must
558
+ verify the encoder remains frozen; finetuning tests must verify the declared update policy; trajectory
559
+ tests must verify per-key samples and horizon handling. The template cannot infer these from opaque
560
+ payloads. Implement these algorithm-specific assertions rather than relying only on dispatch tests.
561
+
562
+ The shared `assert_profile_run` conformance helper reruns the full lifecycle through `run_e2e` with
563
+ forbidden label columns removed. It requires fresh artifacts below a new run directory and preserves
564
+ permitted training/context labels. Include `seed`, `external_meds_dir` and all task inputs in `E2EResult.inputs`
565
+ so the check uses the same histories and task. This detects forbidden reads on the exercised path;
566
+ it does not prove all possible code paths, temporal cutoffs, or absence of labels encoded in other
567
+ features. Implement the corresponding algorithm-specific checks as well.