AI Chief Resident · Medication fetch and match path

Medication matching:
before and now

The LLM states the clinical request. The backend decides if a real Photon treatment is a safe match.

This document describes medication fetch and match only. It does not include lab orders, plan rendering, prescription submission, or pharmacy fulfillment.

Rationale

Why this system needs every part

No one part has all the facts. The system combines model judgment, provider data, standard drug identity, schemas, and fixed backend rules.

LLMUnderstands the clinical discussion and creates a medication request.
PhotonKnows which medication treatments can be ordered through the platform.
RxNormGives each standard drug concept a standard identity.
Backend codeEnforces the rules and creates safe catalog handles.
All four parts together → verified catalog candidate

The LLM can understand a discussion. The LLM cannot prove that a Photon row is the correct orderable treatment.

Photon supplies orderable treatments. A Photon display name is not sufficient proof of standard drug identity.

RxNorm supplies standard identity. NDC connects a Photon product or package to that identity.

Schemas check data shape. Fixed backend checks decide whether a row can continue.

Ownership

Which steps use an LLM and which steps use code

LLM judgment Schema boundary Programmatic rule External data
LLM step 1Create the medication request

The model reads the discussion and proposes drug, brand, dose, route, supply, and instructions.

SchemaParse the candidate

candidateActionSchema checks the model output shape.

LLM step 2Call search and select

The selection model creates the tool input. It later selects a returned handle or returns unmatched.

ProgrammaticSearch and verify

Fixed code searches RxNorm and Photon. It parses, maps, filters, and ranks rows.

ProgrammaticCreate the final handle

The backend creates one opaque handle for each returned candidate.

The LLM decides clinical intent

  • Generic drug name.
  • Exact requested brand.
  • Strength and route.
  • Patient instructions.
  • Days supply and quantity.
  • Refill count.
  • Clinical rationale.
  • Which returned handle to select.

The backend decides catalog validity

  • Which search term runs first.
  • Which Photon rows have a valid shape.
  • Which NDC maps to a usable RxNorm concept.
  • Whether the RxCUIs agree.
  • Whether strength, form, and route agree.
  • Whether OTC policy permits the row.
  • Whether the controlled-drug block removes the row.
  • Which exact Photon ID belongs to a handle.
The ruleThe LLM proposes. The backend proves and filters. The LLM cannot invent a valid Photon treatment ID.

Data boundaries

Which parts are schemas

A schema is a fixed description of allowed data. A schema checks shape and required values.

SchemaWhat it checksFailure result
candidateActionSchemaThe structured action from the first LLM step.The model output cannot enter the selection pipeline.
searchPrescriptionsArgsSchemaThe exact prescription-search tool input.The tool call is rejected.
searchClinicalTreatmentsResponseSchemaThe Photon response envelope.The external response is rejected when the envelope is invalid.
clinicalTreatmentSchemaOne Photon row: ID, name, NDC, strength, and dispense units.The backend drops only that row.
RxNorm response schemasThe shape of NIH RxNorm results.The mapping returns no usable identity.
A schema does not prove clinical correctness. A schema proves that the data has the required shape. The identity and safety checks happen after parsing.

Rules and variation

Which parts are strict and which parts are flexible

Flexible parts

These parts can produce more than one reasonable result.

  • The LLM interpretation of the discussion.
  • The medication proposal.
  • The wording of instructions and rationale.
  • The approximate RxNorm name results.
  • The related RxNorm concepts.
  • The Photon search results.
  • The number of valid Photon candidates.

Strict parts

These parts use fixed rules for the same inputs.

  • Schema parsing.
  • Brand-first and generic-fallback order.
  • Per-row parsing.
  • NDC-to-RxCUI lookup.
  • Allowed RxNorm concept types.
  • RxCUI identity comparison.
  • Strength, form, route, and OTC checks.
  • Handle creation and resolution.
Flexible input → schema boundary → fixed checks → verified handle or unmatched

Mapping rules

Which mappings are one-to-one

Some IDs point to one exact item. Search text and clinical relationships can point to many items.

One-to-one mappings

One input identifies one exact item.

MappingRule
RxCUI → RxNorm conceptOne RxCUI identifies one RxNorm concept.
Photon treatment ID → Photon rowOne provider ID identifies one orderable row.
Generated handle → registered candidateOne handle resolves to one candidate in that generation scope. An unknown handle fails.

Mappings that are not one-to-one

One input can return no item, one item, or many items.

MappingWhy
Discussion → prescription candidateThe LLM interprets free text.
Drug name → RxCUIsOne name can match many strengths, forms, routes, and brands.
RxCUI → related conceptsOne concept can have several related concepts.
Search term → Photon rowsOne term can return many orderable treatments.
NDC → RxCUI across the catalogOne mapped NDC returns one RxCUI. Some NDCs have no mapping. Many NDCs can map to the same RxCUI.
Brand concept → generic conceptsThe code accepts one or more related generic concepts.
Medication request → valid candidatesOne request can match several packages or products.

The main change

Before this branch

Photon Management
Discussion
LLM medication request
RxNorm name search
Photon Management name search
Check strength, form, route, and OTC status
Valid row
Return a handle
No valid row
Return unmatched

Now

Photon Clinical
Discussion
LLM medication requestPreserve the exact brand request.
RxNorm resolves the requested drug concepts
Photon Clinical searchBrand first, then generic fallback.
Parse each Photon row separately
NDC → RxNorm fully specified drug concept
Check identity, strength, form, route, and OTC status
Valid row
Rank it and return a handle
No valid row
Return unmatched and nearest useful result
What stays the sameThe discussion LLM creates the request. RxNorm provides drug concepts. Backend code searches and checks Photon results. The selection LLM selects only a returned handle. It does not invent a Photon treatment ID.

Before: main failure and limits

The Photon Management catalog was not the catalog used by the main EHR prescription search.

For example, the provider UI showed Ivermectin Topical Cream 1 %. Photon Management returned no rows for the same request. The system then returned unmatched.

  • No dedicated brandName field. An exact brand request could be lost.
  • Brand and generic terms did not have a clear brand-first order.
  • A name-matched row could be used without a verified RxNorm link.
  • A missing requested strength could pass.
  • nearest used alphabetical order.

Now: match only verified results

The new system searches the Photon Clinical catalog. This is the same catalog used by the main EHR prescription search.

  1. Read the NDC from a Photon row.
  2. Ask RxNorm for the RxCUI for that NDC.
  3. Accept only a fully specified generic or brand drug concept.
  4. Map a brand concept to its related generic clinical drug concept.
  5. Compare that identity with the original request concepts.

The system then checks strength, dose form, route, OTC status, and the controlled-drug block. Photon Clinical provides form and route information in the full treatment name.

Detailed view

How the two RxNorm paths meet

The system builds one RxNorm identity from the medication request. It builds a second RxNorm identity from each Photon row. A Photon row can match only when the two identities agree.

Path 1 · Medication request

Build the accepted request set

The backend sends the generic name and exact brand name to the RxNorm approximate name search.

The first results can be broad. The backend follows related concepts and keeps fully specified generic and brand drug concepts.

LLM medication request
Drug, brand, strength, form, and route.
RxNorm approximate name search
Returns possible RxCUIs.
Related drug concepts
Keep fully specified generic and brand concepts.
Accepted request RxCUIs
Path 2 · Photon results

Check each Photon row separately

Photon Clinical can return many rows. The backend treats each row as an independent candidate.

1Photon row 1
Parse → NDC → RxNorm → compare
CONTINUE
2Photon row 2
Parse → NDC → RxNorm → compare
CONTINUE
3Invalid Photon row
The other rows remain available.
DROP ROW 3

A row also stops when it has no NDC, no strength, no RxNorm mapping, or only a broad RxNorm concept.

The bridge

What NDC does

An NDC is a National Drug Code. It identifies a marketed medication product or package.

Photon Clinical supplies the NDC. RxNorm maps the NDC to a standard drug concept.

Photon treatmentAn orderable catalog row.
NDCThe product or package code.
RxNormMaps the NDC to an RxCUI.
Standard identityA fully specified generic drug concept.

The Photon name search finds possible treatments. The NDC step verifies what each treatment is.

Names are not sufficient proof. A name can use a brand, a generic name, an abbreviation, or a different text format.

Different NDCs can map to the same RxNorm drug concept. This connects different packages to one standard clinical identity.

Zepbound example
Photon NDC 00002-3544-11standard generic RxCUI 2734623

What the NDC step does

  • Connects a Photon row to RxNorm.
  • Checks identity without trusting the display name.
  • Connects a brand drug to its generic drug concept.
  • Rejects an incomplete RxNorm concept.
  • Helps prevent a similar name from becoming the wrong prescription.

What the NDC step does not do

  • It does not create patient instructions.
  • It does not create days supply.
  • It does not create dispense quantity.
  • It does not create refill counts.
  • It does not prove controlled-drug status.
Final identity check

Compare the two standard identities

If Photon gives a brand concept, RxNorm connects that brand concept to its generic clinical drug concept.

The row passes only when its standard RxCUI is also in the accepted request set.

Request set: accepted RxCUIs
Photon row: standard RxCUI
Request set contains the row RxCUI: continue
Request set does not contain the row RxCUI: reject the row

This is not the final safety check. The backend still checks strength, form, route, OTC status, and the controlled-drug block.

If the doctor names a brand, the system searches that brand first. If it has no verified match, the system searches the generic drug name. If no brand is named, it uses brandPreference. The normal default is generic first.

{
  "intent": {
    "genericName": "tirzepatide",
    "brandName": "Zepbound",
    "strength": "7.5 mg/0.6 mL",
    "form": "pen injector",
    "route": "subcutaneous"
  },
  "brandPreference": "brand"
}

Zepbound example

Request

Brand: Zepbound
Generic drug: tirzepatide
Strength: 7.5 mg/0.6 mL
Form: pen injector
Route: subcutaneous

Verified Photon result

Zepbound KwikPen Subcutaneous Solution Pen-injector 7.5 MG/0.6ML

Its generic RxNorm identity contains 12.5 MG/ML. This is a concentration. The Photon product gives 0.6 mL for one dose.

12.5 mg/mL × 0.6 mL = 7.5 mg

The two strength forms describe the same dose. The full package has 2.4 mL.

2.4 mL ÷ 0.6 mL = 4 doses

Failure behavior now

ConditionSystem result
The exact brand has a verified matchReturn the brand result.
The exact brand has no verified matchSearch the generic drug name.
One Photon row is malformedDrop that row and continue.
One Photon row has no NDC or no strengthDrop that row and continue.
An NDC has no RxNorm mappingDrop that row and continue.
An NDC maps only to a broad RxNorm conceptDrop that row and continue.
The row RxCUI does not match the request RxCUIsReject that row.
A row has the wrong strength, form, or routeReject that row.
All rows failReturn unmatched.
All rows fail, but one row is closeShow the most compatible row as nearest. Do not return it as a selectable match.
A row matches the controlled-drug blockDrop that row and record telemetry.

Warning: controlled-drug status

Do not treat the current controlled-drug check as the final design. Photon Clinical does not provide an authoritative controlled-drug field.

The new system keeps controlled status as unknown. It uses the existing controlled-drug name list as a temporary block.

The next controlled-drug change must add an authoritative data source. That work is separate from this medication fetch change.

Final comparison

QuestionBeforeNow
Photon catalogPhoton Management.Photon Clinical.
Exact brand in candidateNo dedicated field.Yes.
Exact brand orderNot explicit.Brand first, then generic fallback.
Name match proves identityA name-only row could stand without a verified row.No. A Clinical row needs an NDC-to-RxNorm identity link.
One malformed rowThe Management path dropped only that row.The Clinical path also drops only that row.
Missing requested strengthCould pass.Does not pass.
nearestAlphabetical order.Compatibility with brand, drug, strength, form, route, side, and OTC setting.
What AI Chief Resident selectsA returned handle.A returned handle.

Glossary

LLM
The model that reads the discussion and writes a structured medication request.
Photon Management catalog
A Photon API that manages catalog data. It did not return the treatments shown by the main EHR search.
Photon Clinical catalog
The Photon API that supplies choices in the main EHR prescription search.
RxNorm
A standard US drug dictionary.
RxCUI
An ID for one drug concept in RxNorm.
NDC
A medication product or package code. The system uses it to connect a Photon treatment to RxNorm.
Fully specified drug concept
A drug identity with drug, strength, and dose form. It can be generic or brand.
SCD
A fully specified generic drug concept in RxNorm.
SBD
A fully specified brand drug concept in RxNorm. RxNorm connects it to its related SCD.
Handle
A short internal ID for one verified Photon result.

Test command

Use this read-only command to run the real Chief Resident medication search with a structured request. It prints the exact tool input and the exact search result.

pnpm -C apps/api chief-resident:test-prescription-search \
  --generic-name tirzepatide \
  --brand-name Zepbound \
  --strength "7.5 mg/0.6 mL" \
  --form "pen injector" \
  --route subcutaneous \
  --brand-preference brand