# SP20 — NPI Checksum + Tax ID Format Validation **Date:** 2026-06-21 **Branch:** `sp20-npi-validation` **Status:** Shipped **Scope:** Backend only. No frontend changes. --- ## 1. Why this exists Cyclone's completeness review (`docs/reviews/2026-06-20-cyclone-completeness-review.md` §3.1.10) flags this gap: > **No NPI validation.** NPIs are captured (`claim.party.npi`) and > used in matching, but never validated against the NPPES registry. > Bad NPIs (typos, deactivated) silently propagate. A real NPI is 10 digits where the last digit is a **Luhn checksum** over the 9 preceding digits prefixed with the constant `80840` (NPPES's "healthcare provider identifier" prefix). **Tax ID** format: 9 digits, optionally formatted `XX-XXXXXXX`. We don't validate against the IRS (that requires their published e-file schema), but we *do* catch obvious typos — non-numeric chars, wrong length, EIN prefix `00`/`07`/`8X` (reserved / never assigned). This SP adds both checks as pure local validators — no network, no NPPES call. Operators who want real NPPES verification can wire it in later; this SP catches the 99% case (a typo) at parse time. ## 2. Operator surface | Surface | Usage | |---------|-------| | Python | `from cyclone.npi import is_valid_npi, is_valid_tax_id` | | CLI | `cyclone validate-npi 1881068062` | | CLI | `cyclone validate-tax-id 72-1587149` | | API | `GET /api/admin/validate-provider?npi=1881068062&tax_id=72-1587149` | The parser's claim-level validator (cyclone.parsers.validator) gains a new R-rule that flags bad NPIs as a `validation.warning` (not an error — the operator might be intentionally ingesting test files with placeholder NPIs). ## 3. Files * `cyclone/npi.py` — new module (~120 LOC). `is_valid_npi()`, `is_valid_tax_id()`, `npi_checksum()`, plus the NPPES constant. * `cyclone.parsers.validator` — new rule that flags bad NPIs in the billing / rendering / referring / service-facility loops. * `cyclone.api` — new admin endpoint. * `cyclone.cli` — `validate-npi` + `validate-tax-id` subcommands. * Tests: `test_npi.py` (12), `test_api_validate_provider.py` (4), `test_cli_validate.py` (4) — 20 new tests. ## 4. Threat model NPIs and tax IDs are sensitive (PHI under HIPAA). The validators run locally; nothing leaves the process. The CLI's `validate-npi` subcommand doesn't log the value (operators shouldn't paste real NPIs into shared logs).