feat(sp33): fix resubmit CLI session reuse + mismatch skip

- payer-mismatch files are now skipped instead of uploaded
- SFTP session persists across files (was reopened and leaked per file)
- --reconnect-every now counts successful uploads, after increment
- drop unused SftpClient instantiation; read each file once
- apply_999_rejections docstring: R/E/X, not R/E

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Nora
2026-07-03 12:32:30 -06:00
parent f10ab83628
commit 27bca33b09
2 changed files with 24 additions and 23 deletions
+23 -22
View File
@@ -577,7 +577,6 @@ def resubmit_rejected_claims(
from cyclone import db as db_mod
from cyclone.audit_log import AuditEvent, append_event
from cyclone.clearhouse import SftpClient
from cyclone.parsers.parse_837 import parse as parse_837_text
from cyclone.parsers.payer import PayerConfig
from cyclone.store import store as cycl_store
@@ -614,7 +613,6 @@ def resubmit_rejected_claims(
failed = 0
validated = 0
payer_cfg = PayerConfig.co_medicaid()
sftp_client = SftpClient(sftp_block)
start = time.monotonic()
last_progress = start
@@ -641,36 +639,39 @@ def resubmit_rejected_claims(
try: ssh.close()
except Exception: pass
ssh: paramiko.SSHClient | None = None
sftp: paramiko.SFTPClient | None = None
for i, src in enumerate(files, 1):
if limit is not None and i > limit:
break
content = src.read_bytes()
# Validate: parse must succeed AND payer_id must match the
# companion-guide CO_TXIX (catches a bad byte-fix early).
if validate:
try:
parsed = parse_837_text(src.read_text(), payer_cfg)
parsed = parse_837_text(content.decode(), payer_cfg)
except Exception as exc: # noqa: BLE001
failed += 1
click.echo(f"PARSE FAIL {src.name}: {exc.__class__.__name__}: {exc}", err=True)
continue
for c in parsed.claims:
if c.payer.id != "CO_TXIX":
failed += 1
click.echo(
f"PAYER MISMATCH {src.name}: payer.id={c.payer.id!r} "
f"(expected 'CO_TXIX')", err=True,
)
break
else:
validated += 1
mismatch = next(
(c for c in parsed.claims if c.payer.id != "CO_TXIX"), None
)
if mismatch is not None:
failed += 1
click.echo(
f"PAYER MISMATCH {src.name}: payer.id={mismatch.payer.id!r} "
f"(expected 'CO_TXIX')", err=True,
)
continue
validated += 1
content = src.read_bytes()
local_size = len(content)
remote_path = f"{remote_root}/{src.name}"
ssh: paramiko.SSHClient | None = None
sftp: paramiko.SFTPClient | None = None
attempts = 0
ok = False
while attempts < 3:
@@ -713,12 +714,6 @@ def resubmit_rejected_claims(
failed += 1
continue
# Reconnect periodically to dodge MOVEit's per-session cap.
if uploaded > 0 and uploaded % reconnect_every == 0:
click.echo(f"reconnecting (after {uploaded} uploads)", err=True)
_close_session(ssh)
ssh, sftp = None, None
# Audit (best-effort; if the DB is unavailable we still keep
# the file on the wire).
try:
@@ -741,6 +736,12 @@ def resubmit_rejected_claims(
uploaded += 1
# Reconnect periodically to dodge MOVEit's per-session cap.
if uploaded % reconnect_every == 0:
click.echo(f"reconnecting (after {uploaded} uploads)", err=True)
_close_session(ssh)
ssh, sftp = None, None
# Progress every 10s of wall-clock (or at end).
now = time.monotonic()
if now - last_progress >= 10 or i == len(files):
+1 -1
View File
@@ -35,7 +35,7 @@ def apply_999_rejections(
claim_lookup: Callable[[str], Claim | None],
batch_envelope_index: dict[str, list[str]] | None = None,
) -> Apply999Result:
"""For each set response with code R or E, look up the matching claim and
"""For each set response with code R, E, or X, look up the matching claim and
move it to REJECTED. Idempotent on already-rejected claims.
Args: