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:
+23
-22
@@ -577,7 +577,6 @@ def resubmit_rejected_claims(
|
|||||||
|
|
||||||
from cyclone import db as db_mod
|
from cyclone import db as db_mod
|
||||||
from cyclone.audit_log import AuditEvent, append_event
|
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.parse_837 import parse as parse_837_text
|
||||||
from cyclone.parsers.payer import PayerConfig
|
from cyclone.parsers.payer import PayerConfig
|
||||||
from cyclone.store import store as cycl_store
|
from cyclone.store import store as cycl_store
|
||||||
@@ -614,7 +613,6 @@ def resubmit_rejected_claims(
|
|||||||
failed = 0
|
failed = 0
|
||||||
validated = 0
|
validated = 0
|
||||||
payer_cfg = PayerConfig.co_medicaid()
|
payer_cfg = PayerConfig.co_medicaid()
|
||||||
sftp_client = SftpClient(sftp_block)
|
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
last_progress = start
|
last_progress = start
|
||||||
|
|
||||||
@@ -641,36 +639,39 @@ def resubmit_rejected_claims(
|
|||||||
try: ssh.close()
|
try: ssh.close()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
ssh: paramiko.SSHClient | None = None
|
||||||
|
sftp: paramiko.SFTPClient | None = None
|
||||||
|
|
||||||
for i, src in enumerate(files, 1):
|
for i, src in enumerate(files, 1):
|
||||||
if limit is not None and i > limit:
|
if limit is not None and i > limit:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
content = src.read_bytes()
|
||||||
|
|
||||||
# Validate: parse must succeed AND payer_id must match the
|
# Validate: parse must succeed AND payer_id must match the
|
||||||
# companion-guide CO_TXIX (catches a bad byte-fix early).
|
# companion-guide CO_TXIX (catches a bad byte-fix early).
|
||||||
if validate:
|
if validate:
|
||||||
try:
|
try:
|
||||||
parsed = parse_837_text(src.read_text(), payer_cfg)
|
parsed = parse_837_text(content.decode(), payer_cfg)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
failed += 1
|
failed += 1
|
||||||
click.echo(f"PARSE FAIL {src.name}: {exc.__class__.__name__}: {exc}", err=True)
|
click.echo(f"PARSE FAIL {src.name}: {exc.__class__.__name__}: {exc}", err=True)
|
||||||
continue
|
continue
|
||||||
for c in parsed.claims:
|
mismatch = next(
|
||||||
if c.payer.id != "CO_TXIX":
|
(c for c in parsed.claims if c.payer.id != "CO_TXIX"), None
|
||||||
failed += 1
|
)
|
||||||
click.echo(
|
if mismatch is not None:
|
||||||
f"PAYER MISMATCH {src.name}: payer.id={c.payer.id!r} "
|
failed += 1
|
||||||
f"(expected 'CO_TXIX')", err=True,
|
click.echo(
|
||||||
)
|
f"PAYER MISMATCH {src.name}: payer.id={mismatch.payer.id!r} "
|
||||||
break
|
f"(expected 'CO_TXIX')", err=True,
|
||||||
else:
|
)
|
||||||
validated += 1
|
continue
|
||||||
|
validated += 1
|
||||||
|
|
||||||
content = src.read_bytes()
|
|
||||||
local_size = len(content)
|
local_size = len(content)
|
||||||
remote_path = f"{remote_root}/{src.name}"
|
remote_path = f"{remote_root}/{src.name}"
|
||||||
|
|
||||||
ssh: paramiko.SSHClient | None = None
|
|
||||||
sftp: paramiko.SFTPClient | None = None
|
|
||||||
attempts = 0
|
attempts = 0
|
||||||
ok = False
|
ok = False
|
||||||
while attempts < 3:
|
while attempts < 3:
|
||||||
@@ -713,12 +714,6 @@ def resubmit_rejected_claims(
|
|||||||
failed += 1
|
failed += 1
|
||||||
continue
|
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
|
# Audit (best-effort; if the DB is unavailable we still keep
|
||||||
# the file on the wire).
|
# the file on the wire).
|
||||||
try:
|
try:
|
||||||
@@ -741,6 +736,12 @@ def resubmit_rejected_claims(
|
|||||||
|
|
||||||
uploaded += 1
|
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).
|
# Progress every 10s of wall-clock (or at end).
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
if now - last_progress >= 10 or i == len(files):
|
if now - last_progress >= 10 or i == len(files):
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def apply_999_rejections(
|
|||||||
claim_lookup: Callable[[str], Claim | None],
|
claim_lookup: Callable[[str], Claim | None],
|
||||||
batch_envelope_index: dict[str, list[str]] | None = None,
|
batch_envelope_index: dict[str, list[str]] | None = None,
|
||||||
) -> Apply999Result:
|
) -> 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.
|
move it to REJECTED. Idempotent on already-rejected claims.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
Reference in New Issue
Block a user