| title | MedDataOps | ||||
|---|---|---|---|---|---|
| emoji | 🩺 | ||||
| colorFrom | blue | ||||
| colorTo | green | ||||
| sdk | docker | ||||
| app_port | 7860 | ||||
| tags |
|
Clinical data engineering RL environment for training agents to clean hospital data and repair production SQL under realistic constraints.
Quick navigation:
- 1. Motivation
- 2. Environment Overview
- 3. Action Space
- 4. Observation Space
- 5. Tasks
- 6. Reward Function
- 7. Quick Start
- 8. API Reference
- 9. Baseline Scores
- 10. Project Structure
- 11. Contributing
- 12. License
Clinical data pipelines are not abstract spreadsheet problems. They drive triage views, medication safety dashboards, and ICU capacity planning. When these pipelines fail, clinicians make decisions on bad information.
MedDataOps exists to train and evaluate agents on the exact failure modes that appear in real hospital analytics:
- messy, heterogeneous, and partially corrupted tabular data
- broken SQL logic in operational reporting queries
- pressure to produce correct answers with limited steps and auditability
The objective is simple: build agents that can safely recover high-integrity analytics from noisy clinical data.
MedDataOps is an episodic, session-scoped environment exposed through an OpenEnv-style HTTP API.
Story setup:
- You are an on-call clinical data engineer.
- A hospital analytics report is wrong.
- You must clean the working dataset and fix the SQL query.
- You submit only when both are correct.
Core characteristics:
- deterministic task reset via optional seed
- explicit action space (clean_data / run_query / fix_query / submit)
- structured observation payloads for agent planning
- reward decomposition for cleaning quality, query quality, efficiency, and step discipline
Local web UI defaults:
- keyboard-first navigation with a skip link and visible focus indicators
- screen-reader status announcements for demo lifecycle states
- sanitized client rendering path (no direct HTML interpolation of API payloads)
- session identifier validation before local persistence
The API accepts action payloads via POST /step.
| action_type | parameters | description | example |
|---|---|---|---|
clean_data |
{"operations": [...]} |
Apply cleaning transforms to the working dataset (normalize strings, type fixes, null handling, dedupe). | {"action_type":"clean_data","parameters":{"operations":[{"operation":"normalize_strings","columns":["drug_name"],"case":"lower"}]}} |
run_query |
{"query":"SELECT ..."} |
Execute SQL against current episode tables to inspect correctness. | {"action_type":"run_query","parameters":{"query":"SELECT ward, COUNT(*) AS n FROM patients GROUP BY ward"}} |
fix_query |
{"query":"SELECT ..."} |
Replace broken SQL with corrected SQL candidate. | {"action_type":"fix_query","parameters":{"query":"WITH x AS (...) SELECT ..."}} |
submit |
{} |
Finalize episode scoring using current cleaned data and current SQL. | {"action_type":"submit","parameters":{}} |
Notes:
- unsupported actions return validation errors
/steprequires an active session initialized by/reset
Observation payload returned by /reset and included in /step response:
| field | type | description |
|---|---|---|
current_dataset_state |
list[object] |
Current snapshot of episode working rows visible to the agent. |
current_sql_query |
string |
Current SQL query under repair/evaluation. |
error_messages |
list[string] |
Validation or execution errors from previous action/query attempt. |
task_description |
string |
Natural-language objective for the current task. |
step_number |
int |
Zero-based step index in the current episode. |
Three benchmark tasks represent increasing operational complexity.
| task_id | difficulty | data_challenge | sql_challenge | max_score |
|---|---|---|---|---|
triage_report |
easy | Ward casing drift, duplicate patients, mixed date formats, null/N/A age values. |
Fix malformed triage aggregation query and return accurate ward counts. | 1.0 |
medication_summary |
medium | Drug-name normalization, dosage parsing, orphan prescription rows, mixed timestamp formats. | Replace invalid cross-join pattern with correct patient-medication join and grouped counts. | 1.0 |
icu_capacity |
hard | Merge heterogeneous schemas across hospital systems, map ward codes, deduplicate occupancy events. | Rewrite expensive correlated-subquery design into set-based CTE aggregation for capacity metrics. | 1.0 |
Per-step reward is decomposed and explicitly reported.
Formula:
Where:
-
$S_{clean} \in [0,1]$ : cell-level cleaning correctness against expected cleaned rows -
$S_{sql} \in [0,1]$ : query result correctness against expected SQL output (exact + partial matching) -
$B_{eff} \in {0, 0.1}$ : efficiency bonus when query plan cost is below threshold -
$P_{step} = -0.02 \times N_{unnecessary}$ : penalty for unnecessary action churn
Reward object fields:
data_clean_scorequery_correct_scoreefficiency_bonusstep_penaltytotal
python -m venv .venv
# Windows PowerShell
.venv\Scripts\Activate.ps1
pip install -r requirements.txtdocker build -t meddataops .
docker run --rm -p 7860:7860 meddataopsImage: https://hub.docker.com/r/lazerai/meddataops
docker pull lazerai/meddataops:latest
docker run --rm -p 7860:7860 lazerai/meddataops:latestPinned tag for the latest validated release:
docker pull lazerai/meddataops:f313e2a
docker run --rm -p 7860:7860 lazerai/meddataops:f313e2aThen open:
- root UI:
http://localhost:7860/ - health:
http://localhost:7860/health
inference.py expects a chat-completions compatible endpoint.
$env:API_BASE_URL = "https://router.huggingface.co/v1"
$env:MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"
$env:HF_TOKEN = "<your_hf_token>"
python inference.pyIf you are on a low-TPM provider tier and see repeated 429 responses, you can tune retries and prompt/output budgets:
$env:MAX_API_RETRIES = "8"
$env:ACTION_TOKEN_BUDGET = "600"
$env:MODEL_MAX_OUTPUT_TOKENS = "180"
python inference.pycurl -s http://localhost:7860/health
curl -s http://localhost:7860/tasks- Start the service with Docker or Python.
- Open
http://localhost:7860/in your browser. - Use the "Run Demo Episode" control to execute reset -> clean_data -> fix_query -> submit.
- Inspect
/stateto confirm final step details.
Serves the landing page (index.html) with task cards, demo runner, and endpoint examples.
Response:
{
"status": "ok",
"version": "1.0.0"
}Response:
{
"tasks": [
{
"id": "triage_report",
"name": "Morning Triage Report",
"difficulty": "easy",
"description": "The morning triage report is broken...",
"hints": ["Standardize ward strings before grouping."],
"dirty_row_count": 500,
"has_expected_sql": true
}
]
}Request:
{
"task_id": "triage_report",
"seed": 101
}Response headers:
X-Session-Id: <uuid>Set-Cookie: session_id=<uuid>; HttpOnly; SameSite=Lax
Response body (observation):
{
"current_dataset_state": [{"patient_id": "P100001", "ward": "icu"}],
"current_sql_query": "SELECT ward, COUNT(*) as patient_count FROM patients ...",
"error_messages": [],
"task_description": "The morning triage report is broken...",
"step_number": 0
}Request:
{
"action_type": "fix_query",
"parameters": {
"query": "SELECT ward, COUNT(*) AS patient_count FROM patients GROUP BY ward"
}
}Header required (or cookie from /reset):
X-Session-Id: <uuid>
Response:
{
"observation": {
"current_dataset_state": [{"patient_id": "P100001", "ward": "ICU"}],
"current_sql_query": "SELECT ward, COUNT(*) AS patient_count FROM patients GROUP BY ward",
"error_messages": [],
"task_description": "...",
"step_number": 1
},
"reward": {"value": 0.0},
"done": false,
"info": {"action_type": "fix_query", "query_valid": true}
}Header required:
X-Session-Id: <uuid>
Response:
{
"done": false,
"step_number": 1,
"max_steps": 20,
"task": {
"id": "triage_report",
"name": "Morning Triage Report",
"difficulty": "easy",
"description": "...",
"hints": ["..."]
},
"observation": {"current_dataset_state": [], "current_sql_query": "..."},
"latest_reward": {"value": 0.0},
"last_info": {}
}Measured baseline from inference.py:
| task | model | score | steps | status |
|---|---|---|---|---|
| triage_report | llama-3.1-8b-instant | 0.6000 | 20 | ok |
| medication_summary | llama-3.1-8b-instant | 0.0000 | 20 | ok |
| icu_capacity | llama-3.1-8b-instant | 0.0000 | 3 | ok |
A 0.0000 score with status=ok indicates model-capability limits (not environment/runtime failure); the deterministic reference solver below validates that the benchmark pipeline itself is functioning.
Scores measured on 2026-04-04 against lazerai-meddataops.hf.space using an 8B-class model (llama-3.1-8b-instant, equivalent scale to meta-llama/Llama-3.1-8B-Instruct). Medium and hard tasks exceeded this model's capability at default settings. Larger models (70B+) are expected to score meaningfully on all three tasks. Phase 2 evaluation uses a standard frontier model.
Run note: this benchmark completed without llm_failed status. Free-tier TPM limits can still introduce multiple 429 retries and increase runtime.
Reference Solver (deterministic):
| task | solver | score | steps | status |
|---|---|---|---|---|
| triage_report | reference_solver | 1.0000 | 2 | ok |
| medication_summary | reference_solver | 1.0000 | 3 | ok |
| icu_capacity | reference_solver | 0.6343 | 2 | ok |
These values come from reference_solver.py against the live Space (https://lazerai-meddataops.hf.space).
MedDataOps/
├─ Dockerfile
├─ docker-compose.yml
├─ entrypoint.sh
├─ README.md
├─ index.html
├─ inference.py
├─ openenv.yaml
├─ requirements.txt
├─ scripts/
│ ├─ api_server.py
│ ├─ run_env.py
│ └─ seed_db.py
└─ src/
└─ meddataops/
├─ __init__.py
├─ config.py
├─ data_cleaning.py
├─ db.py
├─ env.py
├─ models.py
├─ scoring.py
├─ sql_query.py
├─ sql/
│ ├─ schema.sql
│ └─ seed.sql
└─ tasks/
├─ __init__.py
├─ triage_report.py
├─ medication_summary.py
├─ icu_capacity.py
├─ easy.py
├─ medium.py
└─ hard.py
We welcome contributions that improve realism, reliability, and evaluation rigor.
Recommended workflow:
- Open an issue describing the clinical/technical gap.
- Implement with tests and deterministic seed behavior.
- Run local API smoke checks (
/health,/tasks,/reset,/step,/state). - Submit a PR with before/after behavior and benchmark impact.
Contribution priorities:
- richer clinical noise patterns and schema drift cases
- stronger reward calibration and error attribution
- additional benchmark tasks and baseline trajectories
This project is released under the MIT License. See LICENSE for full terms.