NDC-11 vs NDC-10 Parsing Standards

Deterministic NDC-10 and NDC-11 normalization for pharmacy inventory reconciliation, EDI ingestion, and DEA controlled-substance logging. Parse, validate, and audit reliably.

Regulatory Context & Compliance Boundaries

National Drug Code (NDC) normalization is the foundational control point for pharmacy inventory reconciliation, EDI ingestion, and DEA controlled-substance logging. The FDA mandates a 10-digit canonical format (5-3-2, 5-4-1, or 4-4-2) for labeling and directory publication, while wholesale distributors and pharmacy management systems routinely transmit 11-digit, zero-padded variants (5-4-2) inside X12 832, 845, and 852 transactions. When these representations are reconciled against one another without a deterministic conversion rule, the result is perpetual sync failures, false-positive diversion alerts, and fragmented audit trails. This pipeline operates within the broader Core Architecture & DEA Compliance Frameworks, which coordinates classification, ingestion, offline reconciliation, and audit logging into isolated, cryptographically verifiable data streams.

Normalization logic must satisfy three regulatory domains simultaneously, and each one constrains how a parsed code may be stored, transmitted, and resolved:

Authority Citation Constraint imposed on NDC handling
FDA 21 CFR § 207.33 Defines NDC structure, mandates directory registration, and prohibits unvalidated format conversion in published data
DEA 21 CFR § 1304.21 Requires exact product identification for Schedule II-V substances; ambiguous resolution during perpetual inventory counts is non-compliant
HHS 21 CFR § 1304.22 Two-year minimum retention of controlled-substance transaction records, including the identifier used at point of ingestion
HIPAA 45 CFR § 164.312(e)(1) Transmission integrity and audit logging for EDI payloads carrying inventory metadata adjacent to PHI

The practical consequence is that an NDC is never “just a number” inside a regulated system. Each parse event is a compliance event: the raw input, the conversion rule applied, and the canonical output must all survive to inspection. A code that resolves correctly today but cannot be reproduced from its stored provenance fails a DEA audit just as surely as one that resolved wrong.

NDC Format Specification

The NDC is a three-segment identifier — Labeler, Product, Package — assigned by the FDA. The labeler segment is always five digits. The remaining segments vary, which is the entire source of the parsing problem. The 10-digit form is the format the FDA publishes; the 11-digit form is the zero-padded representation that billing and EDI systems (notably HIPAA-standard 5-4-2) expect.

Padding NDC-10 to the 11-digit 5-4-2 layout Three rows compare each FDA 10-digit configuration with its zero-padded 11-digit 5-4-2 form. A 4-4-2 source gains a leading zero in the five-digit Labeler segment; a 5-3-2 source gains a leading zero in the four-digit Product segment; a 5-4-1 source gains a leading zero in the two-digit Package segment. The shaded cell marks the single inserted zero in each case. Padding NDC-10 to the 11-digit 5-4-2 layout Shaded digit = inserted leading zero. Padding NDC-10 → NDC-11 is deterministic; the reverse is not. NDC-10 (FDA canonical) NDC-11 (5-4-2 billing) 1234 5678 90 01234 5678 90 +0 Source 4-4-2 → prepend 0 to the 5-digit Labeler 12345 678 90 12345 0678 90 +0 Source 5-3-2 → prepend 0 to the 4-digit Product 12345 6789 0 12345 6789 00 +0 Source 5-4-1 → prepend 0 to the 2-digit Package
10-digit format Labeler Product Package Zero-pad rule to reach 11-digit (5-4-2)
4-4-2 4 4 2 Prepend 0 to the labeler segment
5-3-2 5 3 2 Prepend 0 to the product segment
5-4-1 5 4 1 Prepend 0 to the package segment

The conversion is only deterministic in one direction. Padding a known 10-digit code to 11 digits is unambiguous because the source configuration tells you exactly where the zero goes. The reverse — stripping a leading zero from an unlabeled 11-digit string — is not self-describing: a code like 00069-4200-30 could legitimately have originated from a 4-4-2, 5-3-2, or 5-4-1 source. Resolving that ambiguity requires the FDA NDC Directory, not arithmetic. Any parser that guesses is a latent compliance defect. The precise expressions that enforce these boundaries are catalogued in NDC parsing regex patterns for Python.

Deterministic Parsing Workflow

The following workflow enforces deterministic sanitization, classification, conversion, and audit commitment. Each step executes synchronously before any inventory sync or diversion-detection engine consumes the record. The pipeline is a state machine: a record advances to the next state only on success and is routed to quarantine on any ambiguity.

Deterministic NDC normalization state machine A left-to-right state flow. A raw NDC from an EDI feed passes through Sanitize, then Length Classify which branches on a 10- or 11-digit count, then Segment Extraction, Directory-backed Conversion, Schedule Tagging, an Immutable Commit to an append-only ledger, and Sync Dispatch to downstream subsystems. A record advances only on success; an invalid length at the classify stage or an ambiguous or unlisted code at the conversion stage branches down into a Quarantine Queue held for deferred validation under the fallback routing path. Deterministic NDC normalization — advance on success, quarantine on ambiguity Raw Ingestion Sanitize Length Classify Segment Extract Directory Convert Schedule Tag Immutable Commit Sync Dispatch EDI 832 / 852 strip non-digits branch 10 / 11 5 · 3-4 · 1-2 FDA-confirmed strip DEA II–V tag append-only ledger to reconciliation raw input ^\d{10,11}$ digit count §207.33 §1304.21 schedule meta SHA-256 · §1304.22 EDI 852 / 846 invalid length ambiguous / unlisted Quarantine Queue deferred validation, never guessed fallback routing · offline sync advance on success fail → quarantine
  1. Raw ingestion and sanitization. Strip whitespace, hyphens, and non-numeric characters. Reject payloads containing alphabetic characters, control codes, or unexpected separators before any length logic runs.
  2. Length classification. Route on digit count. An 11-digit string enters the normalization path; a 10-digit string enters direct validation; anything else is rejected.
  3. Segment extraction. Parse into Labeler (5), Product (3 or 4), and Package (1 or 2) segments, validating segment boundaries against FDA structural rules.
  4. Directory-backed conversion. For an 11-digit input, resolve the source configuration against the FDA NDC Directory rather than assuming a single strip rule. Where the directory is authoritative, apply the matching zero-strip; where it is silent or returns multiple candidates, trigger a hard failure and quarantine.
  5. Directory cross-reference. Validate the resulting 10-digit code against the FDA NDC Directory API or a cached snapshot. Reject unlisted, discontinued, or inactive codes.
  6. Controlled-substance tagging. If the product is scheduled, attach DEA schedule metadata and route to the DEA Schedule II-V Classification Mapping engine for diversion-threshold calibration.
  7. Immutable commit. Write the normalized record, the raw input, the conversion rule applied, a SHA-256 audit hash, and a UTC timestamp to the append-only ledger.
  8. Sync dispatch. Push the validated payload to inventory reconciliation, purchasing, or controlled-substance logging subsystems.

Production Python Implementation

Production-grade NDC parsing prioritizes type safety, deterministic behavior, and structured audit logging. The implementation below uses a frozen dataclass for the immutable record, strict numeric validation, explicit segment extraction, and a SHA-256 hash that binds the raw input to its normalized output. The hash is what makes the conversion reproducible under inspection: given the stored raw string and rule, an auditor can recompute the digest and confirm the record was never altered.

python
import re
import hashlib
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Literal, Optional

# Structured logging only — never emit raw PHI-adjacent payloads at INFO.
logger = logging.getLogger("ndc_normalizer")

SegmentFormat = Literal["4-4-2", "5-3-2", "5-4-1", "5-4-2"]


@dataclass(frozen=True)
class NDCRecord:
    raw_input: str
    normalized: str
    segment_format: SegmentFormat
    conversion_applied: bool
    timestamp: str = field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat()
    )

    @property
    def audit_hash(self) -> str:
        """SHA-256 over the provenance fields for tamper-evident ledger entries."""
        payload = f"{self.raw_input}|{self.normalized}|{self.segment_format}|{self.timestamp}"
        return hashlib.sha256(payload.encode("utf-8")).hexdigest()


class AmbiguousNDCError(ValueError):
    """Raised when an 11-digit code cannot be resolved without the FDA directory."""


class NDCNormalizer:
    # Numeric-only, exactly 10 or 11 digits.
    _VALID_PATTERN = re.compile(r"^\d{10,11}$")
    # 11-digit canonical billing layout (5-4-2).
    _SEGMENT_11 = re.compile(r"^(\d{5})(\d{4})(\d{2})$")

    @classmethod
    def sanitize(cls, raw: str) -> str:
        """Strip non-numeric characters and validate length."""
        cleaned = re.sub(r"[^\d]", "", raw)
        if not cls._VALID_PATTERN.match(cleaned):
            # Log the failure class, not the raw value, to avoid PHI leakage.
            logger.warning("Rejected NDC payload: invalid length or characters")
            raise ValueError("Invalid NDC payload")
        return cleaned

    @classmethod
    def normalize(cls, raw: str, source_format: Optional[SegmentFormat] = None) -> NDCRecord:
        cleaned = cls.sanitize(raw)

        if len(cleaned) == 10:
            # Canonical 10-digit input; no conversion required.
            return NDCRecord(
                raw_input=raw,
                normalized=cleaned,
                segment_format=source_format or "5-3-2",
                conversion_applied=False,
            )

        match = cls._SEGMENT_11.match(cleaned)
        if not match:
            raise ValueError("Malformed 11-digit NDC structure")
        labeler, product, package = match.groups()

        # Deterministic strip ONLY when the directory-derived source format is known.
        # Per 21 CFR § 207.33, the reverse conversion is not self-describing.
        if source_format == "4-4-2" and labeler.startswith("0"):
            normalized, seg = f"{labeler[1:]}{product}{package}", "4-4-2"
        elif source_format == "5-3-2" and product.startswith("0"):
            normalized, seg = f"{labeler}{product[1:]}{package}", "5-3-2"
        elif source_format == "5-4-1" and package.startswith("0"):
            normalized, seg = f"{labeler}{product}{package[1:]}", "5-4-1"
        else:
            logger.error("Unresolvable 11-digit NDC; routing to quarantine")
            raise AmbiguousNDCError(
                "11-digit NDC requires FDA-directory-confirmed source format"
            )

        record = NDCRecord(
            raw_input=raw,
            normalized=normalized,
            segment_format=seg,
            conversion_applied=True,
        )
        logger.info("NDC normalized format=%s hash=%s", seg, record.audit_hash)
        return record

The signature carries an explicit source_format resolved upstream from the FDA directory. This is the single most important design decision on the page: it converts the ambiguous reverse strip into a deterministic one and makes the unknown case a loud AmbiguousNDCError rather than a silent wrong answer.

Compliance Mapping & Audit Boundaries

The normalizer is a compliance gate, not a utility function. Every transformation it performs must be traceable to satisfy DEA and FDA inspection protocols. When a record is produced, its audit_hash and provenance fields are written to the append-only ledger before any downstream subsystem reads it, and the data type is governed by the controls defined in the Pharmacy Security Framework Architecture — encryption in transit, role-based access to the ledger, and access logging on every read.

Compliance requirement Citation Implementation artifact
Exact product identification 21 CFR § 1304.21 source_format-driven deterministic conversion; AmbiguousNDCError on unresolved codes
Tamper-evident records 21 CFR § 1304.22 NDCRecord.audit_hash (SHA-256 over raw, normalized, format, timestamp)
No unvalidated conversion in published data 21 CFR § 207.33 Directory cross-reference step before commit
Transmission integrity / audit logging 45 CFR § 164.312(e)(1) Structured logging of format and hash only — never raw payloads

What defines the edge of the auditable surface — which events enter the ledger and which fields are retained — is governed by the rules in Audit Boundary Definition & Scope. For scheduled products, the normalized 10-digit code becomes the primary key for diversion analytics, so an incorrect strip does not merely corrupt one row — it silently de-links a Schedule II product from its threshold rules.

Error Handling & Offline Resilience

Deterministic parsing must tolerate network degradation, directory-API latency, and malformed EDI payloads without halting the broader ingestion pipeline. When conversion encounters ambiguous zero-padding or an unlisted directory entry, the record is isolated in a quarantine queue while clean records continue to flow. The quarantine, retry, and deferred-validation mechanics are shared with the Fallback Routing for Offline Sync subsystem, which preserves the original EDI payload in a secure staging queue so that inventory counts stay accurate and premature diversion alerts are suppressed.

During offline windows, the normalizer falls back to a cached FDA directory snapshot. A code that cannot be resolved locally is flagged for deferred validation rather than guessed. Once connectivity is restored, deferred records re-enter the standard workflow, and every retry attempt is logged with its own audit hash for compliance review. This guarantees that an offline period can never introduce an unprovenanced code into the controlled-substance ledger.

Downstream Integration

Normalized NDC records are consumed by several subsystems, and each depends on the 10-digit code being canonical and reproducible:

Because all three consumers key on the same canonical output, the normalizer is the upstream guarantee that keeps reconciliation, retry, and scheduling consistent across the wider Data Ingestion and Inventory Sync Workflows.

Frequently Asked Questions

Why not just strip the leading zero from any 11-digit NDC?

Because the reverse conversion is not self-describing. An 11-digit 5-4-2 string can correspond to a 4-4-2, 5-3-2, or 5-4-1 source, and only the FDA NDC Directory identifies which. Blind stripping produces codes that resolve to the wrong product and silently de-link scheduled substances from their 21 CFR § 1304.21 threshold rules.

Should the normalized code be stored as 10-digit or 11-digit?

Store the FDA-canonical 10-digit code as the primary key and retain the raw input alongside it. Billing systems can re-pad to 11 digits deterministically when needed, but the directory is published in 10-digit form, so it is the only representation that supports a directory cross-reference at audit time.

What happens to a code the FDA directory cannot resolve?

It raises AmbiguousNDCError, is written to the quarantine queue, and is held for deferred validation through the Fallback Routing for Offline Sync path. It is never committed to the controlled-substance ledger on a guess.

How does the audit hash satisfy DEA retention rules?

21 CFR § 1304.22 requires tamper-evident retention of controlled-substance transaction records for at least two years. The SHA-256 audit_hash binds the raw input, normalized output, segment format, and timestamp; an inspector can recompute the digest from the stored fields and confirm the record was never altered.

Explore deeper

Related topics