Skip to main content
Version: v0.1.10

Test your NCPDP handling with generated fixtures

In this tutorial we write a small piece of code that reads NCPDP pharmacy claims and test it with fixtures from @cosyte/synth. We start from one generated Telecom billing claim and end with a SCRIPT prescription whose prescriber identifiers we check. Every message is synthetic, so the tests can live in a public repository and print their input to a CI log.

You need @cosyte/synth and @cosyte/ncpdp installed (Installation). Each step is a complete program you can run on its own.

1. Write the code under test​

The code under test reads a Telecom request with @cosyte/ncpdp and pulls out the transaction code, the product dispensed and the quantity. Feed it a generated B1 billing claim. The generators on this subpath return the wire text itself:

import { claim, parseTelecom } from "@cosyte/ncpdp/telecom";
import { generateB1 } from "@cosyte/synth/ncpdp";

function summarizeClaim(wire: string) {
const transaction = parseTelecom(wire);
const view = claim(transaction);
return {
transactionCode: transaction.header.transactionCode,
product: view?.product?.id,
quantity: view?.quantityDispensed?.impliedDecimal,
};
}

const summary = summarizeClaim(generateB1({ seed: 12345 }));

summary.transactionCode; // => "B1"
summary.product !== undefined; // => true
summary.quantity !== undefined; // => true

2. Check what the fixture carries​

A generated value is not a value you typed, so assert on its kind rather than its spelling. The product comes from a small example pool whose NDCs use the labeler prefix 00000, which we invented, and the claim reads back with no warnings:

import { claim, parseTelecom } from "@cosyte/ncpdp/telecom";
import { generateB1 } from "@cosyte/synth/ncpdp";

const transaction = parseTelecom(generateB1({ seed: 12345 }));

claim(transaction)?.product?.id.startsWith("00000"); // => true
transaction.warnings.length; // => 0

3. Run every Telecom transaction through it​

A pharmacy system sends a reversal (B2) and a rebill (B3) as well as a billing (B1). generateTelecom takes the transaction code as data, so one loop covers all three:

import { parseTelecom } from "@cosyte/ncpdp/telecom";
import { generateTelecom } from "@cosyte/synth/ncpdp";

const codes = (["B1", "B2", "B3"] as const).map(
(code) => parseTelecom(generateTelecom(code, { seed: 777 })).header.transactionCode,
);

codes; // => ["B1", "B2", "B3"]

4. Check a SCRIPT prescription​

SCRIPT is the other NCPDP standard: XML ePrescribing. A NewRx carries the prescriber's NPI and DEA number, and neither may be a real one. isSyntheticNpi and isSyntheticDea check that each fails its check digit, so it cannot have been issued:

import { newRx, parseScript } from "@cosyte/ncpdp/script";
import { isSyntheticDea, isSyntheticNpi } from "@cosyte/synth";
import { generateNewRx } from "@cosyte/synth/ncpdp";

const message = parseScript(generateNewRx({ seed: 12345 }));
const ids = newRx(message)?.prescriber?.identification;

isSyntheticNpi(ids?.npi ?? ""); // => true
isSyntheticDea(ids?.deaNumber ?? ""); // => true
message.warnings.length; // => 0

5. Pin the seed​

A test that regenerates its fixture from a seed needs no fixture file, and its input does not change between runs, because the same seed gives the same bytes:

import { generateB1 } from "@cosyte/synth/ncpdp";

generateB1({ seed: 12345 }) === generateB1({ seed: 12345 }); // => true

The bytes come from @cosyte/ncpdp's builders and serializers, so pin the versions of @cosyte/synth and @cosyte/ncpdp in your lockfile as well as the seed.

What you built​

A reader for NCPDP Telecom claims, tested against a billing, a reversal and a rebill, plus a SCRIPT prescription whose prescriber identifiers cannot be real, each regenerated from a seed. None of it holds patient data.

Where to go next​