feat(backend): parse-835 JSON response includes reconciliation summary

This commit is contained in:
Tyler
2026-06-19 23:42:22 -06:00
parent 44fe88d694
commit 43a88a356c
2 changed files with 61 additions and 1 deletions
+37 -1
View File
@@ -303,6 +303,40 @@ def _has_835_validation_errors(result: ParseResult835) -> bool:
return result.validation is not None and not result.validation.passed
def _reconciliation_summary_for_batch(batch_id: str) -> dict:
"""Return ``{matched, unmatched_claims, unmatched_remittances, skipped}`` for a batch.
Reads from the DB after ``store.add()`` has already triggered T10
reconciliation synchronously (via ``_run_reconcile``). Counts are
observed at this moment; a subsequent manual match/unmatch will not
be reflected until the next request.
``skipped`` is reserved for future use — the T10 orchestrator tracks
skipped claims internally but does not surface a queryable count.
"""
from sqlalchemy import func, select
from cyclone import db
from cyclone.db import Match, Remittance
with db.SessionLocal()() as s:
matched = s.execute(
select(func.count(Match.id)).where(
Match.remittance_id.in_(
select(Remittance.id).where(Remittance.batch_id == batch_id)
)
)
).scalar_one()
# Pull unmatched via the store (small result set; cheap).
unmatched = store.list_unmatched(kind="both")
return {
"matched": matched,
"unmatched_claims": len(unmatched["claims"]),
"unmatched_remittances": len(unmatched["remittances"]),
"skipped": 0, # reserved — T10 does not persist a skipped count
}
@app.post("/api/parse-835")
async def parse_835_endpoint(
request: Request,
@@ -384,7 +418,9 @@ async def parse_835_endpoint(
store.add(rec)
if _client_wants_json(request):
return JSONResponse(content=json.loads(result.model_dump_json()))
body = json.loads(result.model_dump_json())
body["reconciliation"] = _reconciliation_summary_for_batch(rec.id)
return JSONResponse(content=body)
# Default: NDJSON stream.
return StreamingResponse(