Cookbook
Task-oriented recipes for the common reads. Each one is a few lines over a parsed message, using the
real subpath exports: @cosyte/ncpdp/script for ePrescribing XML, @cosyte/ncpdp/telecom for the
PBM claim wire, @cosyte/ncpdp/common for the shared value wrappers.
The rules underneath every recipe are the same: the parser is lenient (vendor quirks become
stable-coded warnings, not failures), the model is immutable, and safety-critical dispositions
are fail-safe: a failure is never quietly read as a success, and money is never a float.
All XML and wire samples below are synthetic. Fixtures must never carry real PHI: no real patient names, member IDs, prescriber NPIs, or NDCs.
Parse a SCRIPT NewRx
Read a NewRx ePrescribing message: header, patient, prescribed medication, and the coded product.
parseScript never throws on a vendor quirk. It collects warnings and reads best-effort. newRx
projects the NewRx body, or returns undefined for any other transaction.
import { parseScript, newRx } from "@cosyte/ncpdp/script";
const xml = `<Message version="2017071">
<Header><MessageID>SYNTH-MSG-0001</MessageID></Header>
<Body><NewRx>
<Patient><HumanPatient>
<Name><LastName>DOE</LastName><FirstName>JANE</FirstName></Name>
</HumanPatient></Patient>
<MedicationPrescribed>
<DrugDescription>Amoxicillin 500 MG Oral Capsule</DrugDescription>
<DrugCoded><ProductCode Qualifier="ND">00000000001</ProductCode></DrugCoded>
<Quantity><Value>30</Value></Quantity>
<Sig><SigText>Take 1 capsule by mouth three times daily for 10 days.</SigText></Sig>
</MedicationPrescribed>
</NewRx></Body>
</Message>`;
const msg = parseScript(xml);
msg.header.messageId; // => "SYNTH-MSG-0001"
const rx = newRx(msg); // the NewRx body, or undefined for another transaction
rx?.patient?.name?.lastName; // => "DOE"
rx?.medication?.description; // => "Amoxicillin 500 MG Oral Capsule"
rx?.medication?.coded?.productCode?.value; // => "00000000001"
rx?.medication?.coded?.productCode?.system; // => "NDC"
rx?.medication?.quantity?.value?.source; // => "30"
- Lenient by default. Vendor quirks (own-text dates, alternate element shapes, an absent version)
become
warningswith an XPath position, not failures. Only unrecoverable structural corruption throws a typedNcpdpScriptParseError: empty input, non-XML, a non-<Message>root, a<!DOCTYPE>/<!ENTITY>payload (the XXE boundary), or a pre-XML legacy version. - Coded product carries its own system.
coded.productCode.systemis recognized from the wire qualifier ("NDC","RXNORM","SNOMED", …); the rawvalueis always kept verbatim. - Warnings never carry field values. Each warning is a stable
codeplus an XPathposition, never patient or drug data.
Read a SCRIPT response (Status / Error / Verify)
Every SCRIPT transaction is answered. The response spine reads the three acknowledgment transactions
and exposes the disposition without ever reading an Error as a success.
import { parseScript, status, error, verify } from "@cosyte/ncpdp/script";
const responseXml = `<Message version="2017071">
<Header><RelatesToMessageID>SYNTH-MSG-0002</RelatesToMessageID></Header>
<Body><Error><Code>900</Code><Description>Prescriber identifier could not be validated.</Description></Error></Body>
</Message>`;
const msg = parseScript(responseXml);
msg.disposition; // "success" (Status) | "error" (Error) | "verify" (Verify) | undefined
msg.correlatesTo; // "SYNTH-MSG-0002": the answered request's MessageID (<RelatesToMessageID>)
error(msg)?.code; // "900": the Error code, verbatim; never reformatted or looked up
status(msg)?.description; // the positive-ack description, verbatim (undefined on an Error)
verify(msg)?.code;
- An
Errornever reads as success.dispositionis derived only from the response body kind, so a failure cannot be coerced to"success":status(msg)isundefinedon an Error. If a malformed message carries more than one response body, the most conservative disposition (Error first) wins andNCPDP_SCRIPT_RESPONSE_AMBIGUOUS_DISPOSITIONis raised. correlatesToties the answer to its request. It reads<RelatesToMessageID>verbatim so you can match the acknowledgment back to the NewRx you sent.- Codes and descriptions are surfaced verbatim.
<Code>,<DescriptionCode>, and<Description>are read as-is; the library bundles no NCPDP code→meaning table.
Read a Telecom PBM response (paid / rejected)
The PBM answers a claim with a response transmission. parseTelecom detects the response shape
automatically (it leads with the Version/Release, not the routing BIN); adjudication lifts the
outcome. The same reader serves B1 billing, B2 reversal, B3 rebill, and E1 eligibility responses.
import { parseTelecom, adjudication } from "@cosyte/ncpdp/telecom";
const t = parseTelecom(rawResponse); // kind: "response" (detected, not configured)
const a = adjudication(t); // undefined for a request transmission
a?.status?.disposition; // "paid" | "rejected" | "captured" | "approved" | "duplicate" | "unknown"
a?.status?.rejectCodes; // every Reject Code (511-FB), verbatim, in wire order (none dropped)
a?.status?.statusConflict; // true when the status field claimed paid but a reject was present
a?.pricing?.patientPayAmount?.amount; // "10.00": implied 2-place decimal, string-wise (never a float)
a?.pricing?.totalAmountPaid?.amount; // "45.00"
a?.dur; // every returned DUR/PPS alert: one per occurrence, never collapsed
- A reject always wins.
dispositionis a total function over the Transaction Response Status (112-AN) and the reject codes together. If any reject is present the disposition is"rejected"even when the status field claims paid. A consumer is never told a rejected claim was paid. The self-contradiction surfaces viaNCPDP_TELECOM_STATUS_CONFLICTandstatus.statusConflict. An unrecognized status reads"unknown", never paid. - Reject codes are verbatim. Each is kept in wire order; an unrecognized code is preserved with
known: false(NCPDP_TELECOM_UNKNOWN_REJECT_CODE) rather than dropped. - Money is never a float. Every dollar amount carries an implied 2-place decimal (and an optional
zoned-decimal overpunch sign); both are interpreted string-wise with the verbatim
sourcekept, so binary floating point can never corrupt a paid amount. Anything unexpected is preserved withisValid: falseand no interpreted amount. Money is never guessed.
Decode the structured SIG (lossy, labeled)
A medication's directions can arrive as free text and as a structured <Sig>. The structured
decode is best-effort and explicitly lossy: the free-text sigText stays the source of truth and
is always preserved verbatim; the structured view is additive and every field is provenance-tagged.
import { parseScript, newRx } from "@cosyte/ncpdp/script";
const sig = newRx(parseScript(xml))?.medication?.sig;
sig?.sigText; // the free-text directions, verbatim (ALWAYS authoritative)
sig?.hasStructuredData; // false when the <Sig> carried only free text
sig?.route.provenance; // "coded" | "derived" | "absent"
sig?.route.code?.system; // "SNOMED" | "NCI" | … when coded
sig?.route.text; // verbatim text when present
sig?.dose.text; // the dose quantity, string-preserved (never a float, never guessed)
- The free text is authoritative and never overwritten. When the structured dose and the free text disagree, both are surfaced as-is. The library never collapses the disagreement into one answer.
- Per-field provenance. Every component (
doseDeliveryMethod,dose,doseUnitOfMeasure,route,siteOfAdministration,administrationTiming,duration,vehicle,indication,maximumDoseRestriction) is taggedcoded/derived/absent. An absent field is never inferred from the free text. - Ambiguous doses are never guessed. If a dose structure is present but no unambiguous quantity can
be read,
doseis surfaced asabsentandNCPDP_SCRIPT_SIG_AMBIGUOUS_DOSEis raised. Whenever any structured component decodes,NCPDP_SCRIPT_SIG_STRUCTURED_LOSSYflags the additive, lossy view. - Decode-only. v1 does not generate a SIG from structure, and does not parse arbitrary natural-language directions. See Structured SIG spec notes.
Read a Telecom B1 claim
The Telecommunication standard is the pharmacy-to-PBM claim protocol: a fixed positional Transaction
Header followed by FS/GS/RS control-character-framed, field-id-keyed segments. parseTelecom decodes
the header and segments; claim lifts the safety-relevant B1/B2/B3 request fields.
import { parseTelecom, claim } from "@cosyte/ncpdp/telecom";
const t = parseTelecom(raw); // raw: string | Buffer (latin1 by default)
t.header.transactionCode; // "B1"
t.warnings; // stable, byte-offset-positioned tolerance warnings, never throws on quirks
const c = claim(t); // the B1/B2/B3 request view, or undefined when no segments decoded
c?.product?.id; // Product/Service ID (e.g. the NDC), verbatim
c?.product?.qualifierMeaning; // "NDC" when the qualifier is recognized
c?.quantityDispensed?.source; // Quantity Dispensed, verbatim
c?.quantityDispensed?.impliedDecimal; // "30.000": implied 3-place decimal, applied string-wise
c?.daysSupply?.source; // decimal-safe, never a float
c?.prescriptionReferenceNumber; // the Rx reference, verbatim
c?.cardholderId; // PHI: synthetic only in fixtures
- Quantity is never a float. Quantity Dispensed carries an implied 3-place decimal; it is scaled
string-wise so binary floating point can never corrupt the value, and the verbatim
sourceis kept. - Versions are not guessed. Only vD.0 is decoded against the fixed offsets. An F6 stamp is
recognized but not decoded (
NCPDP_TELECOM_VF6_NOT_DECODED); any other stamp isNCPDP_TELECOM_UNSUPPORTED_VERSION. A non-empty body with no framing bytes isNCPDP_TELECOM_INVALID_FRAMING. A separator is never guessed. - Nothing is dropped. Unknown segments/fields, a missing
AM, and malformed tokens are preserved verbatim and warned. Only the first transaction is decoded. See Telecom spec notes.
Next
- Read the API reference for every export, generated from source.
- The README covers the lifecycle transactions (renewal / change / cancel), compound / COB / DUR / prior-authorization reads, and the spec-clean serializers and builders.