Batch processing clinical text
SkillDatabases & dataLets your agent batch-process many clinical notes for entity extraction, PII removal, or de-identification on-device.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Batch processing clinical text skill
About this capability
Run large-scale batch NER, PII extraction, or de-identification over many clinical notes on-device with OpenMed, with sharding, checkpointing, resumability, and append-only JSONL output. Use when the user needs to process a corpus or folder of notes, de-identify a dataset, run NER over thousands of
What this skill tells your AI
The instructions your AI receives, as published by maziyarpanahi/openmed in skills/batch-processing-clinical-text/SKILL.md and read by ahel’s review.
openmed.processing runs OpenMed over many documents efficiently, with progress
tracking, per-item error isolation, and streaming. It runs fully on-device:
the corpus, the model, and the output never leave the host. This skill shows a
resumable runner — sharded, checkpointed, append-only JSONL — that you can
restart without reprocessing.
When to use this skill
For corpora, folders, or datasets — anything beyond a handful of notes. For a
single note, just call openmed.analyze_text / deidentify directly
(extracting-clinical-entities, deidentifying-clinical-text). For an
always-on HTTP service, see serving-openmed-rest-api.
Quick start
from openmed import process_batch
texts = ["Patient has type 2 diabetes.", "No acute distress. BP 120/80."]
result = process_batch(texts, model_name="disease_detection_superclinical")
print(result.summary()) # PHI-safe counts + timing
print(result.successful_items, "/", result.total_items)
for item in result.get_successful_results():
print(item.id, item.result.to_dict()["entities"]) # spans only; avoid raw text in logs
process_batch(...) is a thin wrapper over BatchProcessor. Real signatures
(openmed/processing/batch.py):
process_batch(texts, model_name="disease_detection_superclinical", ids=None, config=None, progress_callback=None, on_progress=None, **kwargs) -> BatchResultBatchProcessor(model_name=..., operation="analyze_text", batch_size=8, continue_on_error=True, **analyze_kwargs)withoperation ∈ {"analyze_text", "extract_pii", "deidentify"}.BatchItem(id, text, source=None, metadata=None)BatchResult—.items,.total_items,.successful_items,.failed_items,.success_rate,.average_processing_time,.summary(),.to_dict(),.get_successful_results(),.get_failed_results().BatchItemResult—.id,.result(aPredictionResult/DeidentificationResult),.error,.processing_time,.source,.success,.to_dict().
Choosing the operation
from openmed import BatchProcessor
# NER (default)
ner = BatchProcessor(model_name="disease_detection_superclinical") # operation="analyze_text"
# Detect PHI spans
pii = BatchProcessor(operation="extract_pii", model_name="OpenMed/OpenMed-PII-SuperClinical-Small-44M-v1")
# De-identify (rewrites text)
deid = BatchProcessor(operation="deidentify", method="mask", confidence_threshold=0.7)
BatchProcessor reuses one model loader across items (and a cached privacy
filter for PII ops), so a single processor over many texts is far cheaper than
many one-off calls.
Streaming + PHI-safe progress
from openmed.processing import BatchProcessor, BatchProgress
proc = BatchProcessor(operation="deidentify", method="mask", batch_size=16)
def on_progress(p: BatchProgress) -> None: # frozen record: completed/total/current_index/elapsed
if p.completed % 500 == 0:
print(f"{p.completed}/{p.total} ({p.elapsed:.1f}s)") # NO PHI
for item in proc.iter_process(texts, ids=doc_ids, on_progress=on_progress):
write_jsonl(item) # one result at a time — constant memory for huge corpora
on_progress receives only counts/timing (never text), so it is safe to log.
iter_process yields BatchItemResults without holding the whole corpus.
Workflow
- De-identify upstream if the corpus has PHI, or key everything by stable
internal
idsso logs/output never carry identifiers. - Pick the operation + model — one
BatchProcessorperoperation(analyze_text/extract_pii/deidentify); reuse it across all items so the loader is shared. - Shard the corpus into independent partitions writing to separate JSONL files; run shards as separate processes for parallelism.
- Stream with
iter_processand append eachBatchItemResultto JSONL (flush per item) — that append-only file is your checkpoint. - Track progress with the PHI-safe
on_progresscallback (counts/timing only). - Re-run to resume: skip ids already present in the output; with
continue_on_error=True, failures are recorded, not raised. - Reconcile via
result.get_failed_results()/ a failures pass, then hand JSONL to the downstream consumer.
A resumable batch runner
import json
from pathlib import Path
from openmed.processing import BatchProcessor
def run_resumable(texts, ids, out_path: Path, *, operation="deidentify", **kw):
out_path.parent.mkdir(parents=True, exist_ok=True)
# 1) checkpoint = ids already written (append-only JSONL is the source of truth)
done = set()
if out_path.exists():
with out_path.open() as f:
done = {json.loads(line)["id"] for line in f if line.strip()}
todo = [(i, t) for i, t in zip(ids, texts) if i not in done]
if not todo:
return
pending_ids, pending_texts = zip(*todo)
proc = BatchProcessor(operation=operation, continue_on_error=True, **kw)
# 2) append each result as it completes -> safe to kill/restart anytime
with out_path.open("a") as f:
for item in proc.iter_process(list(pending_texts), ids=list(pending_ids)):
record = {"id": item.id, "ok": item.success,
"result": item.result.to_dict() if item.success else None,
"error": item.error} # item.error is a message, keep PHI out
f.write(json.dumps(record) + "\n")
f.flush()
Re-running skips finished ids (continue_on_error=True keeps one bad doc from
killing the run; failures are recorded, not raised). Shard a corpus by writing
to out/shard_000.jsonl, shard_001.jsonl, … and run shards in separate
processes.
Chunking long documents
BatchProcessor does not split single documents. For notes longer than the
model's max sequence length, pre-split into sentences/windows
(openmed.processing.sentences) — or for analyze_text, rely on built-in
sentence handling — then re-stitch entities by adding the chunk offset back to
each entity's start/end so spans point into the original document.
Hand-off to / from OpenMed
- Per-item engine: each operation calls the same
openmed.analyze_text/extract_pii/deidentifyyou'd call directly — same results, batched. - Downstream: JSONL feeds
building-patient-timelines,etl-to-omop-cdm, andexporting-to-fhir. De-id JSONL feedsevaluating-with-leakage-gates. - Service vs batch: for request/response use
serving-openmed-rest-api; for corpora use this batch path.
Edge cases & gotchas
- No PHI in logs/JSONL keys. Use stable
ids(BatchItem.id), offsets, and labels.BatchItemResult.erroris a message — keep raw text out of inputs to exceptions you log. continue_on_error=Trueis the default and recommended for corpora; checkresult.get_failed_results()afterward. SetFalseonly when one failure should abort everything.batch_sizeis a throughput dial, not correctness — tune to memory/CPU. Larger isn't always faster on CPU.- Append-only is the checkpoint. Don't buffer results in memory and write at the end; you lose progress on a crash. Append + flush per item.
process_files/process_directoryread files for you and setBatchItem.source; unreadable files become failed items (not crashes) undercontinue_on_error.- Mixed languages: pass
lang=per run for PII ops; don't run an English PII model across other languages (deidentifying-multilingual-text).
Standards & references
- JSON Lines (newline-delimited JSON): https://jsonlines.org/
- HIPAA de-identification (when batching PHI removal), 45 CFR 164.514(b): https://www.hhs.gov/hipaa/for-professionals/privacy/special-topics/de-identification/index.html
- OpenMed source:
openmed/processing/batch.py(process_batch,BatchProcessor,BatchItem,BatchItemResult,BatchResult,BatchProgress,iter_process).
Signals
- GitHub stars
- 5k
- Forks
- 666
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
batch-processing-clinical-text- Source
- github.com/maziyarpanahi/openmed