Fully manual example: setup to predictions¶
This walkthrough generates a repository, implements its three supervised methods by hand, creates synthetic inputs, and runs preprocessing → training → prediction locally. No coding agent, MEDS-DEV installation, clinical dataset, GPU or cluster is needed. All commands appear in execution order below.
The example is a constant-prevalence baseline, not a clinical algorithm reproduction. It learns one smoothed probability from the training labels and emits it for every requested held-out key. It copies events during preprocessing but does not use event features to predict. Additional predicates are included to demonstrate composition and provenance, not to claim feature-dependent behavior.
Setup and expected result¶
Use Bash on Linux/macOS or inside WSL on Windows, with Git and uv installed and network access for
Python/packages. Run the commands in a new workspace; the script refuses an existing directory.
uv python install 3.11 installs the tutorial's Python, and uv sync --extra test installs the generated
package, core, Polars and the conformance dependencies into its .venv. No global Python environment
is modified. Copier and the template revision are pinned so the tutorial can be reproduced.
Run the numbered sections below in order, in the same Bash session. Alternatively, expand
All commands at the end to copy the complete script, or download the
same script and run
bash manual-supervised.sh /absolute/path/to/a/new/tutorial-directory.
The generated package is manual_baseline, its class is ManualBaseline, and its named command is
manual-baseline. meds-algorithm is an equivalent entry point. Each lifecycle command starts a fresh
process, using the same config_file. There are three training outcomes (true, true, false) and a
smoothing value of 1, so the resulting probability is (2 + 1) / (3 + 2) = 0.6 for both held-out rows.
1. Create the workspace and generate the repository¶
Choose a new directory and render the supervised profile. These commands create manual-baseline/
and change into it; all subsequent paths are relative to that generated repository.
# A complete local tutorial. Prerequisites: Bash, Git, uv, and network access for dependencies.
set -euo pipefail
command -v git >/dev/null
command -v uv >/dev/null
# Use a NEW directory. Nothing is deleted or overwritten outside this tutorial.
WORKSPACE="${1:-$PWD/manual-meds-example}"
if [[ -e "$WORKSPACE" ]]; then
echo "Choose a new directory: $WORKSPACE already exists" >&2
exit 1
fi
mkdir -p "$WORKSPACE"
cd "$WORKSPACE"
WORKSPACE="$PWD"
# 1. Render a named, single-profile repository from a reproducible template revision.
cat > answers.yaml <<'YAML'
algorithm_name: Manual baseline
algorithm_slug: manual_baseline
algorithm_class: ManualBaseline
algorithm_description: Synthetic tutorial estimating training-label prevalence.
profiles: [supervised]
uses_predicates: true
implementation_source: This tutorial; not a reproduction of a published clinical algorithm.
YAML
uvx --from 'copier==9.17.1' copier copy --defaults --trust \
--vcs-ref=55ddd290d20a39dd19954321580c616cc11a74ae \
--data-file answers.yaml \
https://github.com/McDermottHealthAI/MEDS_algorithm_template.git manual-baseline
cd manual-baseline
2. Install the environment¶
Install Python and the generated package, then inspect its profiles, commands and installed contract.
# 2. Create the Python environment and install the generated package and test dependencies.
uv python install 3.11
uv sync --python 3.11 --extra test
uv run manual-baseline profiles
uv run manual-baseline commands --profile supervised
uv run meds-algorithm-spec > installed-contract.txt
3. Implement the algorithm¶
The code-writing commands below intentionally replace the generated tutorial stubs. The template alone
does not contain an implemented algorithm. Leave its completion gate in place: this short tutorial does
not implement tests/e2e.py, the full implementation report, or the MEDS-DEV/demo acceptance tiers.
The final pytest command runs only the generated contract and operations checks; its success does not
mean a production port has passed the complete workflow.
Typed configuration¶
Define the smoothing parameter and validate it when configuration is loaded.
cat > src/manual_baseline/config.py <<'PY'
from dataclasses import dataclass
from meds_algorithm import AlgorithmConfig
@dataclass(frozen=True)
class ManualBaselineConfig(AlgorithmConfig):
smoothing: float = 1.0
def validate(self):
if self.smoothing <= 0:
raise ValueError("smoothing must be positive")
PY
Algorithm class¶
Implement preprocessing, supervised training and prediction. The learned probability is saved to disk so prediction can run in a separate process.
cat > src/manual_baseline/manual_baseline.py <<'PY'
import json
from pathlib import Path
import polars as pl
import yaml
from meds_algorithm import AlgorithmRepository, PayloadMetadata, SupervisedAlgorithm
from meds_algorithm.requests import (
PreprocessDataRequest, TrainSupervisedRequest, PredictSupervisedRequest,
)
from .config import ManualBaselineConfig
class ManualBaseline(SupervisedAlgorithm):
name = "manual_baseline"
config_type = ManualBaselineConfig
def preprocess_data(self, request: PreprocessDataRequest) -> PayloadMetadata:
events = pl.read_parquet(str(request.meds_dir / "data" / "*.parquet"))
events.write_parquet(request.output.directory / "events.parquet")
# Core supplies the composed feature predicates; keep its reserved file unchanged.
predicates = yaml.safe_load(request.predicates_file.read_text())["predicates"]
(request.output.directory / "summary.json").write_text(json.dumps({
"events": events.height, "predicate_names": sorted(predicates),
}))
return PayloadMetadata("tutorial-events")
def train_supervised(self, request: TrainSupervisedRequest) -> PayloadMetadata:
outcomes = request.labels.read("train")["boolean_value"]
if len(outcomes) == 0:
raise ValueError("Training labels must not be empty")
alpha = self.config.smoothing
probability = (outcomes.sum() + alpha) / (len(outcomes) + 2 * alpha)
(request.output.directory / "probability.json").write_text(json.dumps(probability))
return PayloadMetadata("tutorial-prevalence")
def predict_supervised(self, request: PredictSupervisedRequest) -> PayloadMetadata:
probability = json.loads((request.fitted_algorithm.directory / "probability.json").read_text())
request.index.read().with_columns(
pl.lit(probability).alias("predicted_boolean_probability")
).write_parquet(request.output.directory / "predictions.parquet")
return PayloadMetadata("tutorial-predictions")
IMPLEMENTATIONS = {"supervised": ManualBaseline}
PROFILES = tuple(IMPLEMENTATIONS)
# Keep the completion gate: this tutorial does not implement the full conformance adapters/report.
STUB_PROFILES = set(PROFILES)
def create_repository():
return AlgorithmRepository(IMPLEMENTATIONS, repo=Path(__file__).resolve().parents[2])
PY
4. Prepare synthetic inputs and dataset settings¶
Create the event table, separate training and held-out label/index files, base and additional predicates, and the dataset configuration that refers to those additions.
# 4. Create tiny synthetic MEDS events and an upstream label/index bundle.
# Real work should reuse a reviewed task bundle, not derive labels inside the algorithm.
mkdir -p inputs/meds/data inputs/labels dataset_configs predicates
uv run python - <<'PY'
from datetime import datetime
import polars as pl
pl.DataFrame({
"subject_id": [1, 2, 3, 4, 5],
"time": [datetime(2020, 1, 1)] * 5,
"code": ["DEMO//encounter"] * 5,
"numeric_value": [None] * 5,
}, schema_overrides={"numeric_value": pl.Float64}).write_parquet("inputs/meds/data/events.parquet")
for split, ids, outcomes in (
("train", [1, 2, 3], [True, True, False]),
("held_out", [4, 5], [True, False]),
):
pl.DataFrame({
"subject_id": ids,
"prediction_time": [datetime(2020, 1, 2)] * len(ids),
"boolean_value": outcomes,
}).write_parquet(f"inputs/labels/{split}.parquet")
PY
cat > predicates/base.yaml <<'YAML'
predicates:
encounter:
code: DEMO//encounter
YAML
cat > predicates/demo_additional.yaml <<'YAML'
predicates:
demo_measurement:
code: DEMO//measurement
YAML
cat > dataset_configs/demo.yaml <<'YAML'
smoothing: 1.0
additional_predicates_file: ../predicates/demo_additional.yaml
YAML
5. Run each lifecycle step¶
Set the shared paths once. Each command below then starts its own process.
# 5. Run the complete supervised lifecycle, one independent process per step.
CONFIG="$PWD/dataset_configs/demo.yaml"
MEDS="$PWD/inputs/meds"
LABELS="$PWD/inputs/labels"
BASE_PREDICATES="$PWD/predicates/base.yaml"
ARTIFACTS="$PWD/runs/manual/supervised/artifacts"
Preprocess the data¶
Compose the feature predicates and write the prepared data artifact.
uv run manual-baseline preprocess_data --profile supervised \
"config_file=$CONFIG" "external_meds_dir=$MEDS" \
"external_predicates_file=$BASE_PREDICATES" "output_data_dir=$ARTIFACTS/data"
Train the algorithm¶
Estimate the smoothed probability from the training outcomes and persist it in the algorithm artifact.
uv run manual-baseline train_supervised --profile supervised \
"config_file=$CONFIG" "input_data_dir=$ARTIFACTS/data" \
"external_labels_dir=$LABELS" "output_supervised_algorithm_dir=$ARTIFACTS/algorithm"
Predict on the held-out index¶
Load the saved probability and emit one prediction per requested key. The runner attaches evaluation labels after scoring.
uv run manual-baseline predict_supervised --profile supervised \
"config_file=$CONFIG" "input_data_dir=$ARTIFACTS/data" \
"input_supervised_algorithm_dir=$ARTIFACTS/algorithm" "external_index_dir=$LABELS" \
'splits=[held_out]' "output_predictions_dir=$ARTIFACTS/predictions" \
"external_evaluation_labels_dir=$LABELS"
6. Inspect and verify the outputs¶
Check the two expected probabilities, the prediction schema, artifact metadata and composed predicates. Then run the generated contract and operations checks.
# 6. Inspect and verify the outputs. Only the runner attaches the held-out truth after scoring.
uv run python - <<'PY'
from pathlib import Path
import polars as pl
import yaml
from meds_algorithm import read_manifest
from meds_algorithm.conformance.assertions import assert_prediction_schema
from manual_baseline import ManualBaseline, ManualBaselineConfig
root = Path("runs/manual/supervised/artifacts")
predictions = pl.read_parquet(root / "predictions/predictions.parquet")
assert predictions["subject_id"].to_list() == [4, 5]
assert predictions["predicted_boolean_probability"].to_list() == [0.6, 0.6]
assert predictions["boolean_value"].to_list() == [True, False]
assert_prediction_schema(predictions)
print(predictions)
for stage in ("data", "algorithm", "predictions"):
manifest = read_manifest(root / stage)
assert (root / stage / "config.json").is_file()
assert (root / stage / "request.json").is_file()
print(stage, manifest["command"]["name"], manifest["resources"]["wall_seconds"])
resolved = yaml.safe_load((root / "data/resolved_feature_predicates.yaml").read_text())
assert set(resolved["predicates"]) == {"encounter", "demo_measurement"}
config = ManualBaselineConfig.load(root / "algorithm/config.json")
assert ManualBaseline(config).name == "manual_baseline"
PY
uv run pytest -q tests/test_contract.py tests/test_operations.py
printf 'Tutorial completed in %s\n' "$PWD"
What was produced¶
Under manual-meds-example/manual-baseline/ (or your chosen workspace):
| Path | Contents |
|---|---|
src/manual_baseline/manual_baseline.py |
Concrete algorithm methods and profile association |
src/manual_baseline/config.py |
Named, typed configuration with positive smoothing validation |
dataset_configs/demo.yaml |
Dataset-specific settings and relative additional-predicates path |
inputs/meds/data/, inputs/labels/ |
Synthetic events and the separate label/index bundle |
runs/manual/supervised/artifacts/data/ |
Prepared events, summary, resolved feature predicates and core metadata |
runs/manual/supervised/artifacts/algorithm/ |
Persisted learned probability and core metadata |
runs/manual/supervised/artifacts/predictions/ |
Held-out probabilities, post-score truth and core metadata |
Each artifact has manifest.yaml, config.json and request.json. The manifest records measurements,
input identities and the configuration. Because this example does not create a Git commit for the
generated implementation, its commit provenance may be unavailable; commit reviewed implementation
code before reproducible production runs. Keep input data and artifact payloads out of source control.
The index and evaluation labels happen to share a directory in this example. Prediction reads only
keys through request.index.read(); the runner attaches boolean_value afterward. In real work,
reuse the upstream reviewed task bundle, respect all temporal cutoffs, and do not create labels inside
the algorithm. Additional feature predicates must not redefine the evaluated task.
To rerun, choose fresh output paths. Explicit do_overwrite=true opts into replacing an artifact with
rollback; do not add it automatically to every command. Other profiles have different intermediate
steps: use the profile lifecycle table and
per-step arguments when adapting this example.
All commands¶
This is the complete script used by the sections above. Run it from a fresh workspace instead of running it again in the directory you just created.
All commands — complete runnable script
#!/usr/bin/env bash
# A complete local tutorial. Prerequisites: Bash, Git, uv, and network access for dependencies.
set -euo pipefail
command -v git >/dev/null
command -v uv >/dev/null
# Use a NEW directory. Nothing is deleted or overwritten outside this tutorial.
WORKSPACE="${1:-$PWD/manual-meds-example}"
if [[ -e "$WORKSPACE" ]]; then
echo "Choose a new directory: $WORKSPACE already exists" >&2
exit 1
fi
mkdir -p "$WORKSPACE"
cd "$WORKSPACE"
WORKSPACE="$PWD"
# 1. Render a named, single-profile repository from a reproducible template revision.
cat > answers.yaml <<'YAML'
algorithm_name: Manual baseline
algorithm_slug: manual_baseline
algorithm_class: ManualBaseline
algorithm_description: Synthetic tutorial estimating training-label prevalence.
profiles: [supervised]
uses_predicates: true
implementation_source: This tutorial; not a reproduction of a published clinical algorithm.
YAML
uvx --from 'copier==9.17.1' copier copy --defaults --trust \
--vcs-ref=55ddd290d20a39dd19954321580c616cc11a74ae \
--data-file answers.yaml \
https://github.com/McDermottHealthAI/MEDS_algorithm_template.git manual-baseline
cd manual-baseline
# 2. Create the Python environment and install the generated package and test dependencies.
uv python install 3.11
uv sync --python 3.11 --extra test
uv run manual-baseline profiles
uv run manual-baseline commands --profile supervised
uv run meds-algorithm-spec > installed-contract.txt
# 3. Implement the generated stubs manually. No agent or MEDS-DEV runner is involved.
cat > src/manual_baseline/config.py <<'PY'
from dataclasses import dataclass
from meds_algorithm import AlgorithmConfig
@dataclass(frozen=True)
class ManualBaselineConfig(AlgorithmConfig):
smoothing: float = 1.0
def validate(self):
if self.smoothing <= 0:
raise ValueError("smoothing must be positive")
PY
cat > src/manual_baseline/manual_baseline.py <<'PY'
import json
from pathlib import Path
import polars as pl
import yaml
from meds_algorithm import AlgorithmRepository, PayloadMetadata, SupervisedAlgorithm
from meds_algorithm.requests import (
PreprocessDataRequest, TrainSupervisedRequest, PredictSupervisedRequest,
)
from .config import ManualBaselineConfig
class ManualBaseline(SupervisedAlgorithm):
name = "manual_baseline"
config_type = ManualBaselineConfig
def preprocess_data(self, request: PreprocessDataRequest) -> PayloadMetadata:
events = pl.read_parquet(str(request.meds_dir / "data" / "*.parquet"))
events.write_parquet(request.output.directory / "events.parquet")
# Core supplies the composed feature predicates; keep its reserved file unchanged.
predicates = yaml.safe_load(request.predicates_file.read_text())["predicates"]
(request.output.directory / "summary.json").write_text(json.dumps({
"events": events.height, "predicate_names": sorted(predicates),
}))
return PayloadMetadata("tutorial-events")
def train_supervised(self, request: TrainSupervisedRequest) -> PayloadMetadata:
outcomes = request.labels.read("train")["boolean_value"]
if len(outcomes) == 0:
raise ValueError("Training labels must not be empty")
alpha = self.config.smoothing
probability = (outcomes.sum() + alpha) / (len(outcomes) + 2 * alpha)
(request.output.directory / "probability.json").write_text(json.dumps(probability))
return PayloadMetadata("tutorial-prevalence")
def predict_supervised(self, request: PredictSupervisedRequest) -> PayloadMetadata:
probability = json.loads((request.fitted_algorithm.directory / "probability.json").read_text())
request.index.read().with_columns(
pl.lit(probability).alias("predicted_boolean_probability")
).write_parquet(request.output.directory / "predictions.parquet")
return PayloadMetadata("tutorial-predictions")
IMPLEMENTATIONS = {"supervised": ManualBaseline}
PROFILES = tuple(IMPLEMENTATIONS)
# Keep the completion gate: this tutorial does not implement the full conformance adapters/report.
STUB_PROFILES = set(PROFILES)
def create_repository():
return AlgorithmRepository(IMPLEMENTATIONS, repo=Path(__file__).resolve().parents[2])
PY
# 4. Create tiny synthetic MEDS events and an upstream label/index bundle.
# Real work should reuse a reviewed task bundle, not derive labels inside the algorithm.
mkdir -p inputs/meds/data inputs/labels dataset_configs predicates
uv run python - <<'PY'
from datetime import datetime
import polars as pl
pl.DataFrame({
"subject_id": [1, 2, 3, 4, 5],
"time": [datetime(2020, 1, 1)] * 5,
"code": ["DEMO//encounter"] * 5,
"numeric_value": [None] * 5,
}, schema_overrides={"numeric_value": pl.Float64}).write_parquet("inputs/meds/data/events.parquet")
for split, ids, outcomes in (
("train", [1, 2, 3], [True, True, False]),
("held_out", [4, 5], [True, False]),
):
pl.DataFrame({
"subject_id": ids,
"prediction_time": [datetime(2020, 1, 2)] * len(ids),
"boolean_value": outcomes,
}).write_parquet(f"inputs/labels/{split}.parquet")
PY
cat > predicates/base.yaml <<'YAML'
predicates:
encounter:
code: DEMO//encounter
YAML
cat > predicates/demo_additional.yaml <<'YAML'
predicates:
demo_measurement:
code: DEMO//measurement
YAML
cat > dataset_configs/demo.yaml <<'YAML'
smoothing: 1.0
additional_predicates_file: ../predicates/demo_additional.yaml
YAML
# 5. Run the complete supervised lifecycle, one independent process per step.
CONFIG="$PWD/dataset_configs/demo.yaml"
MEDS="$PWD/inputs/meds"
LABELS="$PWD/inputs/labels"
BASE_PREDICATES="$PWD/predicates/base.yaml"
ARTIFACTS="$PWD/runs/manual/supervised/artifacts"
uv run manual-baseline preprocess_data --profile supervised \
"config_file=$CONFIG" "external_meds_dir=$MEDS" \
"external_predicates_file=$BASE_PREDICATES" "output_data_dir=$ARTIFACTS/data"
uv run manual-baseline train_supervised --profile supervised \
"config_file=$CONFIG" "input_data_dir=$ARTIFACTS/data" \
"external_labels_dir=$LABELS" "output_supervised_algorithm_dir=$ARTIFACTS/algorithm"
uv run manual-baseline predict_supervised --profile supervised \
"config_file=$CONFIG" "input_data_dir=$ARTIFACTS/data" \
"input_supervised_algorithm_dir=$ARTIFACTS/algorithm" "external_index_dir=$LABELS" \
'splits=[held_out]' "output_predictions_dir=$ARTIFACTS/predictions" \
"external_evaluation_labels_dir=$LABELS"
# 6. Inspect and verify the outputs. Only the runner attaches the held-out truth after scoring.
uv run python - <<'PY'
from pathlib import Path
import polars as pl
import yaml
from meds_algorithm import read_manifest
from meds_algorithm.conformance.assertions import assert_prediction_schema
from manual_baseline import ManualBaseline, ManualBaselineConfig
root = Path("runs/manual/supervised/artifacts")
predictions = pl.read_parquet(root / "predictions/predictions.parquet")
assert predictions["subject_id"].to_list() == [4, 5]
assert predictions["predicted_boolean_probability"].to_list() == [0.6, 0.6]
assert predictions["boolean_value"].to_list() == [True, False]
assert_prediction_schema(predictions)
print(predictions)
for stage in ("data", "algorithm", "predictions"):
manifest = read_manifest(root / stage)
assert (root / stage / "config.json").is_file()
assert (root / stage / "request.json").is_file()
print(stage, manifest["command"]["name"], manifest["resources"]["wall_seconds"])
resolved = yaml.safe_load((root / "data/resolved_feature_predicates.yaml").read_text())
assert set(resolved["predicates"]) == {"encounter", "demo_measurement"}
config = ManualBaselineConfig.load(root / "algorithm/config.json")
assert ManualBaseline(config).name == "manual_baseline"
PY
uv run pytest -q tests/test_contract.py tests/test_operations.py
printf 'Tutorial completed in %s\n' "$PWD"