Skip to content

AI Foundation — Sprint A–B Buildable Specs (the contract, the door, the gate)

Date: 2026-06-04 · From: Gene (Pharmacy Ops) · For: Faraz + Emil Scope: the three foundation artifacts that gate everything else — build these first. (A) the shared medication-object contract (FHIR), (B) the MCP server the knowledge layer exposes, and (C) the deterministic clinical-line rail. Each is concrete enough to start coding. (Parent: AI_Foundation_Architecture_Spec_for_Faraz_2026-06-04.)

Why these three first: the contract is what every engine produces/consumes; the MCP server is the one door to the knowledge; the rail is the boundary that must exist before any model emits to a member. The med-object contract is also Phase 0 of the Medication Intelligence Engine and the object the Replit demo renders — so building it now feeds both.


A · The shared medication-object contract (FHIR)

Base standards: FHIR R4 MedicationRequest + Medication + Provenance, the SIG aligned to NCPDP SCRIPT SCS (so Surescripts ingest is a mapping, not a rewrite), with three Rx360 extensions (provenance-weight, confidence, flags). This is the canonical object; every input path produces it and every engine reads it.

A.1 The object (illustrative JSON — metformin 500mg BID with food)

{
  "resourceType": "MedicationRequest",
  "id": "mr-uuid",
  "status": "active",
  "subject": { "reference": "Patient/member-uuid" },
  "medicationReference": { "reference": "Medication/med-uuid" },
  "dosageInstruction": [{
    "sequence": 1,                                  // multi-phase → tapers (sequence 2,3…)
    "text": "Take 1 tablet by mouth twice daily with food",   // live SigText preview
    "timing": { "repeat": { "frequency": 2, "period": 1, "periodUnit": "d",
                            "when": ["MORN","EVE"] } },
    "route": { "coding": [{ "system": "http://snomed.info/sct", "code": "26643006",
                            "display": "Oral route" }] },
    "doseAndRate": [{ "doseQuantity": { "value": 1, "unit": "tablet" } }],
    "additionalInstruction": [{ "text": "with food" }]        // food/timing rule
  }],
  "_rx360": {                                       // ── Rx360 extensions ──
    "provenance": { "source": "pharmacist-verified", "weight": 1.0,
                    "npi": "1234567890", "verifiedAt": "2026-06-04T15:00:00Z" },
    "confidence": { "score": 0.97, "gate": "auto-default" },   // auto | force-confirm | manual
    "flags": [ { "type": "fall-risk-class", "view": "pharmacist" } ],
    "audit": { "ocrOriginal": "METFORMIN 500MG TAB ...", "confirmed": "..." }
  }
}
// Medication resource (referenced):
{ "resourceType": "Medication", "id": "med-uuid",
  "code": { "coding": [{ "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                         "code": "860975", "display": "metformin 500 MG oral tablet" }] },
  "form": { "coding": [{ "display": "oral tablet" }] },
  "ingredient": [{ "itemCodeableConcept": { "text": "metformin" } }],
  "_rx360": { "ndc": "00093-1048-01", "drugClasses": ["biguanides"], "deaSchedule": null } }

A.2 Field → source-of-truth → who consumes it

Field Source Consumed by
Medication.code (RXCUI) RxNorm all engines (identity)
_rx360.drugClasses (RxClass) RxNav/RxClass Balance Meter (FRID/Beers), rail (duplicate/interaction)
dosageInstruction.timing + text structured SIG builder Scheduling (cadence/tolerance)
additionalInstruction (food/timing) DailyMed/SPL Scheduling (dose-context payload)
_rx360.provenance (weight) verification gate Balance Meter weighting, scheduling precision
_rx360.confidence.gate matcher the UX (auto/force-confirm/manual)
_rx360.flags knowledge-graph query pharmacist view only
_rx360.audit OCR + confirm the learning loop

A.3 Validation rules (non-negotiable)

  1. Never invent — if Medication.code (RXCUI) can't be resolved at confidence ≥ threshold, the object is gate: "manual"; no strength/dose is auto-filled.
  2. High-risk → gate: "force-confirm" regardless of confidence (insulin · anticoagulant · opioid · controlled · NTI · LASA — from the ruleset-config).
  3. Provenance required on every stored object; member-self ×0.5 → pharmacist-verified ×1.0.
  4. Schema-valid against FHIR + the _rx360 extension schema before it leaves the app (XSD/JSON-schema validation on save).
  5. PRN meds carry no scheduled timing (flagged PRN; refill/usage tracked instead).

B · The MCP server — the one door to the knowledge layer

The curated KB + graph + vector index are exposed through one MCP (Model Context Protocol) server, so every engine queries the knowledge the same way and governance isn't re-implemented per engine. Stateless (per the 2026 MCP roadmap) for horizontal scale.

B.1 Tools (signatures)

# Every tool returns a PROVENANCE ENVELOPE (see B.2). Read-only; no PHI in queries.
resolve_drug(query: str | None, ndc: str | None, rxcui: str | None)
    -> { rxcui, ingredient, brand, form, route, strengths[], drug_classes[], dea_schedule }
modal_defaults(rxcui: str)                       # the smart-match priors
    -> { strength, dose_form, frequency, timing[], food }
graph_query(rxcui: str, member_med_rxcuis: str[])# the "smart intelligence" flags
    -> { duplicate_therapy[], interactions[], time_separation[], frid_class: bool }
tolerance(rxcui: str | None, drug_class: str | None)   # drug-forgiveness (scheduling)
    -> { cadence, tolerance_window, food_rules, high_stakes: bool }   # preliminary until sign-off
semantic_search(query: str, k: int = 8)          # fallback / dose-context
    -> { chunks[]: {text, kb_entry_id} }

B.2 The provenance envelope (on every response)

{ "value": { ... },                 // the answer
  "source": "RxNorm" | "OpenFDA" | "DailyMed" | "curated-KB" | "graph",
  "kb_entry_id": "kb:biguanides:tolerance:v3",   // traceable
  "version": "2026-06-01",
  "clinical_line_flag": false }     // true → must route through the rail / pharmacist, never auto-served

B.3 Notes

  • Structured-first: resolve_drug / modal_defaults / graph_query hit deterministic tables; semantic_search is the fallback. (Don't let the vector store answer what a table can.)
  • The graph is a projection of the lakehouse, synced via the event stream — read here, never hand-edited.
  • Engines (and any LLM) get clinical facts only through this server — never from model memory.

C · The deterministic clinical-line rail — the boundary

The terminal, fail-closed gate on every member-facing output. Non-ML by design (heterogeneous failure vs the model-based rails upstream).

C.1 Contract

gate(candidate_output: str, target: "member" | "pharmacist", grounding: GroundingRefs)
    -> { action: "emit" | "block_and_route", reason?: str, queue?: "pharmacist" }
Runs on the final rendered string, after all model-based rails, with no model in the path that could be re-injected.

C.2 The rules (all deterministic)

  1. Allow-listed output schema — member-facing payload must be exactly {type: reminder | confirmation | insight, ...}. Any other shape → block.
  2. Lexicon / pattern detectors (block on match):
  3. Dosing language: a quantity+unit (\d+\s?(mg|mcg|mL|units|tablet)) attached to an instruction verb (take|increase|decrease|titrate|split|double|skip|stop).
  4. Diagnostic assertion: "you have / this is" + a condition; naming a specific disease as fact.
  5. Clinical-management claim: treatment/management directive.
  6. Grounding check: every clinical field in the output must carry a kb_entry_id (from the MCP envelope); ungrounded clinical content → block.
  7. Lexicon sweep against the Rx360 Wellness Lexicon red-flag set.

C.3 Fail-closed behavior

On any detector match, low confidence, schema violation, or rail error/timeoutblock_and_route → pharmacist queue. Default state = "do not emit." A timeout is a block, never a pass-through.

C.4 Hard rules

  • Do not stream member-facing clinical-adjacent output — buffer fully, gate before the first token reaches the member.
  • OCR'd Rx text is untrusted (injection vector) — never treated as instructions.
  • Target metric: clinical-line leakage = 0, measured by sampling emitted member output.

D · How A–B–C feed the demo + the build

  • The med-object (A) is what the Replit demo renders ("one object → reminder + fall-risk + refill") and Phase 0 of the Med-Intelligence Engine.
  • The MCP server (B) is how the demo does live enrichment (RxNorm/OpenFDA) + the smart-link flags.
  • The rail (C) is the visible "guardrail" in the demo (warfarin → force-confirm; a dosing string → blocked → pharmacist queue).
  • Sprint order: A (contract) + C (rail) can be built in parallel; B (MCP server) wraps the knowledge once the contract exists. Then the engines + the demo.

E · Open for Faraz

  1. Backend stack (confirms FHIR-validation lib + the _rx360 extension JSON-schema home).
  2. MCP server hosting (stateless behind a gateway) + which knowledge sources are live vs cached in v1.
  3. The high-risk + lexicon rulesets — confirm they live as pharmacist-curated config (not code), versioned.
  4. Confidence thresholds (auto / suggest / manual cut-points) — set with Dr. B + measured in pilot.