164 lines
6.2 KiB
Python
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="",
|
|
)
|