feat(sp41): claim-id dedup at SFTP pre-flight (30-day window, configurable)
This commit is contained in:
@@ -51,7 +51,7 @@ 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 21 after
|
||||
user_version already at the latest version — currently 22 after
|
||||
0004-0006 line_reconciliation, 0005 ta1_acks, SP9's 0007
|
||||
providers/payers/clearhouse, SP10's 0008 payer_rejected,
|
||||
SP11's 0009 audit_log, SP14's 0010 payer_rejected_acknowledged,
|
||||
@@ -62,16 +62,16 @@ def test_migration_latest_idempotent_on_fresh_db():
|
||||
claim.patient_control_number backfill UPDATE, SP28's 0018
|
||||
claim_acks join table, SP32's 0019
|
||||
rendering_provider_npi + service_provider_npi, SP37's 0020
|
||||
transaction_set_control_number, and SP39's 0021 resubmissions
|
||||
audit table)."""
|
||||
transaction_set_control_number, SP39's 0021 resubmissions
|
||||
audit table, and SP41's 0022 submission_dedup)."""
|
||||
with db.engine().begin() as c:
|
||||
v1 = c.exec_driver_sql("PRAGMA user_version").scalar() or 0
|
||||
assert v1 == 21
|
||||
assert v1 == 22
|
||||
# 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 == 21
|
||||
assert v2 == 22
|
||||
|
||||
|
||||
def test_add_ack_persists_row():
|
||||
|
||||
@@ -128,14 +128,15 @@ def test_migration_latest_idempotent_on_fresh_db(tmp_path: Path) -> None:
|
||||
service_provider_npi on claims and remittances.
|
||||
SP37 bumped it to 20 with batch transaction_set_control_number.
|
||||
SP39 bumped it to 21 with the resubmissions audit table.
|
||||
SP41 bumped it to 22 with submission_dedup.
|
||||
"""
|
||||
engine = _fresh_engine(tmp_path)
|
||||
db_migrate.run(engine)
|
||||
v_after_first = _user_version(engine)
|
||||
assert v_after_first == 21, f"expected head=21, got {v_after_first}"
|
||||
assert v_after_first == 22, f"expected head=22, got {v_after_first}"
|
||||
|
||||
db_migrate.run(engine)
|
||||
assert _user_version(engine) == 21, "second run should not bump version"
|
||||
assert _user_version(engine) == 22, "second run should not bump version"
|
||||
|
||||
|
||||
def test_drop_claims_unique_constraint_migration(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@@ -161,7 +162,7 @@ def test_drop_claims_unique_constraint_migration(tmp_path: Path, monkeypatch: py
|
||||
engine = _fresh_engine(tmp_path)
|
||||
|
||||
db_migrate.run(engine)
|
||||
assert _user_version(engine) == 21, f"expected head=21, got {_user_version(engine)}"
|
||||
assert _user_version(engine) == 22, f"expected head=22, got {_user_version(engine)}"
|
||||
|
||||
# Two claims in one batch with the same patient_control_number
|
||||
# must be insertable. If 0015's table recreation re-introduced a
|
||||
|
||||
@@ -102,10 +102,10 @@ def migrated_engine(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
engine = _fresh_engine(tmp_path / "mig0020.db")
|
||||
db_migrate.run(engine)
|
||||
|
||||
# Confirm head is 21 (every migration applied, including SP39's 0021).
|
||||
# Confirm head is 22 (every migration applied, including SP41's 0022).
|
||||
with engine.connect() as conn:
|
||||
v = conn.exec_driver_sql("PRAGMA user_version").scalar() or 0
|
||||
assert v == 21, f"expected migration head=21, got {v}"
|
||||
assert v == 22, f"expected migration head=22, got {v}"
|
||||
|
||||
yield engine
|
||||
engine.dispose()
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Claim-id dedup at the SFTP pre-flight stage."""
|
||||
from datetime import datetime
|
||||
from cyclone.store.submission_dedup import (
|
||||
DuplicateClaimError,
|
||||
record_submission,
|
||||
check_duplicate,
|
||||
DEFAULT_WINDOW_DAYS,
|
||||
)
|
||||
|
||||
|
||||
def test_check_duplicate_returns_none_for_first_submission(tmp_path):
|
||||
db = tmp_path / "test.db"
|
||||
assert check_duplicate("ORIG-001", db_url=f"sqlite:///{db}") is None
|
||||
|
||||
|
||||
def test_check_duplicate_raises_within_window(tmp_path):
|
||||
db = tmp_path / "test.db"
|
||||
record_submission("ORIG-002", submitted_at=datetime(2026, 6, 1), db_url=f"sqlite:///{db}")
|
||||
try:
|
||||
check_duplicate("ORIG-002", db_url=f"sqlite:///{db}",
|
||||
now=datetime(2026, 6, 15))
|
||||
except DuplicateClaimError as e:
|
||||
assert e.claim_id == "ORIG-002"
|
||||
assert e.original_submission_at == datetime(2026, 6, 1)
|
||||
else:
|
||||
raise AssertionError("expected DuplicateClaimError")
|
||||
|
||||
|
||||
def test_check_duplicate_passes_after_window(tmp_path):
|
||||
db = tmp_path / "test.db"
|
||||
record_submission("ORIG-003", submitted_at=datetime(2026, 1, 1), db_url=f"sqlite:///{db}")
|
||||
# 100 days later — past the 30-day default window
|
||||
assert check_duplicate("ORIG-003", db_url=f"sqlite:///{db}",
|
||||
now=datetime(2026, 4, 11)) is None
|
||||
|
||||
|
||||
def test_default_window_is_30_days():
|
||||
assert DEFAULT_WINDOW_DAYS == 30
|
||||
Reference in New Issue
Block a user