feat: citation rule IDs on every documentation requirement (plan 02, P2)
core/rules.py format-tolerant readers + stable registry (R001-R004 plan-scoped, R100-R102 universal per LCD L33822). payer_rules.json wrapped in the same commit as both engine readers (coupling hazard closed). Every DocItem now cites the rule that decided it; NOT_EVALUATED cites nothing. Independent audit: SHIP (registry-drift test added per its L2 finding). 138 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
242e54e01a
commit
d24340c47a
8 changed files with 216 additions and 37 deletions
|
|
@ -180,6 +180,9 @@ class ReadinessItemOut(BaseModel):
|
||||||
quality: str # GOOD | PENDING | BAD | NONE
|
quality: str # GOOD | PENDING | BAD | NONE
|
||||||
value: Optional[str] = None
|
value: Optional[str] = None
|
||||||
satisfied: bool
|
satisfied: bool
|
||||||
|
# Citation: the payer_rules.json rule that decided required_state.
|
||||||
|
# None when NOT_EVALUATED (no rule fired, nothing to cite).
|
||||||
|
rule_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class RecordOut(BaseModel):
|
class RecordOut(BaseModel):
|
||||||
|
|
@ -427,6 +430,7 @@ def _to_record_out(
|
||||||
quality=i.quality.value,
|
quality=i.quality.value,
|
||||||
value=i.value,
|
value=i.value,
|
||||||
satisfied=i.satisfied,
|
satisfied=i.satisfied,
|
||||||
|
rule_id=i.rule_id,
|
||||||
)
|
)
|
||||||
for i in readiness.doc_items
|
for i in readiness.doc_items
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
{
|
{
|
||||||
"_comment": "Wear-day rules by device type and payer. Updated 2026-06-07. Run quarterly review per compliance checklist.",
|
"_comment": "Wear-day rules by device type and payer. Updated 2026-07-07 (P2: plan-scoped keys wrapped with citation rule_ids). Run quarterly review per compliance checklist.",
|
||||||
|
"_rule_registry": {
|
||||||
|
"R001": "visit_renewal_days — visit renewal window in days (standard: 180)",
|
||||||
|
"R002": "refill_window_days — resupply window in days before coverage end (standard: 30; legacy code key name retained)",
|
||||||
|
"R003": "pa_required — prior authorization required for this plan type",
|
||||||
|
"R004": "pecos_required — PECOS enrollment verification required for this plan type",
|
||||||
|
"R100": "swo_required — SWO is a universal claim requirement per LCD L33822",
|
||||||
|
"R101": "visit_required — qualifying visit is a universal requirement per LCD L33822",
|
||||||
|
"R102": "diagnosis_required — diagnosis (ICD-10) is a universal requirement per LCD L33822"
|
||||||
|
},
|
||||||
"devices": {
|
"devices": {
|
||||||
"dexcom_g6": {
|
"dexcom_g6": {
|
||||||
"display_name": "Dexcom G6",
|
"display_name": "Dexcom G6",
|
||||||
|
|
@ -31,42 +40,42 @@
|
||||||
},
|
},
|
||||||
"payer_rules": {
|
"payer_rules": {
|
||||||
"medicare": {
|
"medicare": {
|
||||||
"visit_renewal_days": 180,
|
"visit_renewal_days": { "value": 180, "rule_id": "R001" },
|
||||||
"refill_window_days": 30,
|
"refill_window_days": { "value": 30, "rule_id": "R002" },
|
||||||
"pa_required": false,
|
"pa_required": { "value": false, "rule_id": "R003" },
|
||||||
"pecos_required": true,
|
"pecos_required": { "value": true, "rule_id": "R004" },
|
||||||
"_note": "Medicare FFS. PA not required (CGM coverage expansion 2023). Face-to-face visit required every 6 months. CMS-1828-F: suppliers at >=90% affirmation rate may qualify for PA exemption from June 1, 2026.",
|
"_note": "Medicare FFS. PA not required (CGM coverage expansion 2023). Face-to-face visit required every 6 months. CMS-1828-F: suppliers at >=90% affirmation rate may qualify for PA exemption from June 1, 2026.",
|
||||||
"covered_devices": ["dexcom_g6", "dexcom_g7", "freestyle_libre_2", "freestyle_libre_3"]
|
"covered_devices": ["dexcom_g6", "dexcom_g7", "freestyle_libre_2", "freestyle_libre_3"]
|
||||||
},
|
},
|
||||||
"medicare_advantage": {
|
"medicare_advantage": {
|
||||||
"visit_renewal_days": 180,
|
"visit_renewal_days": { "value": 180, "rule_id": "R001" },
|
||||||
"refill_window_days": 30,
|
"refill_window_days": { "value": 30, "rule_id": "R002" },
|
||||||
"pa_required": true,
|
"pa_required": { "value": true, "rule_id": "R003" },
|
||||||
"pecos_required": true,
|
"pecos_required": { "value": true, "rule_id": "R004" },
|
||||||
"_note": "Medicare Advantage — PA required, varies by plan. Verify plan-specific rules.",
|
"_note": "Medicare Advantage — PA required, varies by plan. Verify plan-specific rules.",
|
||||||
"covered_devices": ["dexcom_g6", "dexcom_g7", "freestyle_libre_2", "freestyle_libre_3"]
|
"covered_devices": ["dexcom_g6", "dexcom_g7", "freestyle_libre_2", "freestyle_libre_3"]
|
||||||
},
|
},
|
||||||
"medicaid": {
|
"medicaid": {
|
||||||
"visit_renewal_days": 180,
|
"visit_renewal_days": { "value": 180, "rule_id": "R001" },
|
||||||
"refill_window_days": 30,
|
"refill_window_days": { "value": 30, "rule_id": "R002" },
|
||||||
"pa_required": true,
|
"pa_required": { "value": true, "rule_id": "R003" },
|
||||||
"pecos_required": false,
|
"pecos_required": { "value": false, "rule_id": "R004" },
|
||||||
"_note": "PA required — Keystone First, UPMC Health Plan, Highmark BCBS require PA. NJ FamilyCare, Horizon BCBS NJ require PA.",
|
"_note": "PA required — Keystone First, UPMC Health Plan, Highmark BCBS require PA. NJ FamilyCare, Horizon BCBS NJ require PA.",
|
||||||
"covered_devices": []
|
"covered_devices": []
|
||||||
},
|
},
|
||||||
"commercial": {
|
"commercial": {
|
||||||
"visit_renewal_days": 180,
|
"visit_renewal_days": { "value": 180, "rule_id": "R001" },
|
||||||
"refill_window_days": 30,
|
"refill_window_days": { "value": 30, "rule_id": "R002" },
|
||||||
"pa_required": true,
|
"pa_required": { "value": true, "rule_id": "R003" },
|
||||||
"pecos_required": false,
|
"pecos_required": { "value": false, "rule_id": "R004" },
|
||||||
"_note": "Commercial payer rules vary by plan. PA required by default — verify per plan.",
|
"_note": "Commercial payer rules vary by plan. PA required by default — verify per plan.",
|
||||||
"covered_devices": []
|
"covered_devices": []
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"visit_renewal_days": 180,
|
"visit_renewal_days": { "value": 180, "rule_id": "R001" },
|
||||||
"refill_window_days": 30,
|
"refill_window_days": { "value": 30, "rule_id": "R002" },
|
||||||
"pa_required": true,
|
"pa_required": { "value": true, "rule_id": "R003" },
|
||||||
"pecos_required": false,
|
"pecos_required": { "value": false, "rule_id": "R004" },
|
||||||
"covered_devices": []
|
"covered_devices": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -328,8 +328,10 @@ def calculate_coverage(
|
||||||
rule_version=RULE_VERSION,
|
rule_version=RULE_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
visit_renewal_days = payer_config.get("visit_renewal_days")
|
from core.rules import rule_value
|
||||||
refill_window_days = payer_config.get("refill_window_days", 30)
|
|
||||||
|
visit_renewal_days = rule_value(payer_config, "visit_renewal_days")
|
||||||
|
refill_window_days = rule_value(payer_config, "refill_window_days", 30)
|
||||||
|
|
||||||
# Resolve visit date using priority chain
|
# Resolve visit date using priority chain
|
||||||
visit_date, confidence = _resolve_visit_date(record, confirmed_visit_date)
|
visit_date, confidence = _resolve_visit_date(record, confirmed_visit_date)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from core.rules import UNIVERSAL_RULE_IDS, rule_id_for, rule_value
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PAYER_RULES_PATH = Path(__file__).parent.parent / "config" / "payer_rules.json"
|
PAYER_RULES_PATH = Path(__file__).parent.parent / "config" / "payer_rules.json"
|
||||||
|
|
@ -87,6 +89,9 @@ class DocItem:
|
||||||
quality: Quality
|
quality: Quality
|
||||||
value: Optional[str]
|
value: Optional[str]
|
||||||
satisfied: bool
|
satisfied: bool
|
||||||
|
# Names the payer_rules.json rule that decided required_state. None when
|
||||||
|
# required_state is NOT_EVALUATED: no rule fired, so nothing is cited.
|
||||||
|
rule_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -174,7 +179,8 @@ def _pa_quality(value: Optional[str]) -> Quality:
|
||||||
|
|
||||||
|
|
||||||
def _make_item(doc_type: str, required: RequiredState,
|
def _make_item(doc_type: str, required: RequiredState,
|
||||||
quality: Quality, value: Optional[str]) -> DocItem:
|
quality: Quality, value: Optional[str],
|
||||||
|
rule_id: Optional[str] = None) -> DocItem:
|
||||||
input_state = InputState.ABSENT if quality == Quality.NONE else InputState.SUPPLIED
|
input_state = InputState.ABSENT if quality == Quality.NONE else InputState.SUPPLIED
|
||||||
|
|
||||||
if required == RequiredState.NOT_REQUIRED:
|
if required == RequiredState.NOT_REQUIRED:
|
||||||
|
|
@ -191,26 +197,36 @@ def _make_item(doc_type: str, required: RequiredState,
|
||||||
quality=quality,
|
quality=quality,
|
||||||
value=value,
|
value=value,
|
||||||
satisfied=satisfied,
|
satisfied=satisfied,
|
||||||
|
rule_id=rule_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _required_from_config(plan_known: bool, plan_cfg: Optional[dict],
|
def _required_from_config(
|
||||||
flag_key: str, plan_type: Optional[str]) -> RequiredState:
|
plan_known: bool, plan_cfg: Optional[dict],
|
||||||
"""Required-ness for a plan-dependent item, read from config.
|
flag_key: str, plan_type: Optional[str],
|
||||||
|
) -> tuple[RequiredState, Optional[str]]:
|
||||||
|
"""(Required-ness, citing rule_id) for a plan-dependent item, from config.
|
||||||
|
|
||||||
A missing config (known plan absent from payer_rules.json, or the flag key
|
A missing config (known plan absent from payer_rules.json, or the flag key
|
||||||
missing) means we CANNOT decide -> NOT_EVALUATED, which blocks green. We
|
missing) means we CANNOT decide -> NOT_EVALUATED, which blocks green. We
|
||||||
never treat a config gap as 'not required' (that was the false-green hole).
|
never treat a config gap as 'not required' (that was the false-green hole).
|
||||||
|
When NOT_EVALUATED, rule_id is None: no rule fired, nothing is cited.
|
||||||
|
Accepts both flat and wrapped ({"value":..., "rule_id":...}) config values.
|
||||||
"""
|
"""
|
||||||
if not plan_known:
|
if not plan_known:
|
||||||
return RequiredState.NOT_EVALUATED
|
return RequiredState.NOT_EVALUATED, None
|
||||||
if not plan_cfg or flag_key not in plan_cfg:
|
if not plan_cfg or flag_key not in plan_cfg:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"plan_type '%s' missing '%s' in payer_rules.json -> cannot grade %s",
|
"plan_type '%s' missing '%s' in payer_rules.json -> cannot grade %s",
|
||||||
plan_type, flag_key, flag_key,
|
plan_type, flag_key, flag_key,
|
||||||
)
|
)
|
||||||
return RequiredState.NOT_EVALUATED
|
return RequiredState.NOT_EVALUATED, None
|
||||||
return RequiredState.REQUIRED if bool(plan_cfg[flag_key]) else RequiredState.NOT_REQUIRED
|
required = (
|
||||||
|
RequiredState.REQUIRED
|
||||||
|
if bool(rule_value(plan_cfg, flag_key))
|
||||||
|
else RequiredState.NOT_REQUIRED
|
||||||
|
)
|
||||||
|
return required, rule_id_for(plan_cfg, flag_key)
|
||||||
|
|
||||||
|
|
||||||
def evaluate_readiness(
|
def evaluate_readiness(
|
||||||
|
|
@ -233,19 +249,24 @@ def evaluate_readiness(
|
||||||
|
|
||||||
# Plan-dependent required-ness from config (single source of truth, never sets).
|
# Plan-dependent required-ness from config (single source of truth, never sets).
|
||||||
plan_cfg = rules.get("payer_rules", {}).get(plan_type) if plan_known else None
|
plan_cfg = rules.get("payer_rules", {}).get(plan_type) if plan_known else None
|
||||||
pecos_required = _required_from_config(plan_known, plan_cfg, "pecos_required", plan_type)
|
pecos_required, pecos_rule = _required_from_config(plan_known, plan_cfg, "pecos_required", plan_type)
|
||||||
pa_required = _required_from_config(plan_known, plan_cfg, "pa_required", plan_type)
|
pa_required, pa_rule = _required_from_config(plan_known, plan_cfg, "pa_required", plan_type)
|
||||||
|
|
||||||
visit_date = csv_visit_date or confirmed_visit_date
|
visit_date = csv_visit_date or confirmed_visit_date
|
||||||
items = [
|
items = [
|
||||||
_make_item("swo", RequiredState.REQUIRED, _swo_quality(csv_swo_status), csv_swo_status),
|
_make_item("swo", RequiredState.REQUIRED, _swo_quality(csv_swo_status),
|
||||||
|
csv_swo_status, rule_id=UNIVERSAL_RULE_IDS["swo"]),
|
||||||
_make_item("visit", RequiredState.REQUIRED,
|
_make_item("visit", RequiredState.REQUIRED,
|
||||||
_visit_quality(csv_visit_date, confirmed_visit_date),
|
_visit_quality(csv_visit_date, confirmed_visit_date),
|
||||||
visit_date.isoformat() if visit_date else None),
|
visit_date.isoformat() if visit_date else None,
|
||||||
|
rule_id=UNIVERSAL_RULE_IDS["visit"]),
|
||||||
_make_item("diagnosis", RequiredState.REQUIRED,
|
_make_item("diagnosis", RequiredState.REQUIRED,
|
||||||
_diagnosis_quality(csv_diagnosis_on_file), csv_diagnosis_on_file),
|
_diagnosis_quality(csv_diagnosis_on_file), csv_diagnosis_on_file,
|
||||||
_make_item("pecos", pecos_required, _pecos_quality(csv_pecos_verified), csv_pecos_verified),
|
rule_id=UNIVERSAL_RULE_IDS["diagnosis"]),
|
||||||
_make_item("pa", pa_required, _pa_quality(csv_pa_status), csv_pa_status),
|
_make_item("pecos", pecos_required, _pecos_quality(csv_pecos_verified),
|
||||||
|
csv_pecos_verified, rule_id=pecos_rule),
|
||||||
|
_make_item("pa", pa_required, _pa_quality(csv_pa_status),
|
||||||
|
csv_pa_status, rule_id=pa_rule),
|
||||||
]
|
]
|
||||||
|
|
||||||
return ReadinessVerdict(
|
return ReadinessVerdict(
|
||||||
|
|
|
||||||
52
python-backend/core/rules.py
Normal file
52
python-backend/core/rules.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
"""
|
||||||
|
rules.py
|
||||||
|
Signal — STTIL Solutions
|
||||||
|
|
||||||
|
Format-tolerant readers for payer_rules.json plus the stable rule-ID registry
|
||||||
|
(build plan 02, item P2; design: readiness-model-design-phase2 section 3.1).
|
||||||
|
|
||||||
|
A rule value may be FLAT (legacy: `"visit_renewal_days": 180`) or WRAPPED with
|
||||||
|
its citation (`"visit_renewal_days": {"value": 180, "rule_id": "R001"}`).
|
||||||
|
Both engines read through these helpers so the JSON format can carry citation
|
||||||
|
IDs without breaking either reader.
|
||||||
|
|
||||||
|
RULE IDS ARE STABLE — never change an ID after shipping. The ID names the rule
|
||||||
|
KIND; the plan type plus the value together are the citation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# Universal claim/coverage requirements per LCD L33822, independent of plan
|
||||||
|
# type. They live in code (no plan-type dependency) but carry rule IDs for
|
||||||
|
# citation consistency.
|
||||||
|
UNIVERSAL_RULE_IDS = {"swo": "R100", "visit": "R101", "diagnosis": "R102"}
|
||||||
|
|
||||||
|
# Plan-scoped keys in payer_rules.json and their registry IDs.
|
||||||
|
PLAN_KEY_RULE_IDS = {
|
||||||
|
"visit_renewal_days": "R001",
|
||||||
|
"refill_window_days": "R002",
|
||||||
|
"pa_required": "R003",
|
||||||
|
"pecos_required": "R004",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def rule_value(cfg: Optional[dict], key: str, default=None):
|
||||||
|
"""Read a rule value, accepting flat (180) and wrapped
|
||||||
|
({'value': 180, 'rule_id': 'R001'}) formats."""
|
||||||
|
if not cfg:
|
||||||
|
return default
|
||||||
|
raw = cfg.get(key, default)
|
||||||
|
if isinstance(raw, dict) and "value" in raw:
|
||||||
|
return raw["value"]
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
def rule_id_for(cfg: Optional[dict], key: str) -> Optional[str]:
|
||||||
|
"""The citation ID for a plan-scoped rule: from the wrapped config when
|
||||||
|
present, else the registry default for that key."""
|
||||||
|
raw = cfg.get(key) if cfg else None
|
||||||
|
if isinstance(raw, dict) and "rule_id" in raw:
|
||||||
|
return raw["rule_id"]
|
||||||
|
return PLAN_KEY_RULE_IDS.get(key)
|
||||||
|
|
@ -101,3 +101,12 @@ def test_priority_sort_order():
|
||||||
assert results[0].flag == CoverageFlag.SUPPLY_LAPSED
|
assert results[0].flag == CoverageFlag.SUPPLY_LAPSED
|
||||||
assert results[1].flag == CoverageFlag.RENEWAL_CRITICAL
|
assert results[1].flag == CoverageFlag.RENEWAL_CRITICAL
|
||||||
assert results[2].flag == CoverageFlag.ACTIVE
|
assert results[2].flag == CoverageFlag.ACTIVE
|
||||||
|
|
||||||
|
|
||||||
|
def test_wrapped_config_drives_timing_rules():
|
||||||
|
"""P2: with the wrapped citation format on disk, the timing calculator
|
||||||
|
still resolves visit_renewal_days correctly (R001 = 180 days)."""
|
||||||
|
visit = TODAY - timedelta(days=30)
|
||||||
|
r = make_record(csv_visit_date=visit)
|
||||||
|
result = calculate_coverage(r)
|
||||||
|
assert result.next_visit_due_date == visit + timedelta(days=180)
|
||||||
|
|
|
||||||
|
|
@ -255,3 +255,70 @@ def test_known_plan_types_match_payer_rules_config():
|
||||||
configured = set(json.loads(rules_path.read_text())["payer_rules"].keys()) - {"default"}
|
configured = set(json.loads(rules_path.read_text())["payer_rules"].keys()) - {"default"}
|
||||||
from core.readiness import KNOWN_PLAN_TYPES
|
from core.readiness import KNOWN_PLAN_TYPES
|
||||||
assert set(KNOWN_PLAN_TYPES) == configured
|
assert set(KNOWN_PLAN_TYPES) == configured
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# P2: citation rule IDs (build plan 02, readiness-model-design-phase2 §3.1)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_doc_items_carry_rule_ids():
|
||||||
|
v = evaluate_readiness(
|
||||||
|
"medicare",
|
||||||
|
csv_swo_status="On File",
|
||||||
|
csv_visit_date=VISIT,
|
||||||
|
csv_pecos_verified="Yes",
|
||||||
|
csv_diagnosis_on_file="Yes",
|
||||||
|
)
|
||||||
|
items = items_by_type(v)
|
||||||
|
assert items["swo"].rule_id == "R100"
|
||||||
|
assert items["visit"].rule_id == "R101"
|
||||||
|
assert items["diagnosis"].rule_id == "R102"
|
||||||
|
assert items["pecos"].rule_id == "R004"
|
||||||
|
assert items["pa"].rule_id == "R003"
|
||||||
|
|
||||||
|
|
||||||
|
def test_flat_and_wrapped_config_produce_identical_verdicts():
|
||||||
|
flat = {"payer_rules": {"medicare": {
|
||||||
|
"pa_required": False, "pecos_required": True,
|
||||||
|
}}}
|
||||||
|
wrapped = {"payer_rules": {"medicare": {
|
||||||
|
"pa_required": {"value": False, "rule_id": "R003"},
|
||||||
|
"pecos_required": {"value": True, "rule_id": "R004"},
|
||||||
|
}}}
|
||||||
|
kwargs = dict(
|
||||||
|
csv_swo_status="On File", csv_visit_date=VISIT,
|
||||||
|
csv_pecos_verified="Yes", csv_diagnosis_on_file="Yes",
|
||||||
|
)
|
||||||
|
v_flat = evaluate_readiness("medicare", rules=flat, **kwargs)
|
||||||
|
v_wrapped = evaluate_readiness("medicare", rules=wrapped, **kwargs)
|
||||||
|
assert v_flat.line_status == v_wrapped.line_status
|
||||||
|
for a, b in zip(v_flat.doc_items, v_wrapped.doc_items):
|
||||||
|
assert (a.doc_type, a.required_state, a.satisfied) == (
|
||||||
|
b.doc_type, b.required_state, b.satisfied
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_not_evaluated_items_have_no_rule_id():
|
||||||
|
v = evaluate_readiness(None)
|
||||||
|
items = items_by_type(v)
|
||||||
|
assert items["pecos"].required_state == RequiredState.NOT_EVALUATED
|
||||||
|
assert items["pecos"].rule_id is None
|
||||||
|
assert items["pa"].rule_id is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_disk_rule_ids_match_registry():
|
||||||
|
"""Guards citation drift: every wrapped rule_id in payer_rules.json must
|
||||||
|
equal the registry ID for its key (auditor finding L2, 2026-07-07)."""
|
||||||
|
import json
|
||||||
|
from pathlib import Path as _P
|
||||||
|
from core.rules import PLAN_KEY_RULE_IDS
|
||||||
|
|
||||||
|
cfg = json.loads(
|
||||||
|
(_P(__file__).parent.parent / "python-backend" / "config" / "payer_rules.json").read_text()
|
||||||
|
)
|
||||||
|
for plan, entry in cfg["payer_rules"].items():
|
||||||
|
for key, expected_id in PLAN_KEY_RULE_IDS.items():
|
||||||
|
raw = entry.get(key)
|
||||||
|
assert isinstance(raw, dict) and raw.get("rule_id") == expected_id, (
|
||||||
|
f"{plan}.{key} must be wrapped with rule_id {expected_id}"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -594,3 +594,18 @@ def test_confirm_visit_normalizes_plan_type(monkeypatch):
|
||||||
rec = resp.json()
|
rec = resp.json()
|
||||||
assert rec["plan_type"] == "medicare"
|
assert rec["plan_type"] == "medicare"
|
||||||
assert rec["readiness_status"] == "Clear to Ship"
|
assert rec["readiness_status"] == "Clear to Ship"
|
||||||
|
|
||||||
|
|
||||||
|
def test_upload_readiness_items_carry_rule_ids():
|
||||||
|
"""P2 contract W2: the serialized doc_items cite their rules."""
|
||||||
|
csv_text = (
|
||||||
|
HEADER
|
||||||
|
+ f"\nPT-RID-1,Dexcom G7,{SHIP},3,Medicare Part B,medicare,"
|
||||||
|
+ f"On File,{VISIT},Yes,,Yes"
|
||||||
|
)
|
||||||
|
resp = _upload(csv_text)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
items = {i["doc_type"]: i for i in resp.json()["records"][0]["readiness_items"]}
|
||||||
|
assert items["pecos"]["rule_id"] == "R004"
|
||||||
|
assert items["pa"]["rule_id"] == "R003"
|
||||||
|
assert items["swo"]["rule_id"] == "R100"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue