Files
cyclone/backend/src/cyclone/parsers/payer.py
T

164 lines
6.2 KiB
Python

"""Payer-specific configuration for the Cyclone parsers.
The same parser module can ingest 837P / 835 files from any payer, but
the validation rules differ. ``PayerConfig`` (837P) and ``PayerConfig835``
(835 ERA) carry the differences so the validator functions can be
payer-aware without branching in the parser.
"""
from __future__ import annotations
import re
from typing import Pattern
from pydantic import BaseModel, ConfigDict, Field
# Full CMS Place of Service code list (X12 837P CLM05-1).
# Two-digit zero-padded codes covering the entire CMS POS set as of 2026.
CMS_PLACE_OF_SERVICE_CODES: set[str] = {
"01", "02", "03", "04", "05", "06", "07", "08", "09",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
"30", "31", "32", "33", "34", "41", "42", "49", "50", "51",
"52", "53", "54", "55", "56", "57", "58", "59", "60", "61",
"62", "63", "64", "65", "66", "67", "68", "69", "70", "71",
"72", "73", "74", "75", "76", "77", "78", "79", "80", "81",
"82", "83", "84", "85", "86", "87", "88", "89", "90", "91",
"92", "93", "94", "95", "96", "97", "98", "99",
}
# --------------------------------------------------------------------------- #
# 837P (Professional Claim)
# --------------------------------------------------------------------------- #
class PayerConfig(BaseModel):
model_config = ConfigDict(frozen=True)
name: str
sbr09_claim_filing: str = "MC"
allowed_claim_frequencies: set[int] = Field(default_factory=lambda: {1, 7, 8})
require_ref_g1_for_adjustments: bool = False
allowed_bht06: set[str] = Field(default_factory=lambda: {"CH"})
payer_id: str = ""
payer_name: str = ""
no_patient_loop: bool = False
encounter_claim_in_same_batch: bool = False
# CLM05-2 Facility Code Qualifier. "B" = CMS Place of Service code.
allowed_facility_qualifiers: set[str] = Field(default_factory=lambda: {"B"})
# CLM05-1 Facility Type Code / Place of Service. Defaults to the full CMS POS list.
allowed_place_of_service_codes: set[str] = Field(
default_factory=lambda: set(CMS_PLACE_OF_SERVICE_CODES)
)
@classmethod
def co_medicaid(cls) -> "PayerConfig":
"""Defaults for Colorado Medical Assistance Program (FFS).
Source: ``docs/companionguides/837p.md``.
"""
return cls(
name="Colorado Medical Assistance Program",
sbr09_claim_filing="MC",
allowed_claim_frequencies={1, 7, 8},
# Lenient in v1 — see spec §9 R031.
require_ref_g1_for_adjustments=False,
allowed_bht06={"CH"},
payer_id="CO_TXIX",
payer_name="CO_TXIX",
no_patient_loop=True,
encounter_claim_in_same_batch=False,
allowed_facility_qualifiers={"B"},
allowed_place_of_service_codes=set(CMS_PLACE_OF_SERVICE_CODES),
)
@classmethod
def generic_837p(cls) -> "PayerConfig":
"""Relaxed defaults for an unknown payer. Structural rules only."""
return cls(
name="Generic 837P",
sbr09_claim_filing="",
allowed_claim_frequencies={1, 2, 3, 4, 5, 6, 7, 8, 9},
require_ref_g1_for_adjustments=False,
allowed_bht06={"CH", "RP"},
payer_id="",
payer_name="",
no_patient_loop=False,
encounter_claim_in_same_batch=True,
allowed_facility_qualifiers={"B"},
allowed_place_of_service_codes=set(CMS_PLACE_OF_SERVICE_CODES),
)
# --------------------------------------------------------------------------- #
# 835 ERA (Health Care Claim Payment/Advice)
# --------------------------------------------------------------------------- #
class PayerConfig835(BaseModel):
"""Payer-specific configuration for the 835 ERA parser.
Mirrors :class:`PayerConfig` but carries the 835-specific fields
(handling codes, status codes, CO Medicaid tax IDs / health plan ID).
Kept in a separate class — not a subclass — because the 835 and 837P
validation surfaces don't share enough structure to make inheritance
pay off.
"""
model_config = ConfigDict(frozen=True, extra="ignore")
name: str
allowed_handling_codes: set[str] = Field(
default_factory=lambda: {"C", "D", "I", "X"}
)
allowed_status_codes: set[str] = Field(
default_factory=lambda: {"1", "2", "3", "4", "19", "20", "21", "22", "23", "25"}
)
# CO Medicaid sends BPR10 with or without the IRS hyphen — accept both
# the dashed form ("81-1725341", "84-0644739") and the un-dashed
# 10-digit form ("811725341", "840644739") that newer production 835s
# use. Also accept "1811725341" (no leading 8, no hyphen), which is
# what real CO Medicaid data sends on Information Only (BPR01="I") ERAs.
expected_payer_tax_ids: set[str] = Field(default_factory=set)
# CO uses "7912900843" as the Health Plan ID on N104.
expected_payer_health_plan_id: str = ""
# Regex matched against Payer.name (N102). Example: r"^CO_(TXIX|BHA)$".
payer_name_pattern: str = ""
def payer_name_regex(self) -> Pattern[str] | None:
"""Compile ``payer_name_pattern`` (or return ``None`` if unset/blank)."""
if not self.payer_name_pattern:
return None
return re.compile(self.payer_name_pattern)
@classmethod
def co_medicaid_835(cls) -> "PayerConfig835":
"""Defaults for Colorado Medical Assistance Program (835).
Source: ``docs/companionguides/835.md``.
"""
return cls(
name="Colorado Medical Assistance Program (835)",
expected_payer_tax_ids={
"81-1725341",
"811725341",
"84-0644739",
"840644739",
"1811725341",
},
expected_payer_health_plan_id="7912900843",
payer_name_pattern=r"^CO_(TXIX|BHA)$",
)
@classmethod
def generic_835(cls) -> "PayerConfig835":
"""Relaxed defaults for an unknown payer. Structural rules only."""
return cls(
name="Generic 835",
expected_payer_tax_ids=set(),
expected_payer_health_plan_id="",
payer_name_pattern="",
)