feat(sp16): live MFT polling scheduler

Adds an asyncio-based background scheduler that polls the Gainwell
MFT inbound path, downloads new files, and routes them through the
appropriate parser (999 / 835 / 277CA / TA1). Idempotent (re-ticks
and restarts skip already-processed files via the new
processed_inbound_files table). Crash-safe (per-file try/except so
one bad file doesn't stop the loop).

Lifespan auto-configures from the seeded dzinesco clearhouse's SFTP
block; auto-start is opt-in via CYCLONE_SCHEDULER_AUTOSTART.

Five admin endpoints added:
  GET  /api/admin/scheduler/status
  POST /api/admin/scheduler/start
  POST /api/admin/scheduler/stop
  POST /api/admin/scheduler/tick
  GET  /api/admin/scheduler/processed-files?status=&limit=

20 new tests (15 unit + 5 API).
This commit is contained in:
Tyler
2026-06-21 09:20:58 -06:00
parent 1267a341e3
commit 40f184c858
10 changed files with 1424 additions and 11 deletions
+5 -4
View File
@@ -51,18 +51,19 @@ def test_migration_0002_creates_acks_table():
def test_migration_latest_idempotent_on_fresh_db():
"""Re-running the migration on the same DB must be a no-op (PRAGMA
user_version already at the latest version — currently 10 after
user_version already at the latest version — currently 11 after
0004-0006 line_reconciliation, 0005 ta1_acks, SP9's 0007
providers/payers/clearhouse, SP10's 0008 payer_rejected,
SP11's 0009 audit_log, and SP14's 0010 payer_rejected_acknowledged)."""
SP11's 0009 audit_log, SP14's 0010 payer_rejected_acknowledged,
SP16's 0011 processed_inbound_files)."""
with db.engine().begin() as c:
v1 = c.exec_driver_sql("PRAGMA user_version").scalar() or 0
assert v1 == 10
assert v1 == 11
# A second run should not raise and should not bump the version.
db_migrate.run(db.engine())
with db.engine().begin() as c:
v2 = c.exec_driver_sql("PRAGMA user_version").scalar() or 0
assert v2 == 10
assert v2 == 11
def test_add_ack_persists_row():