feat(sp39): cli resubmissions status

This commit is contained in:
Nora
2026-07-07 17:23:24 -06:00
parent 538fad211b
commit 0493814489
2 changed files with 182 additions and 0 deletions
+62
View File
@@ -1376,6 +1376,68 @@ def ack_orphans_reconcile_cmd(dry_run: bool) -> None:
click.echo("(dry-run — no rows inserted)")
# ---------------------------------------------------------------------------
# SP39: `cyclone resubmissions status` — operator view onto the
# resubmissions audit table. Joins against claim_acks (via the
# existing SP28/31 auto-link) + remittances (via CLP->claim) so the
# operator can confirm post-submission outcomes of the corrected
# 837 files pushed via `cyclone resubmit-rejected-claims`.
# ---------------------------------------------------------------------------
@main.group("resubmissions")
def resubmissions_group() -> None:
"""SP39: inspect post-submission status of corrected-file pushes."""
@resubmissions_group.command("status")
@click.option("--batch-id", default=None,
help="Restrict to a single source batch_id "
"(matches ingest/corrected-v2/<batch-id>-<N>-claims/).")
@click.option("--limit", default=200, type=int,
help="Maximum rows to print (default 200).")
def resubmissions_status_cmd(batch_id: str | None, limit: int) -> None:
"""Print per-claim resubmission status (ack + payment).
One row per ``resubmissions`` row, optionally filtered by
``--batch-id``. The ``ack`` column reflects the latest inbound
999 / 277CA accept-reject code linked to the claim; the
``payment`` column is ``paid`` once a remittance CLP links.
Exit codes: 0 on success, 1 on DB error.
"""
from cyclone import db as db_mod
from cyclone.store import store as cycl_store
try:
db_mod.init_db()
rows = cycl_store.find_resubmission_status(batch_id=batch_id)
except Exception as exc: # noqa: BLE001
click.echo(f"resubmissions status failed: {type(exc).__name__}: {exc}", err=True)
sys.exit(1)
if not rows:
click.echo("no resubmissions recorded")
return
click.echo(
f"{'claim_id':<22} {'batch_id':<36} {'resubmitted_at':<20} "
f"{'ack':<16} {'payment':<14}"
)
click.echo("-" * 110)
for r in rows[:limit]:
click.echo(
f"{r.claim_id:<22} {r.batch_id:<36} "
f"{r.resubmitted_at.isoformat(timespec='seconds'):<20} "
f"{(r.ack_status or 'pending_999'):<16} "
f"{(r.payment_status or '-'):<14}"
)
if len(rows) > limit:
click.echo(
f"... ({len(rows) - limit} more; pass --limit to see all)"
)
# ---------------------------------------------------------------------------
# SP25: ``cyclone recover-ingest`` — offline parse + DB-write helper for
# stranded source files in ingest/. Mirrors the data-writing half of
+120
View File
@@ -0,0 +1,120 @@
"""SP39: tests for the `cyclone resubmissions status` CLI."""
from __future__ import annotations
from datetime import datetime, timezone
from click.testing import CliRunner
from cyclone import db as cycl_db
from cyclone.cli import resubmissions_status_cmd
from cyclone.db import Resubmission
def _seed_one(claim_id: str = "claim-1", batch_id: str = "batch-1",
icn: str = "000000001") -> None:
cycl_db.init_db()
with cycl_db.SessionLocal()() as s:
s.add(Resubmission(
claim_id=claim_id,
batch_id=batch_id,
resubmitted_at=datetime.now(timezone.utc),
source_corrected_path=f"/tmp/{claim_id}.x12",
interchange_control_number=icn,
group_control_number="1",
))
s.commit()
def _cleanup(batch_id: str) -> None:
with cycl_db.SessionLocal()() as s:
s.query(Resubmission).filter(Resubmission.batch_id == batch_id).delete()
s.commit()
def test_no_resubmissions_prints_message_and_exits_0():
cycl_db.init_db()
runner = CliRunner()
result = runner.invoke(
resubmissions_status_cmd,
["--batch-id", "no-such-batch-zzz"],
)
assert result.exit_code == 0, result.output
assert "no resubmissions recorded" in result.output
def test_one_resubmission_prints_status_row():
try:
_seed_one(claim_id="claim-A", batch_id="batch-A")
runner = CliRunner()
result = runner.invoke(
resubmissions_status_cmd,
["--batch-id", "batch-A"],
)
assert result.exit_code == 0, result.output
assert "claim-A" in result.output
assert "pending_999" in result.output # no inbound ack yet
assert "batch-A" in result.output
finally:
_cleanup("batch-A")
def test_limit_truncates_output():
try:
for i in range(5):
_seed_one(
claim_id=f"claim-L{i}", batch_id="batch-L",
icn=f"00000000{i}",
)
runner = CliRunner()
result = runner.invoke(
resubmissions_status_cmd,
["--batch-id", "batch-L", "--limit", "2"],
)
assert result.exit_code == 0, result.output
assert "more" in result.output
# Only 2 rows printed in the table (plus 1 "(N more)" line).
body_lines = [
ln for ln in result.output.splitlines()
if ln.startswith("claim-L")
]
assert len(body_lines) == 2
finally:
_cleanup("batch-L")
def test_status_reflects_paid_when_remittance_links():
"""When a remittance.claim_id links to the resubmitted claim,
the payment column shows 'paid'."""
try:
from cyclone.db import Remittance
from decimal import Decimal
_seed_one(claim_id="claim-P", batch_id="batch-P")
with cycl_db.SessionLocal()() as s:
s.add(Remittance(
id="remit-P-1",
batch_id="batch-P",
payer_claim_control_number="claim-P",
claim_id="claim-P",
status_code="1",
status_label="Processed as Primary",
total_charge=Decimal("100.00"),
total_paid=Decimal("80.00"),
adjustment_amount=Decimal("0"),
claim_level_adjustment_amount=Decimal("0"),
received_at=datetime.now(timezone.utc),
))
s.commit()
runner = CliRunner()
result = runner.invoke(
resubmissions_status_cmd,
["--batch-id", "batch-P"],
)
assert result.exit_code == 0, result.output
assert "claim-P" in result.output
assert "paid" in result.output
finally:
from cyclone.db import Remittance as _R
with cycl_db.SessionLocal()() as s:
s.query(_R).filter(_R.id == "remit-P-1").delete()
s.commit()
_cleanup("batch-P")