Streaming Prescription Extraction
Streaming Prescription Extraction
DELPHOS can listen to a doctor speaking naturally and produce a fully structured, safety-checked prescription in real time. As the doctor dictates, medications appear one by one — each passing through safety gates before the final prescription is assembled.
This is the streaming prescription agent — the most powerful integration point in the DELPHOS platform.
How It Works
Doctor speaks DELPHOS processes Your UI updates─────────────── ────────► ──────────────────── ────────► ──────────────────"Amoxicilina 1. Extract items from speech Item 1 appears with 500mg oral 2. Per-item safety gates safety gate results 8/8h por (validation, CMED, posology) 7 dias. 3. Repeat for each medication Item 2 appears... Dipirona 4. Cross-item safety gates 500mg SOS" (drug interactions, Final prescription duplicate therapy) with all gates completeThe streaming architecture uses a two-phase gate model:
- Per-item gates (1, 2, 6) fire immediately as each medication is detected
in the speech stream. Results arrive with each
item_detectedevent. - Cross-item gates (3, 4) fire after the full input is processed, since
they need to analyze interactions between all medications. Results arrive
in the
gates_completeevent.
Sequence Diagram
Client DELPHOS API Safety Gates │ │ │ │ POST /v1/prescriptions/stream │ │ │ { doctor_input, stream: true } │ │ │ ─────────────────────────────► │ │ │ │ │ │ event: status │ │ │ data: {"type":"analyzing"} │ │ │ ◄───────────────────────────── │ │ │ │ Gate 1 (Validation) │ │ │ Gate 2 (CMED Resolution) │ │ │ Gate 6 (Controlled Subst.)│ │ event: item_detected │ ◄────────────────────────── │ │ data: {index:0, item, gates} │ │ │ ◄───────────────────────────── │ │ │ │ Per-item gates repeat │ │ event: item_detected │ for each medication │ │ data: {index:1, item, gates} │ ◄────────────────────────── │ │ ◄───────────────────────────── │ │ │ │ Gate 3 (Drug Interactions)│ │ │ Gate 4 (Duplicate Therapy)│ │ event: gates_complete │ ◄────────────────────────── │ │ data: {gate3, gate4 results} │ │ │ ◄───────────────────────────── │ │ │ │ │ │ event: prescription │ │ │ data: {items, gates, final} │ │ │ ◄───────────────────────────── │ │Endpoint Reference
POST /v1/prescriptions/streamRequest Headers
| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
x-api-key | Your API key | Yes |
Accept | text/event-stream | Recommended |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
consultation_id | string | Yes | External consultation identifier (1–255 chars) |
patient_id | UUID | Yes | UUID of the patient receiving the prescription |
doctor_id | UUID | Yes | UUID of the prescribing physician |
doctor_input | string | Yes | Raw doctor speech or text describing medications (1–10,000 chars) |
stream | boolean | No | If true (default), return SSE stream. If false, return a single JSON response |
Example Request
{ "consultation_id": "ATD-2024-001234", "patient_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "doctor_id": "f0e1d2c3-b4a5-6789-0abc-def123456789", "doctor_input": "Amoxicilina 500mg via oral de 8 em 8 horas por 7 dias. Dipirona 500mg via oral se dor, máximo 6 em 6 horas.", "stream": true}SSE Event Types
The stream emits five event types in a defined sequence. Your client receives them in this order and should handle each accordingly.
1. status — Processing Started
Always the first event emitted. Indicates DELPHOS is analyzing the doctor’s input.
event: statusdata: {"type": "analyzing"}2. item_detected — Medication Found
Emitted once per medication as it is parsed from the doctor’s speech. Each event includes the extracted item data and the results of per-item safety gates.
The pending_gates array lists the cross-item gates that will arrive later in
the gates_complete event.
event: item_detecteddata: { "index": 0, "item": { "medication_name": "Amoxicilina", "dosage": "500mg", "route": "oral", "frequency": "8/8h", "duration": "7 dias", "quantity": 21, "unit": "comprimidos", "instructions": null }, "gates": { "gate1_input_validation": { "status": "passed", "message": "Dados do medicamento válidos." }, "gate2_cmed_resolution": { "status": "passed", "message": "Medicamento identificado na base CMED.", "details": { "match_tier": "auto", "cmed_product": "AMOXICILINA 500MG CAP GEL DURA CT BL AL/AL X 21" } }, "gate6_controlled_substance": { "status": "passed", "message": "Medicamento não é substância controlada." } }, "pending_gates": [ "gate3_drug_interactions", "gate4_duplicate_therapy" ]}Item Fields
| Field | Type | Description |
|---|---|---|
medication_name | string | Name of the medication as spoken by the doctor |
dosage | string | Dosage (e.g., "500mg", "500mg/5ml") |
route | string | Administration route ("oral", "IV", "IM", "SC", "sublingual", "topical") |
frequency | string | Dosing frequency (e.g., "8/8h", "1x/dia", "12/12h") |
duration | string | null | Treatment duration (e.g., "7 dias", "uso contínuo") |
quantity | integer | null | Total quantity to dispense |
unit | string | null | Quantity unit (e.g., "comprimidos", "mL") |
instructions | string | null | Additional instructions (e.g., "se dor", "em jejum") |
3. gates_complete — Cross-Item Analysis Done
Emitted after all items have been detected and the cross-item safety gates finish their analysis across the full prescription.
event: gates_completedata: { "gate3_drug_interactions": { "status": "passed", "details": { "pairs_checked": 1, "interactions_found": 0 } }, "gate4_duplicate_therapy": { "status": "passed", "details": { "duplicates_found": 0 } }}When interactions are found, the gate returns detailed information:
event: gates_completedata: { "gate3_drug_interactions": { "status": "warning", "severity": "major", "message": "Interação medicamentosa detectada entre Warfarina e Amoxicilina.", "details": { "pairs_checked": 3, "interactions_found": 1, "interactions": [ { "drug_a": "Warfarina", "drug_b": "Amoxicilina", "severity": "major", "description": "Aumento do risco de sangramento" } ] } }, "gate4_duplicate_therapy": { "status": "passed", "details": { "duplicates_found": 0 } }}4. prescription — Final Result
The complete, aggregated prescription with all items and gate results. This is the terminal success event — the stream closes after it.
event: prescriptiondata: { "items": [ { "medication_name": "Amoxicilina", "dosage": "500mg", "route": "oral", "frequency": "8/8h", "duration": "7 dias", "quantity": 21, "unit": "comprimidos", "instructions": null }, { "medication_name": "Dipirona", "dosage": "500mg", "route": "oral", "frequency": "6/6h", "duration": null, "quantity": null, "unit": null, "instructions": "se dor" } ], "gates_per_item": [ { "gate1_input_validation": { "status": "passed" }, "gate2_cmed_resolution": { "status": "passed", "details": { "match_tier": "auto" } }, "gate6_controlled_substance": { "status": "passed" } }, { "gate1_input_validation": { "status": "passed" }, "gate2_cmed_resolution": { "status": "passed", "details": { "match_tier": "auto" } }, "gate6_controlled_substance": { "status": "passed" } } ], "gates_cross_item": { "gate3_drug_interactions": { "status": "passed" }, "gate4_duplicate_therapy": { "status": "passed" } }, "requires_confirmation": true, "is_degraded": false}| Field | Type | Description |
|---|---|---|
items | array | All extracted medication items |
gates_per_item | array | Per-item gate results, indexed by item position |
gates_cross_item | object | Cross-item gate results (gates 3 and 4) |
requires_confirmation | boolean | Always true — physician must confirm before finalizing |
is_degraded | boolean | true if a safety gate or service encountered an error |
5. error — Processing Failed
Emitted when an unrecoverable error occurs during processing. The degraded
flag indicates whether partial results may still be usable.
event: errordata: { "code": "PROCESSING_TIMEOUT", "message": "Processing time limit exceeded", "degraded": true}| Error Code | Description | Retry? |
|---|---|---|
PROCESSING_TIMEOUT | Processing exceeded the time limit | Yes, with backoff |
PROCESSING_ERROR | Internal processing failure | Yes, after short delay |
PARSE_ERROR | Could not extract medications from input — retry once, then rephrase | Yes, once |
INTERNAL_ERROR | Unexpected server error | Yes, with backoff |
Safety Gates
Every prescription passes through six safety gates. DELPHOS follows the physician autonomy principle — all gates are advisory. They inform and warn, but never block the physician’s clinical decision.
| Gate | Name | Scope | What It Checks |
|---|---|---|---|
| 1 | Input Validation | Per-item | Required fields are present (medication name is mandatory) |
| 2 | CMED Resolution | Per-item | Matches medication against the ANVISA/CMED national database |
| 3 | Drug Interactions | Cross-item | Checks for drug-drug interactions across all prescription items |
| 4 | Duplicate Therapy | Cross-item | Detects overlapping therapeutic classes among medications |
| 6 | Controlled Substance | Per-item | Flags ANVISA controlled substances (Portaria 344/98, RDC 20/2011) |
Gate Severity Levels
When a gate produces a warning, it includes a severity level to help your UI prioritize display:
| Severity | Meaning | Recommended UI Treatment |
|---|---|---|
info | Informational — no concerns | Subtle indicator |
warning | Advisory alert — physician should review | Yellow highlight |
moderate | Moderate concern — review recommended | Orange highlight |
major | Significant concern — careful review needed | Red highlight with details |
critical | Serious safety concern — demands attention | Prominent red alert |
CMED Resolution Tiers (Gate 2)
When Gate 2 resolves a medication against the CMED database, it reports a match tier:
| Tier | Meaning |
|---|---|
auto | High-confidence match — medication automatically resolved |
suggestion | Moderate confidence — DELPHOS suggests a CMED product for physician confirmation |
none | No match found — medication name could not be resolved in the CMED database |
Client Integration
JavaScript — EventSource Pattern
/** * Stream prescription extraction via SSE. * Handles all five event types progressively. */async function streamPrescription(consultationId, patientId, doctorId, doctorInput, onEvent) { const response = await fetch('/v1/prescriptions/stream', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY', 'Accept': 'text/event-stream' }, body: JSON.stringify({ consultation_id: consultationId, patient_id: patientId, doctor_id: doctorId, doctor_input: doctorInput, stream: true }) });
if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); }
const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = '';
while (true) { const { done, value } = await reader.read(); if (done) break;
buffer += decoder.decode(value, { stream: true }); const blocks = buffer.split('\n\n'); buffer = blocks.pop(); // keep incomplete block in buffer
for (const block of blocks) { if (!block.trim()) continue; const evtMatch = block.match(/^event:\s*(.+)$/m); const dataMatch = block.match(/^data:\s*(.+)$/m); if (evtMatch && dataMatch) { onEvent(evtMatch[1].trim(), JSON.parse(dataMatch[1])); } } }}
// ── Usage ──────────────────────────────────────────────
streamPrescription( 'ATD-2024-001234', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'f0e1d2c3-b4a5-6789-0abc-def123456789', 'Amoxicilina 500mg oral 8/8h por 7 dias. Dipirona 500mg se dor.', (type, data) => { switch (type) { case 'status': showSpinner(data.type); // "analyzing" break; case 'item_detected': addItemToUI(data.index, data.item, data.gates); break; case 'gates_complete': updateCrossItemGates(data); // drug interactions, duplicates break; case 'prescription': showFinalPrescription(data); // all items + all gates hideSpinner(); break; case 'error': showError(data.code, data.message); hideSpinner(); break; } });import httpximport jsonfrom typing import AsyncGenerator
async def stream_prescription( base_url: str, api_key: str, consultation_id: str, patient_id: str, doctor_id: str, doctor_input: str,) -> AsyncGenerator[tuple[str, dict], None]: """Stream prescription extraction via SSE.
Yields (event_type, data) tuples as medications are detected and safety gates complete. """ url = f"{base_url}/v1/prescriptions/stream" payload = { "consultation_id": consultation_id, "patient_id": patient_id, "doctor_id": doctor_id, "doctor_input": doctor_input, "stream": True, } headers = { "Content-Type": "application/json", "x-api-key": api_key, "Accept": "text/event-stream", }
async with httpx.AsyncClient() as client: async with client.stream( "POST", url, json=payload, headers=headers, timeout=60.0 ) as response: response.raise_for_status() buffer = ""
async for chunk in response.aiter_text(): buffer += chunk
while "\n\n" in buffer: block, buffer = buffer.split("\n\n", 1) if not block.strip(): continue
event_type = None data_str = None for line in block.split("\n"): if line.startswith("event: "): event_type = line[7:].strip() elif line.startswith("data: "): data_str = line[6:].strip()
if event_type and data_str: yield event_type, json.loads(data_str)
# ── Usage ──────────────────────────────────────────────
import asyncio
async def main(): async for event_type, data in stream_prescription( base_url="https://your-instance.delphos.app", api_key="YOUR_API_KEY", consultation_id="ATD-2024-001234", patient_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890", doctor_id="f0e1d2c3-b4a5-6789-0abc-def123456789", doctor_input="Amoxicilina 500mg oral 8/8h por 7 dias", ): if event_type == "item_detected": item = data["item"] print(f" [{data['index']}] {item['medication_name']} {item['dosage']}") elif event_type == "prescription": print(f"Prescription complete: {len(data['items'])} items") elif event_type == "error": print(f"Error: {data['code']} — {data['message']}")
asyncio.run(main())curl -X POST 'https://your-instance.delphos.app/v1/prescriptions/stream' \ -H 'Content-Type: application/json' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Accept: text/event-stream' \ --no-buffer \ -d '{ "consultation_id": "ATD-2024-001234", "patient_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "doctor_id": "f0e1d2c3-b4a5-6789-0abc-def123456789", "doctor_input": "Amoxicilina 500mg via oral 8/8h por 7 dias. Dipirona 500mg se dor.", "stream": true }'Non-Streaming Fallback
Set "stream": false to receive a single JSON response with all items and
gate results at once. The response structure is identical to the prescription
SSE event payload.
curl -X POST 'https://your-instance.delphos.app/v1/prescriptions/stream' \ -H 'Content-Type: application/json' \ -H 'x-api-key: YOUR_API_KEY' \ -d '{ "consultation_id": "ATD-2024-001234", "patient_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "doctor_id": "f0e1d2c3-b4a5-6789-0abc-def123456789", "doctor_input": "Amoxicilina 500mg oral 8/8h por 7 dias", "stream": false }'Error Handling Mid-Stream
When an error occurs during streaming, DELPHOS emits an error event and
closes the connection. Your client should implement retry logic with exponential
backoff, falling back to the non-streaming endpoint after max retries.
async function streamWithRetry(params, onEvent, maxRetries = 3) { let attempt = 0;
while (attempt < maxRetries) { try { await streamPrescription( params.consultationId, params.patientId, params.doctorId, params.doctorInput, (type, data) => { if (type === 'error' && !['PARSE_ERROR', 'PROCESSING_TIMEOUT', 'PROCESSING_ERROR'].includes(data.code)) { throw new Error(data.code); } onEvent(type, data); } ); return; // success — exit retry loop } catch (err) { attempt++; if (attempt >= maxRetries) { // Fall back to non-streaming endpoint const response = await fetch('/v1/prescriptions/stream', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY', }, body: JSON.stringify({ consultation_id: params.consultationId, patient_id: params.patientId, doctor_id: params.doctorId, doctor_input: params.doctorInput, stream: false }) }); const data = await response.json(); onEvent('prescription', data); return; } // Exponential backoff: 1s, 2s, 4s await new Promise(r => setTimeout(r, 1000 * 2 ** (attempt - 1))); } }}Related Articles
- Creating Prescriptions — Manual and auto-creation workflows
- Drug Safety — Detailed safety gate reference
- Medication Search — CMED database queries
- SOAP Streaming — Real-time clinical note generation via SSE
- API Explorer — Browse all DELPHOS endpoints