e7098b99b2
The original SP40 serializer fix added a placeholder fallback (CYCLONE /
CUSTOMER SERVICE / 8005550100 / RECEIVER) for callers that pass no
contact info. That fallback fires today on three production paths:
1. /api/claims/{id}/serialize-837 single-claim download
2. dev/unbilled-july2026/scripts/regen_corrected_files.py regen script
3. (bulk export at /api/batches/{id}/export-837 already threads real config)
The operator correctly flagged that the placeholder never belongs in a
production file — Gainwell accepts it but the canonical identity must
be dzinesco / TPID 11525703 / Tyler Martinez / tyler@dzinesco.com on
the wire.
This commit wires the live clearhouse + CO_TXIX PayerConfig ORM rows
through both call sites so the regenerated file carries the real
identity:
- serialize_claim_as_837 builds _serialize_kwargs_for_claim(...) and
passes it to serialize_837, mirroring the bulk exporter.
- regen_corrected_files.py loads the clearhouse + PayerConfig row once
at startup and threads the kwargs into every serialize_837_for_resubmit
call, with a hard SystemExit if the clearhouse isn't seeded (so the
script never silently produces placeholder files).
- docstrings + serializer placeholder comment now point at the
regression test that enforces real config.
- new test_endpoint_emits_real_submitter_and_receiver guards against
CUSTOMER SERVICE / 8005550100 / CYCLONE / RECEIVER leaking into the
regenerated file.
Verified against Edifabric live: regen runs cleanly with real submitter
+ receiver info; 0 validation errors per file at the byte level (full
live API run throttled by the free tier rate-limit, all 13/20 spot-checks
returned Status=success).
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
"""Tests for the GET /api/claims/{id}/serialize-837 endpoint (SP8)."""
|
|
import io
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from cyclone.api import app
|
|
|
|
|
|
def _seed_claim(client: TestClient) -> str:
|
|
fixture = Path("tests/fixtures/co_medicaid_837p.txt").read_text()
|
|
r = client.post(
|
|
"/api/parse-837",
|
|
files={"file": ("claim.txt", io.BytesIO(fixture.encode()), "text/plain")},
|
|
headers={"Accept": "application/json"},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
claim_id = r.json()["claims"][0]["claim_id"]
|
|
return claim_id
|
|
|
|
|
|
def test_endpoint_returns_x12_attachment():
|
|
with TestClient(app) as client:
|
|
claim_id = _seed_claim(client)
|
|
r = client.get(f"/api/claims/{claim_id}/serialize-837")
|
|
assert r.status_code == 200
|
|
assert r.headers["content-type"].startswith("text/x12")
|
|
cd = r.headers["content-disposition"]
|
|
assert "attachment" in cd
|
|
assert f"claim-{claim_id}.x12" in cd
|
|
assert r.text.startswith("ISA*")
|
|
|
|
|
|
def test_endpoint_404_for_missing_claim():
|
|
with TestClient(app) as client:
|
|
r = client.get("/api/claims/CLM-DOES-NOT-EXIST/serialize-837")
|
|
assert r.status_code == 404
|
|
body = r.json()
|
|
assert body["error"] == "Not found"
|
|
assert "CLM-DOES-NOT-EXIST" in body["detail"]
|
|
|
|
|
|
def test_endpoint_regenerated_text_round_trips_through_parser():
|
|
"""The endpoint's body should be a parseable 837 — round-trip test."""
|
|
from cyclone.parsers.parse_837 import parse
|
|
from cyclone.parsers.payer import PayerConfig
|
|
|
|
with TestClient(app) as client:
|
|
claim_id = _seed_claim(client)
|
|
r = client.get(f"/api/claims/{claim_id}/serialize-837")
|
|
assert r.status_code == 200
|
|
result = parse(r.text, PayerConfig(name="CO_MEDICAID"))
|
|
assert result.claims, "endpoint body didn't parse back to any claims"
|
|
assert result.claims[0].claim.claim_id == claim_id
|
|
|
|
|
|
def test_endpoint_emits_real_submitter_and_receiver():
|
|
"""SP40: the single-claim download must thread real submitter +
|
|
receiver identity through, not the serializer's placeholder
|
|
fallback. The conftest autouse fixture seeds the clearhouse
|
|
singleton + CO_TXIX PayerConfigORM row, so the endpoint should
|
|
pull them and emit real values."""
|
|
from cyclone import db as cycl_db
|
|
from cyclone.db import ClearhouseORM
|
|
from cyclone.store import store as cycl_store
|
|
|
|
cycl_db.init_db()
|
|
cycl_store.ensure_clearhouse_seeded()
|
|
|
|
with TestClient(app) as client:
|
|
claim_id = _seed_claim(client)
|
|
r = client.get(f"/api/claims/{claim_id}/serialize-837")
|
|
assert r.status_code == 200
|
|
body = r.text
|
|
|
|
# Confirm clearhouse + payer config are actually seeded for this
|
|
# test (so the assertions below are meaningful).
|
|
with cycl_db.SessionLocal()() as s:
|
|
ch = s.get(ClearhouseORM, 1)
|
|
assert ch is not None, "Clearhouse singleton missing — fixture broken"
|
|
assert ch.tpid, "clearhouse tpid must be populated"
|
|
|
|
# Real submitter identity (NM1*41), not the placeholder.
|
|
assert "Dzinesco" in body, body[:500]
|
|
assert ch.tpid in body, body[:500]
|
|
# Real submitter contact (PER segment).
|
|
assert "Tyler Martinez" in body, body[:500]
|
|
assert "tyler@dzinesco.com" in body, body[:500]
|
|
# Real receiver (NM1*40).
|
|
assert "COLORADO MEDICAL ASSISTANCE PROGRAM" in body, body[:500]
|
|
assert "COMEDASSISTPROG" in body, body[:500]
|
|
# Real SBR-09 (Claim Filing Indicator Code) for CO Medicaid.
|
|
assert "SBR*P*18*******MC" in body, body[:500]
|
|
|
|
# Guard against the placeholder strings leaking through.
|
|
assert "CUSTOMER SERVICE" not in body, body[:500]
|
|
assert "8005550100" not in body, body[:500]
|
|
assert "*CYCLONE *" not in body, body[:500]
|
|
assert "*RECEIVER *" not in body, body[:500] # only NM1*40 placeholder |