Automated CPT Coding, Registry Extraction, and Synoptic Reporting for Interventional Pulmonology.
This toolkit enables:
Install:
micromamba activate medparse-py311
make install
make preflight
Configure:
Create .env with your GEMINI_API_KEY.
Run:
# Start the API/Dev Server (ATLAS UI by default)
./ops/devserver.sh
UI variants:
# ATLAS UI (default)
./ops/devserver.sh
# ATLAS UI (explicit)
./ops/devserver.sh --ui=atlas
# Classic UI
./ops/devserver.sh --ui=classic
Then open:
http://localhost:8000/ui/http://localhost:8000/ui/workflow.htmlThe UI flow is: paste note -> run PHI detection -> apply redactions -> submit scrubbed note -> review results. Optional: edit values in Flattened Tables (Editable) (generates Edited JSON (Training)) and export JSON/tables.
PDF upload/extraction is browser-local and worker-based (ui/static/phi_redactor/pdf_local/):
workers/pdf.worker.js (pdf.js text layer + text/image region analysis).nativeTextDensity = charCount / pageArea.
NATIVE_DENSE_TEXT) and stay native.workers/ocr.worker.js with:
auto, on, offLidocaine 49% -> Lidocaine 4%Atropine 9.5 mg -> Atropine 0.5 mglyrnphadenopathy -> lymphadenopathyhytnph -> lymphtracheobronchial).Security/ops constraints:
ui/static/phi_redactor/vendor/).http://localhost:8000/ui/reporter_builder.htmlRun Detection -> Apply Redactions -> Seed BundlePOST /report/seed_from_text supports:
REPORTER_SEED_STRATEGY=registry_extract_fields (default)REPORTER_SEED_STRATEGY=llm_findings (reporter-only findings path)REPORTER_SEED_LLM_STRICT=1 to disable fallbackReporter findings mode uses existing OpenAI-compatible settings:
LLM_PROVIDER=openai_compatOPENAI_MODEL_STRUCTURER=gpt-5-miniOPENAI_API_KEY=...OPENAI_OFFLINE=0 (or fallback/strict behavior applies)Generate random prompt/output reporter runs from training JSONL files:
python ops/tools/run_reporter_random_seeds.py \
--input-dir /home/rjm/projects/proc_suite_notes/reporter_training/reporter_training \
--count 20 \
--seed 42 \
--output reporter_tests.txt \
--include-metadata-json
Outputs:
reporter_tests.txtreporter_tests.json (or custom path via --metadata-output)PROCSUITE_ALLOW_ONLINE=1 \
LLM_PROVIDER=openai_compat \
OPENAI_MODEL_STRUCTURER=gpt-5-mini \
python ops/tools/eval_reporter_prompt_llm_findings.py
proc_schemas/shared/ebus_events.py and are re-exported via app/registry/schema/ebus_events.py.app/registry/schema/granular_models.py and logic to app/registry/schema/granular_logic.py; app/registry/schema_granular.py is a compat shim.app/registry/schema/v2_dynamic.py; app/registry/schema.py is now a thin entrypoint preserving the __path__ hack.app/registry/schema/ip_v3_extraction.py with a compatibility re-export at app/registry/schema/ip_v3.py; the rich registry entry schema remains at proc_schemas/registry/ip_v3.py.app/registry/schema/adapters/v3_to_v2.py with a compat shim at app/registry/adapters/v3_to_v2.py.NOTES_SCHEMA_REFACTOR.md and tests/registry/test_schema_refactor_smoke.py.31647 (initial lobe) + 31651 (each additional lobe); valve removal uses 31648 (initial lobe) + 31649 (each additional lobe).31634 is derived only when Chartis is documented; suppressed when Chartis is in the same lobe as valve placement, and flagged for modifier documentation when distinct lobes are present.99152/99153 are derived only when sedation.type="Moderate", anesthesia_provider="Proceduralist", and intraservice minutes ≥10 (computed from start/end if needed).registry.coding_support (rules applied + QA flags) and enriches registry.billing.cpt_codes[] with description, derived_from, and evidence spans.providers_team[] (auto-derived from legacy providers when missing).pathology_results.pdl1_tps_text to preserve values like "<1%" or ">50%".docs/KNOWLEDGE_INVENTORY.md, docs/KNOWLEDGE_RELEASE_CHECKLIST.md, and make validate-knowledge-release for safer knowledge/schema updates."version" (override: PSUITE_KNOWLEDGE_ALLOW_VERSION_MISMATCH=1).master_code_index, and synonym phrase lists are centralized in KB synonyms.POST /report/seed_from_text now supports REPORTER_SEED_STRATEGY=llm_findings (default remains registry_extract_fields).NERToRegistryMapper -> ClinicalGuardrails -> deterministic CPT derivation -> existing Jinja templates.already_scrubbed=true.ops/tools/run_reporter_random_seeds.py and ops/tools/eval_reporter_prompt_llm_findings.py.| Module | Description |
|---|---|
app/api/fastapi_app.py | Main FastAPI backend |
app/coder/ | CPT coding engine with CodingService (8-step pipeline) |
ml/lib/ml_coder/ | ML-based code predictor and training pipeline |
app/registry/ | Registry extraction with RegistryService and RegistryEngine |
app/agents/ | 3-agent pipeline: Parser → Summarizer → Structurer |
app/reporter/ | Template-based synoptic report generator |
ui/static/phi_redactor/ | Main UI (served at /ui/): client-side PHI scrubbing + clinical dashboard |
Note (Current as of 2026-01): The server enforces
PROCSUITE_PIPELINE_MODE=extraction_firstat startup. The authoritative production endpoint isPOST /api/v1/process, and its primary pipeline is Extraction‑First: Registry extraction → deterministic Registry→CPT rules. The older CPT-first (ML-first) hybrid flows still exist in code for legacy endpoints and tooling, but are expected to be gated/disabled in production.
┌─────────────────────────────────────────────────────────────────────┐
│ Procedure Note │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ FastAPI Layer (app/api/) │
│ • /api/v1/process - Unified extraction-first endpoint (prod) │
│ • /v1/coder/run - Legacy CPT coding endpoint (gated) │
│ • /v1/registry/run - Legacy registry extraction endpoint (gated) │
│ • /v1/report/render - Report generation endpoint │
└─────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ RegistryService │ │ Reporter │
│ (Extraction- │ │ (Jinja temps) │
│ First) │ └─────────────────┘
└─────────────────┘
│
▼
┌──────────────────────────────┐
│ RegistryRecord (V3-shaped) │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ Deterministic Registry→CPT │
│ (no note parsing) │
└──────────────────────────────┘
/api/v1/process)The production pipeline (as exercised by the UI at /ui/ and POST /api/v1/process) is:
already_scrubbed=true)REGISTRY_EXTRACTION_ENGINE, production requires parallel_ner)RegistryRecord (no raw note parsing)Some older endpoints and internal tools still use a CPT-first hybrid approach:
| File | Purpose |
|---|---|
data/knowledge/ip_coding_billing_v3_0.json | CPT codes, RVUs, bundling rules |
data/knowledge/IP_Registry.json | Registry schema definition |
data/knowledge/golden_extractions/ | Training data for ML models |
schemas/IP_Registry.json | JSON Schema for validation |
# Run all tests (Codex/vanilla, no conda required)
make test
# Recreate test environment from scratch
rm -rf .venv && make test
# Optional local conda route
make test-conda
# Run specific test suites in venv
./.venv/bin/python -m pytest tests/coder/ -v
./.venv/bin/python -m pytest tests/registry/ -v
./.venv/bin/python -m pytest tests/ml_coder/ -v
# Validate registry extraction
make validate-registry
# Run preflight checks
make preflight
Please read docs/DEVELOPMENT.md before making changes.
app/api/fastapi_app.py (not api/app.py - deprecated)CodingService from app/coder/application/coding_service.pyRegistryService from app/registry/application/registry_service.pydata/knowledge/ip_coding_billing_v3_0.jsonmake test before committing| Variable | Description | Default |
|---|---|---|
LLM_PROVIDER | LLM backend: gemini or openai_compat | gemini |
GEMINI_API_KEY | API key for Gemini LLM | Required for LLM features |
GEMINI_OFFLINE | Disable LLM calls (use stubs) | 1 |
REGISTRY_USE_STUB_LLM | Use stub LLM for registry tests | 1 |
OPENAI_API_KEY | API key for OpenAI-protocol backend (openai_compat) | Required unless OPENAI_OFFLINE=1 |
OPENAI_BASE_URL | Base URL for OpenAI-protocol backend (no /v1) | https://api.openai.com |
OPENAI_MODEL | Default model name for openai_compat | Required unless OPENAI_OFFLINE=1 |
OPENAI_MODEL_SUMMARIZER | Model override for summarizer/focusing tasks (openai_compat only) | OPENAI_MODEL |
OPENAI_MODEL_STRUCTURER | Model override for structurer tasks (openai_compat only) | OPENAI_MODEL |
OPENAI_MODEL_JUDGE | Model override for self-correction judge (openai_compat only) | OPENAI_MODEL |
OPENAI_OFFLINE | Disable openai_compat network calls (use stubs) | 0 |
REPORTER_SEED_STRATEGY | Reporter seed mode: registry_extract_fields or llm_findings | registry_extract_fields |
REPORTER_SEED_LLM_STRICT | In llm_findings mode, fail instead of fallback when LLM seeding errors | 0 |
OPENAI_PRIMARY_API | Primary API: responses or chat | responses |
OPENAI_RESPONSES_FALLBACK_TO_CHAT | Fall back to Chat Completions on 404 | 1 |
OPENAI_TIMEOUT_READ_REGISTRY_SECONDS | Read timeout for registry tasks (seconds) | 180 |
OPENAI_TIMEOUT_READ_DEFAULT_SECONDS | Read timeout for default tasks (seconds) | 60 |
PROCSUITE_SKIP_WARMUP | Skip NLP model loading at startup | false |
PROCSUITE_PIPELINE_MODE | Pipeline mode (startup-enforced): extraction_first | extraction_first |
REGISTRY_EXTRACTION_ENGINE | Registry extraction engine: engine, agents_focus_then_engine, or agents_structurer | engine |
REGISTRY_AUDITOR_SOURCE | Registry auditor source (extraction-first): raw_ml or disabled | raw_ml |
REGISTRY_ML_AUDIT_USE_BUCKETS | Audit set = high_conf + gray_zone when 1; else use top_k + min_prob | 1 |
REGISTRY_ML_AUDIT_TOP_K | Audit top-k predictions when buckets disabled | 25 |
REGISTRY_ML_AUDIT_MIN_PROB | Audit minimum probability when buckets disabled | 0.50 |
REGISTRY_ML_SELF_CORRECT_MIN_PROB | Min prob for self-correction trigger candidates | 0.95 |
REGISTRY_SELF_CORRECT_ENABLED | Enable guarded self-correction loop | 0 |
REGISTRY_SELF_CORRECT_ALLOWLIST | Comma-separated JSON Pointer allowlist for self-correction patch paths (default: app/registry/self_correction/validation.py ALLOWED_PATHS) | builtin |
REGISTRY_SELF_CORRECT_MAX_ATTEMPTS | Max successful auto-corrections per case | 1 |
REGISTRY_SELF_CORRECT_MAX_PATCH_OPS | Max JSON Patch ops per proposal | 5 |
Last updated: January 2026
381 commits
2 commits
Python
78.2%
JavaScript
17.1%
Jinja
1.8%
Automated CPT Coding, Registry Extraction, and Synoptic Reporting for Interventional Pulmonology.
This toolkit enables:
Install:
micromamba activate medparse-py311
make install
make preflight
Configure:
Create .env with your GEMINI_API_KEY.
Run:
# Start the API/Dev Server (ATLAS UI by default)
./ops/devserver.sh
UI variants:
# ATLAS UI (default)
./ops/devserver.sh
# ATLAS UI (explicit)
./ops/devserver.sh --ui=atlas
# Classic UI
./ops/devserver.sh --ui=classic
Then open:
http://localhost:8000/ui/http://localhost:8000/ui/workflow.htmlThe UI flow is: paste note -> run PHI detection -> apply redactions -> submit scrubbed note -> review results. Optional: edit values in Flattened Tables (Editable) (generates Edited JSON (Training)) and export JSON/tables.
PDF upload/extraction is browser-local and worker-based (ui/static/phi_redactor/pdf_local/):
workers/pdf.worker.js (pdf.js text layer + text/image region analysis).nativeTextDensity = charCount / pageArea.
NATIVE_DENSE_TEXT) and stay native.workers/ocr.worker.js with:
auto, on, offLidocaine 49% -> Lidocaine 4%Atropine 9.5 mg -> Atropine 0.5 mglyrnphadenopathy -> lymphadenopathyhytnph -> lymphtracheobronchial).Security/ops constraints:
ui/static/phi_redactor/vendor/).http://localhost:8000/ui/reporter_builder.htmlRun Detection -> Apply Redactions -> Seed BundlePOST /report/seed_from_text supports:
REPORTER_SEED_STRATEGY=registry_extract_fields (default)REPORTER_SEED_STRATEGY=llm_findings (reporter-only findings path)REPORTER_SEED_LLM_STRICT=1 to disable fallbackReporter findings mode uses existing OpenAI-compatible settings:
LLM_PROVIDER=openai_compatOPENAI_MODEL_STRUCTURER=gpt-5-miniOPENAI_API_KEY=...OPENAI_OFFLINE=0 (or fallback/strict behavior applies)Generate random prompt/output reporter runs from training JSONL files:
python ops/tools/run_reporter_random_seeds.py \
--input-dir /home/rjm/projects/proc_suite_notes/reporter_training/reporter_training \
--count 20 \
--seed 42 \
--output reporter_tests.txt \
--include-metadata-json
Outputs:
reporter_tests.txtreporter_tests.json (or custom path via --metadata-output)PROCSUITE_ALLOW_ONLINE=1 \
LLM_PROVIDER=openai_compat \
OPENAI_MODEL_STRUCTURER=gpt-5-mini \
python ops/tools/eval_reporter_prompt_llm_findings.py
proc_schemas/shared/ebus_events.py and are re-exported via app/registry/schema/ebus_events.py.app/registry/schema/granular_models.py and logic to app/registry/schema/granular_logic.py; app/registry/schema_granular.py is a compat shim.app/registry/schema/v2_dynamic.py; app/registry/schema.py is now a thin entrypoint preserving the __path__ hack.app/registry/schema/ip_v3_extraction.py with a compatibility re-export at app/registry/schema/ip_v3.py; the rich registry entry schema remains at proc_schemas/registry/ip_v3.py.app/registry/schema/adapters/v3_to_v2.py with a compat shim at app/registry/adapters/v3_to_v2.py.NOTES_SCHEMA_REFACTOR.md and tests/registry/test_schema_refactor_smoke.py.31647 (initial lobe) + 31651 (each additional lobe); valve removal uses 31648 (initial lobe) + 31649 (each additional lobe).31634 is derived only when Chartis is documented; suppressed when Chartis is in the same lobe as valve placement, and flagged for modifier documentation when distinct lobes are present.99152/99153 are derived only when sedation.type="Moderate", anesthesia_provider="Proceduralist", and intraservice minutes ≥10 (computed from start/end if needed).registry.coding_support (rules applied + QA flags) and enriches registry.billing.cpt_codes[] with description, derived_from, and evidence spans.providers_team[] (auto-derived from legacy providers when missing).pathology_results.pdl1_tps_text to preserve values like "<1%" or ">50%".docs/KNOWLEDGE_INVENTORY.md, docs/KNOWLEDGE_RELEASE_CHECKLIST.md, and make validate-knowledge-release for safer knowledge/schema updates."version" (override: PSUITE_KNOWLEDGE_ALLOW_VERSION_MISMATCH=1).master_code_index, and synonym phrase lists are centralized in KB synonyms.POST /report/seed_from_text now supports REPORTER_SEED_STRATEGY=llm_findings (default remains registry_extract_fields).NERToRegistryMapper -> ClinicalGuardrails -> deterministic CPT derivation -> existing Jinja templates.already_scrubbed=true.ops/tools/run_reporter_random_seeds.py and ops/tools/eval_reporter_prompt_llm_findings.py.| Module | Description |
|---|---|
app/api/fastapi_app.py | Main FastAPI backend |
app/coder/ | CPT coding engine with CodingService (8-step pipeline) |
ml/lib/ml_coder/ | ML-based code predictor and training pipeline |
app/registry/ | Registry extraction with RegistryService and RegistryEngine |
app/agents/ | 3-agent pipeline: Parser → Summarizer → Structurer |
app/reporter/ | Template-based synoptic report generator |
ui/static/phi_redactor/ | Main UI (served at /ui/): client-side PHI scrubbing + clinical dashboard |
Note (Current as of 2026-01): The server enforces
PROCSUITE_PIPELINE_MODE=extraction_firstat startup. The authoritative production endpoint isPOST /api/v1/process, and its primary pipeline is Extraction‑First: Registry extraction → deterministic Registry→CPT rules. The older CPT-first (ML-first) hybrid flows still exist in code for legacy endpoints and tooling, but are expected to be gated/disabled in production.
┌─────────────────────────────────────────────────────────────────────┐
│ Procedure Note │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ FastAPI Layer (app/api/) │
│ • /api/v1/process - Unified extraction-first endpoint (prod) │
│ • /v1/coder/run - Legacy CPT coding endpoint (gated) │
│ • /v1/registry/run - Legacy registry extraction endpoint (gated) │
│ • /v1/report/render - Report generation endpoint │
└─────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ RegistryService │ │ Reporter │
│ (Extraction- │ │ (Jinja temps) │
│ First) │ └─────────────────┘
└─────────────────┘
│
▼
┌──────────────────────────────┐
│ RegistryRecord (V3-shaped) │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ Deterministic Registry→CPT │
│ (no note parsing) │
└──────────────────────────────┘
/api/v1/process)The production pipeline (as exercised by the UI at /ui/ and POST /api/v1/process) is:
already_scrubbed=true)REGISTRY_EXTRACTION_ENGINE, production requires parallel_ner)RegistryRecord (no raw note parsing)Some older endpoints and internal tools still use a CPT-first hybrid approach:
| File | Purpose |
|---|---|
data/knowledge/ip_coding_billing_v3_0.json | CPT codes, RVUs, bundling rules |
data/knowledge/IP_Registry.json | Registry schema definition |
data/knowledge/golden_extractions/ | Training data for ML models |
schemas/IP_Registry.json | JSON Schema for validation |
# Run all tests (Codex/vanilla, no conda required)
make test
# Recreate test environment from scratch
rm -rf .venv && make test
# Optional local conda route
make test-conda
# Run specific test suites in venv
./.venv/bin/python -m pytest tests/coder/ -v
./.venv/bin/python -m pytest tests/registry/ -v
./.venv/bin/python -m pytest tests/ml_coder/ -v
# Validate registry extraction
make validate-registry
# Run preflight checks
make preflight
Please read docs/DEVELOPMENT.md before making changes.
app/api/fastapi_app.py (not api/app.py - deprecated)CodingService from app/coder/application/coding_service.pyRegistryService from app/registry/application/registry_service.pydata/knowledge/ip_coding_billing_v3_0.jsonmake test before committing| Variable | Description | Default |
|---|---|---|
LLM_PROVIDER | LLM backend: gemini or openai_compat | gemini |
GEMINI_API_KEY | API key for Gemini LLM | Required for LLM features |
GEMINI_OFFLINE | Disable LLM calls (use stubs) | 1 |
REGISTRY_USE_STUB_LLM | Use stub LLM for registry tests | 1 |
OPENAI_API_KEY | API key for OpenAI-protocol backend (openai_compat) | Required unless OPENAI_OFFLINE=1 |
OPENAI_BASE_URL | Base URL for OpenAI-protocol backend (no /v1) | https://api.openai.com |
OPENAI_MODEL | Default model name for openai_compat | Required unless OPENAI_OFFLINE=1 |
OPENAI_MODEL_SUMMARIZER | Model override for summarizer/focusing tasks (openai_compat only) | OPENAI_MODEL |
OPENAI_MODEL_STRUCTURER | Model override for structurer tasks (openai_compat only) | OPENAI_MODEL |
OPENAI_MODEL_JUDGE | Model override for self-correction judge (openai_compat only) | OPENAI_MODEL |
OPENAI_OFFLINE | Disable openai_compat network calls (use stubs) | 0 |
REPORTER_SEED_STRATEGY | Reporter seed mode: registry_extract_fields or llm_findings | registry_extract_fields |
REPORTER_SEED_LLM_STRICT | In llm_findings mode, fail instead of fallback when LLM seeding errors | 0 |
OPENAI_PRIMARY_API | Primary API: responses or chat | responses |
OPENAI_RESPONSES_FALLBACK_TO_CHAT | Fall back to Chat Completions on 404 | 1 |
OPENAI_TIMEOUT_READ_REGISTRY_SECONDS | Read timeout for registry tasks (seconds) | 180 |
OPENAI_TIMEOUT_READ_DEFAULT_SECONDS | Read timeout for default tasks (seconds) | 60 |
PROCSUITE_SKIP_WARMUP | Skip NLP model loading at startup | false |
PROCSUITE_PIPELINE_MODE | Pipeline mode (startup-enforced): extraction_first | extraction_first |
REGISTRY_EXTRACTION_ENGINE | Registry extraction engine: engine, agents_focus_then_engine, or agents_structurer | engine |
REGISTRY_AUDITOR_SOURCE | Registry auditor source (extraction-first): raw_ml or disabled | raw_ml |
REGISTRY_ML_AUDIT_USE_BUCKETS | Audit set = high_conf + gray_zone when 1; else use top_k + min_prob | 1 |
REGISTRY_ML_AUDIT_TOP_K | Audit top-k predictions when buckets disabled | 25 |
REGISTRY_ML_AUDIT_MIN_PROB | Audit minimum probability when buckets disabled | 0.50 |
REGISTRY_ML_SELF_CORRECT_MIN_PROB | Min prob for self-correction trigger candidates | 0.95 |
REGISTRY_SELF_CORRECT_ENABLED | Enable guarded self-correction loop | 0 |
REGISTRY_SELF_CORRECT_ALLOWLIST | Comma-separated JSON Pointer allowlist for self-correction patch paths (default: app/registry/self_correction/validation.py ALLOWED_PATHS) | builtin |
REGISTRY_SELF_CORRECT_MAX_ATTEMPTS | Max successful auto-corrections per case | 1 |
REGISTRY_SELF_CORRECT_MAX_PATCH_OPS | Max JSON Patch ops per proposal | 5 |
Last updated: January 2026
381 commits
2 commits
Python
78.2%
JavaScript
17.1%
Jinja
1.8%