Skip to main content
Version: v0.1.10

Generate vendor-quirk fixtures

A spec-clean fixture shows that a parser reads a correct message. A quirk fixture shows how it handles the deviations real vendor traffic carries: does it read the message, and does it raise the right warning?

Each quirk changes the structure of a message that otherwise reads back with no warnings, so that a parse with no profile raises exactly one intended warning code. The quirks are the ones the Cosyte parsers' own profile systems (defineProfile in @cosyte/hl7, defineCcdaProfile in @cosyte/ccda, defineAstmProfile in @cosyte/astm) describe as tolerable. Where a built-in public profile claims the deviation, the round trip also parses under that profile and reports whether it was suppressed or re-badged to PROFILE_QUIRK_APPLIED. Every quirk generator checks the intended warning before it returns, so it never hands you a fixture that does not raise it.

A quirk changes the shape of a message, never where its values come from, so a quirk fixture is as synthetic as any other. Quirks ship for HL7 v2, C-CDA, and ASTM; the quirk catalog lists every one.

HL7 v2: a vendor Z segment​

A site-defined Z segment is the usual HL7 v2 quirk. A parse with no profile flags it UNKNOWN_SEGMENT; a profile that declares the segment suppresses it. Here that is @cosyte/hl7's public visage imaging profile, which declares ZDS:

import { generateHl7Quirk, hl7QuirkRoundTrip } from "@cosyte/synth/hl7";

const rt = hl7QuirkRoundTrip(generateHl7Quirk({ seed: 12345, quirk: "unknown-zsegment" }));
rt.warnings; // => ["UNKNOWN_SEGMENT"]
rt.intendedWarningHeld; // => true
rt.withProfile?.tolerated; // => true

C-CDA: a deprecated LOINC code​

C-CDA profiles re-badge a tolerated deviation to PROFILE_QUIRK_APPLIED. A deprecated result LOINC code raises DEPRECATED_LOINC, and @cosyte/ccda's public smartScorecard profile re-badges it:

import { generateCcdaQuirk, ccdaQuirkRoundTrip } from "@cosyte/synth/ccda";

const rt = ccdaQuirkRoundTrip(generateCcdaQuirk({ seed: 12345, quirk: "deprecated-loinc" }));
rt.warnings; // => ["DEPRECATED_LOINC"]
rt.withProfile?.warnings; // => ["PROFILE_QUIRK_APPLIED"]
rt.withProfile?.tolerated; // => true

ASTM: a non-standard escape​

An ASTM &Z& escape raises ASTM_UNKNOWN_ESCAPE_SEQUENCE, and @cosyte/astm's public referenceCorpus profile, grounded in the open-source python-astm and SENAITE corpora, re-badges it:

import { generateAstmQuirk, astmQuirkRoundTrip } from "@cosyte/synth/astm";

const rt = astmQuirkRoundTrip(generateAstmQuirk({ seed: 12345, quirk: "unknown-escape" }));
rt.warnings; // => ["ASTM_UNKNOWN_ESCAPE_SEQUENCE"]
rt.withProfile?.tolerated; // => true

An unknown quirk name throws​

A quirk name outside the format's registry throws SYNTH_UNSUPPORTED_QUIRK. It is never ignored, and no fixture is made up for it:

import { generateHl7Quirk } from "@cosyte/synth/hl7";

let code = "";
try {
generateHl7Quirk({ seed: 1, quirk: "no-such-quirk" as never });
} catch (err) {
code = (err as { code?: string }).code ?? "";
}
code; // => "SYNTH_UNSUPPORTED_QUIRK"

Branch on err.code, never on err.message. The message is a fixed entry in the frozen SYNTH_FATAL_MESSAGES table and does not name the quirk you asked for: SynthError takes a code and no value, so nothing you pass reaches the thrown object. To see what a format accepts, read its registry directly: Object.keys(HL7_QUIRKS), Object.keys(CCDA_QUIRKS), Object.keys(ASTM_QUIRKS).

Build a quirk corpus​

Each format has a quirk corpus that cycles through every quirk in its registry. Its manifest lists the quirks applied, and each artifact's warnings holds the intended codes:

import { ccdaQuirkCorpus } from "@cosyte/synth/ccda";

const corpus = ccdaQuirkCorpus({ seed: 42 });
[...corpus.manifest.quirks].sort(); // => ["deprecated-code-system", "deprecated-loinc", "template-extension-absent"]

Choose the quirks with a profile​

defineSynthProfile names a set of quirks once, and a quirk corpus takes it as profile. Each format also exports a profile with every quirk it ships (hl7QuirkProfile, ccdaQuirkProfile, astmQuirkProfile):

import { defineSynthProfile } from "@cosyte/synth";
import { hl7QuirkCorpus } from "@cosyte/synth/hl7";

const labFeed = defineSynthProfile({ name: "lab-feed", quirks: ["unknown-escape"] });
const corpus = hl7QuirkCorpus({ seed: 1, count: 2, profile: labFeed });

corpus.manifest.quirks; // => ["unknown-escape"]
corpus.artifacts.map((a) => a.warnings); // => [["UNKNOWN_ESCAPE_SEQUENCE"], ["UNKNOWN_ESCAPE_SEQUENCE"]]

A profile that names a quirk outside the format's registry throws SYNTH_UNSUPPORTED_QUIRK, and one whose quirks is an empty list throws SYNTH_EMPTY_POOL. A profile that leaves quirks out does not choose the quirks: the corpus uses its quirks option, or every quirk. defineSynthProfile also accepts givenNames and familyNames and stores them on the profile, but no generator reads them in this release.

Where each quirk comes from​

Every quirk is grounded in a deviation that is documented in public, or in a parser's public profile: a published implementation guide, a vendor interface specification, or an open-source corpus that may be redistributed. A quirk that would need a private, vendor-attributed corpus is not shipped. Quirks for FHIR, X12, and NCPDP are not generated yet.