fix(sp31): read NPI and charge from raw_json when ORM attribute missing

This commit is contained in:
Nora
2026-07-02 15:27:32 -06:00
parent 5a54961930
commit 9bade2429c
2 changed files with 82 additions and 11 deletions
+38 -11
View File
@@ -133,9 +133,15 @@ def _content_keys_match(remit, claim) -> bool:
- shim / dataclass test objects (planned names: total_charge_amount,
total_charge, rendering_provider_npi), and
- real SQLAlchemy ORM instances (Claim.charge_amount, Remittance.total_charge,
Claim.provider_npi). The Remittance ORM has no NPI column — callers
that need NPI on a Remit set the transient ``rendering_provider_npi``
attribute (the 835 parser does this on raw_json; tests do it directly).
Claim.provider_npi).
Production note: the 835 parser stores ``rendering_provider_npi`` and
``total_charge_amount`` on ``Remittance.raw_json`` (the ORM has no
dedicated column), so the read order below is:
- NPI: transient attribute → raw_json
- Charge: planned attribute name → real ORM column → raw_json
The ``raw_json`` fallback is what makes this helper safe against a
raw ``Remittance`` ORM instance from ``reconcile.run()``.
"""
keys_matched = 0
@@ -144,19 +150,40 @@ def _content_keys_match(remit, claim) -> bool:
(getattr(claim, "patient_control_number", "") or "").strip():
keys_matched += 1
# Charge: total_charge_amount/total_charge ↔ total_charge/charge_amount (within $0.01).
remit_charge = getattr(remit, "total_charge_amount", None) or getattr(remit, "total_charge", None)
claim_charge = getattr(claim, "total_charge", None) or getattr(claim, "charge_amount", None)
if remit_charge is not None and claim_charge is not None and \
abs(remit_charge - claim_charge) < CHARGE_TOLERANCE:
# Charge: prefer the planned attribute names, fall back to real ORM columns,
# then to raw_json (where the 835 parser stores CLP03). Explicit ``is None``
# checks avoid the ``Decimal("0")`` truthiness footgun — ``Decimal("0")``
# is truthy in a boolean context but ``or`` would still let it through;
# the more important property is that we don't accidentally replace a real
# value with a default.
_remit_charge = getattr(remit, "total_charge_amount", None)
if _remit_charge is None:
_remit_charge = getattr(remit, "total_charge", None)
if _remit_charge is None:
_remit_charge = (getattr(remit, "raw_json", None) or {}).get("total_charge_amount")
_claim_charge = getattr(claim, "total_charge", None)
if _claim_charge is None:
_claim_charge = getattr(claim, "charge_amount", None)
if _claim_charge is None:
_claim_charge = (getattr(claim, "raw_json", None) or {}).get("total_charge_amount")
if _remit_charge is not None and _claim_charge is not None and \
abs(_remit_charge - _claim_charge) < CHARGE_TOLERANCE:
keys_matched += 1
# NPI: rendering_provider_npi (or provider_npi fallback on Claim) ↔
# rendering_provider_npi (must be non-empty on the remit side).
remit_npi = (getattr(remit, "rendering_provider_npi", "") or "").strip()
# NPI: prefer attribute, fall back to raw_json (where the 835 parser
# stores rendering_provider_npi). The Remittance ORM has no NPI column,
# so the raw_json path is the production read for raw ORM instances —
# it must NOT be removed.
remit_npi = (
(getattr(remit, "rendering_provider_npi", "") or "")
or ((getattr(remit, "raw_json", None) or {}).get("rendering_provider_npi", "") or "")
).strip()
claim_npi = (
(getattr(claim, "rendering_provider_npi", "") or "")
or (getattr(claim, "provider_npi", "") or "")
or ((getattr(claim, "raw_json", None) or {}).get("rendering_provider_npi", "") or "")
).strip()
if remit_npi and remit_npi == claim_npi:
keys_matched += 1