Metadata-Version: 2.1
Name: athio
Version: 0.2.0
Summary: Local clinical research workflows with Dagster and DuckDB
Requires-Python: <3.13,>=3.11
Description-Content-Type: text/markdown
Requires-Dist: dagster<1.10,>=1.9.6
Requires-Dist: dagster-duckdb<0.26,>=0.25.6
Requires-Dist: duckdb<2,>=1.1
Requires-Dist: pandas<3,>=2.2
Requires-Dist: numpy<2,>=1.26
Requires-Dist: pydantic<3,>=2.9
Requires-Dist: httpx<1,>=0.27
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: packaging>=24
Requires-Dist: scikit-learn<1.7,>=1.6
Requires-Dist: catboost<1.3,>=1.2.7
Requires-Dist: onnxruntime<2,>=1.20
Provides-Extra: dev
Requires-Dist: pytest<10,>=8; extra == "dev"
Requires-Dist: build>=1; extra == "dev"
Requires-Dist: ruff>=0.11; extra == "dev"
Provides-Extra: tracking
Requires-Dist: mlflow<3,>=2.21; extra == "tracking"

# Athio

A local Python package for clinical research: prepare data, inspect features, train models,
identify complications, and assess configurable criteria. Dagster assets orchestrate the
work and DuckDB processes the data; neither needs to be configured by a notebook user.

Athio 0.2.0 is the sole supported application entry point: `from athio import AthioClient`.
The old HTTP API, command-line runners and `athio.sdk` facade have been removed. Any future
API or CLI will be an adapter that calls this package. See [architecture and dependencies](docs/architecture.md).

## Installation

Python 3.11 and 3.12 are targeted. Install from a local build today:

```sh
python -m pip install .
```

After the publication directory is deployed to the existing Athio host:

```sh
python -m pip install "athio @ https://www.athio.nl/python/package/files/athio-0.2.0-py3-none-any.whl"
```

The public pip index will be at `https://www.athio.nl/python/package/simple/`.
The direct wheel command selects the exact Athio artifact; dependencies use your configured
pip index. Nothing has been deployed by this repository change.

For runnable examples tailored to your background, see [four user journeys](docs/user-journeys.md).
See the [QA follow-up](docs/qa-followup.md) for current release limitations.

## One client, independent workflows

```python
from athio import AthioClient

client = AthioClient()
data = client.data.from_frames(
    diagproc=diagproc, meas_lab=meas_lab, med_adm=med_adm, memo=memo, ot=ot,
)

tamponade = client.complications.create("tamponade", context="cardiologie")
bloeding = client.complications.create("bloeding", context="cardiologie")

# Explicit choice: train a structured model without making LLM requests.
tamponade.settings.method = "structured"
tamponade.fit(data, labels)
result = tamponade.predict(data)
result.to_pandas()
tamponade.save("./models/tamponade")
```

`client.complications.list()` lists the cardiologie and CTC profiles. Each workflow owns
its settings and fitted model. Compatible data preparation is reused by the client.
`fit()` returns the workflow; `transform()` returns episode identifiers and feature columns.

Labels are a DataFrame with `DBC_Patnum`, `DBC_Traj`, and `target` (0 or 1). Alternatively,
use `TrajId` from `transform()` together with `target`. Every selected episode needs a label;
labels are joined by identifiers, never by incidental row order. Overlapping trajectories
may be combined by the existing curation logic; label the resulting episode identifiers.

The default method is `ensemble`, retaining each profile's memo and structured settings.
It needs an LLM endpoint and, when the profile includes a structured model, a fitted or
loaded model. `rules` runs the profile's selection predicate; it is a distinct research
output, not the final ensemble classification. `structured` uses the fitted model alone.
Ensemble fitting learns structured models and tunes ensemble thresholds from patient-grouped
out-of-fold predictions. `evaluate(data, labels, folds=...)` reports held-out fold metrics;
for ensemble evaluation, threshold tuning is nested within the outer training folds.

## Data and configuration

Keep the existing Athio `DBC_*`, `PROC_*`, `MEAS_*`, `MED_ADM_*`, `MEMO_*`, and `OT_*`
schemas. `diagproc` requires patient, trajectory, and trajectory start-date columns. Optional
tables may be omitted. `DBC_Traj` must identify one patient across the dataset.

```python
data.raw("diagproc")          # independent DataFrame copy
data.curated("traj")         # curated episodes and stable identifiers within this dataset
data.validate()              # input-table row counts; invalid inputs raise ValueError
data = data.select("DBC_Groupcode = 320")

client.data.schemas()         # required columns and full canonical schemas
# UTF-8, named columns; preserves textual codes and leading-zero patient IDs.
data = client.data.from_files(connector="canonical_csv", diagproc="episodes.csv")

# Existing positional CSV conventions and the Antonius adapter are retained.
data = client.data.from_files(connector="csv", diagproc="DBC&PROC.csv", memo="MEMO.csv")
# For Antonius multi-file inputs, supply the existing parameter dictionary:
# memo={"fileLocation": "exports/", "files": ["notes.csv", "radiology.csv"]}

tamponade.settings.features.minimum_importance = 0.1
tamponade.settings.training.iterations = 200
tamponade.settings.memo.trigger_words = ["tampo", "pericard"]
tamponade.save_config("tamponade.json")
tamponade = client.complications.from_config("tamponade.json")
```

Settings are typed and reject unknown fields. Configuration writes are explicit.
Changing feature or cohort settings after fitting requires refitting. Model bundles contain
ordered feature names, preprocessing settings, thresholds, profile versions, and checksummed
model files. Training records and endpoint credentials are not saved.

`AthioClient(workspace="./research")` preserves local run databases. The default workspace
is temporary; use `with AthioClient() as client:` or `client.close()` to release it. Each
client has separate mutable databases, including when clients share a workspace.
`client.clear_cache()` removes processing databases while retaining downloaded bundles.

## Models

```python
# After publishing a newly trained bundle; construction itself does not use the network.
client.models.list()
bundle = client.models.download("cardiologie/tamponade", version="1.0.0")
tamponade = client.complications.load(bundle)
result = tamponade.predict(data)
```

Downloads are checksummed and cached in the workspace. Use a persistent workspace for
reuse across sessions and offline access. Athio 0.2 uses corrected measurement features and
patient-local exposure counts. Historical weights and 0.1 bundles are incompatible and must
be retrained; they are not republished. Supply newly trained model bundles to the release tool.
Until those bundles are supplied, the catalog explicitly lists the profiles as unavailable.
Downloadable weights are not embedded in the Python package.

## LLM endpoints

```python
from athio import LLMSettings

client = AthioClient(llm=LLMSettings(
    provider="ollama", base_url="http://localhost:11434", model="phi4",
))
# Or use an explicitly configured compatible endpoint:
client = AthioClient(llm=LLMSettings(
    provider="openai", base_url="https://your-inference-host/v1",
    model="your-model", api_key="your-key",
))
```

Athio runs locally; LLM requests go to the endpoint you configure. The compatible endpoint
must implement `/chat/completions` with JSON-schema response formatting. Athio validates
responses, uses bounded retries, and reuses identical successful requests within a workflow.
It does not install an inference server or automatically download LLM weights. Each workflow
owns a copy of the client LLM settings; configure `workflow.llm` to change an existing workflow.
Bundles preserve nonsecret LLM settings, including the endpoint and model.

## Linder–Mellhammar and custom criteria

Built-in published LMCI profiles cover abdominal infection, bloodstream infection, pneumonia,
wound/skin-and-soft-tissue infection, and UTI. They include all component rows for these target
categories, fixed classification thresholds, unknown-evidence bounds and bloodstream qualifiers.

```python
workflow = client.criteria.linder_mellhammar(types=["Pneumonie", "UWI"])
workflow.schema()  # criterion IDs, source predicates, row membership and points
assessment = workflow.assess(workflow.extract(data))
assessment.predictions   # per-episode/site scores, score bounds and classification
assessment.criteria      # component scores and supporting predicate states
assessment.evidence      # source observations and quotations
assessment.diagnostics   # failed requests or rejected evidence
workflow.save("./models/lmci")
```

Published criteria do not need training. `predict(evidence)` applies the same fixed assessment;
`evaluate(evidence, labels)` measures a separate hold-out set without learning thresholds.
Missing evidence remains unknown. See [the five LMCI categories and study plan](docs/linder-mellhammar.md)
for the exact source mapping, bloodstream subtypes, outputs and development/hold-out use.

For research adaptations, `client.criteria.create(definition=...)` still accepts custom
`CriteriaDefinition` and `EvidenceField` objects. These generic workflows support evidence
extraction, deterministic scoring and optional ordinal training. Importable structured
extractor functions can be supplied to either workflow through `extractors=[...]`.
Loading a bundle with custom extractors imports their Python modules.
See [custom criteria definitions](docs/criteria.md) for the rule vocabulary.

## Results, examples, and validation

Results expose `predictions`, `criteria`, `evidence`, `diagnostics`, and `metadata`.
`to_pandas()` returns the primary table; `export("results", format="csv")` or `format="json"`
writes all tables and metadata. Complication outputs preserve their existing domain columns;
criteria outputs carry the configurable evidence and scoring details. Coverage failures are included in diagnostics. Structured inference returns
`probability`; ensemble inference returns `structured_probability` for its structured component.
It does not report a calibrated ensemble confidence.

Inference uses each patient's episodes independently of unrelated patients in a batch.
Measurement `_min` and `_max` now mean minimum and maximum. Episode-local ward days, ICU days
and antibiotic-administration counts replace the former cohort-relative pneumonia features.
Changing preprocessing requires newly trained weights and a version-2 model bundle.

Run a self-contained synthetic example without an LLM:

```sh
python scripts/verify_wheel.py
```

Development checks and publication handoff:

```sh
python -m pip install ".[dev]"
python -m pytest tests -q
python -m build
python -m athio.release --dist dist --output publication
```

CI is configured to test Python 3.11/3.12 on Linux, Windows, and macOS and installs the built wheel outside
the checkout. The release workflow produces a static directory for `/python/package/`;
it does not deploy the website. See [publication instructions](docs/publication.md).

Start with the runnable [complication notebook](notebooks/athio_workflows.ipynb) or
[five-category LMCI notebook](notebooks/lmci_assessment.ipynb). Both use synthetic data and
run without an LLM endpoint. Historical configuration and notebooks are archived under
`docs/history/`; they are unsupported and excluded from the package distributions.
