← back to Unclaimed Property Platform

tests/test_cycle11_reconciliation.py

75 lines

"""Cycle 10 test — feed-completeness reconciliation (catches silent truncation).

Run:  python -m tests.test_cycle11_reconciliation

The sample feed has 8 rows (7 accepted + 1 rejected empty-owner) => 8 records PARSED.
Reconciliation compares parsed vs the state-declared control total:
  1. expected == parsed         -> 'ok'
  2. expected  > parsed         -> 'short'  (SILENT-TRUNCATION signal + missing delta)
  3. expected  < parsed         -> 'over'
  4. no expected declared       -> 'unknown' (not flagged)
and the result is persisted on the batch (queryable).
"""
from __future__ import annotations

import sys
import tempfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from services.common.sqlite_repo import FileObjectStore, SqliteRepository
from services.ingestion.ingest import FeedDefinition, ingest_authorized_feed

SAMPLE = Path(__file__).resolve().parents[1] / "data" / "sample" / "sample_state_feed.csv"


def ok(msg: str) -> None:
    print(f"  ✓ {msg}")


def _ingest(tmp: Path, sub: str, expected):
    store = FileObjectStore(tmp / sub)
    (tmp / sub).mkdir(parents=True, exist_ok=True)
    store.write_bytes("incoming/s.csv", SAMPLE.read_bytes())
    repo = SqliteRepository(str(tmp / sub / "p.db"))
    feed = FeedDefinition("SAMPLE", "incoming/s.csv", expected_count=expected)
    return repo, ingest_authorized_feed(feed, store, repo)


def main() -> int:
    tmp = Path(tempfile.mkdtemp(prefix="upp-cycle11-"))

    print("0) baseline parse counts")
    repo, r = _ingest(tmp, "ok", expected=8)
    assert r["accepted"] == 7 and r["rejected"] == 1, r
    ok("8 parsed (7 accepted + 1 rejected)")

    print("1) expected == parsed -> ok")
    assert r["reconciliation"] == "ok" and r["reconciliation_delta"] == 0, r
    stored = repo.batch_reconciliation(r["batch_id"])
    assert stored["reconciliation"] == "ok" and stored["expected_count"] == 8, stored
    ok("reconciliation 'ok' (persisted on batch)")

    print("2) expected > parsed -> short (silent-truncation signal)")
    _, r2 = _ingest(tmp, "short", expected=10)
    assert r2["reconciliation"] == "short" and r2["reconciliation_delta"] == 2, r2
    ok(f"expected 10, parsed 8 -> 'short', missing {r2['reconciliation_delta']} (would alert)")

    print("3) expected < parsed -> over")
    _, r3 = _ingest(tmp, "over", expected=5)
    assert r3["reconciliation"] == "over" and r3["reconciliation_delta"] == 3, r3
    ok("expected 5, parsed 8 -> 'over' (delta 3)")

    print("4) no control total -> unknown (not flagged)")
    _, r4 = _ingest(tmp, "unknown", expected=None)
    assert r4["reconciliation"] == "unknown", r4
    ok("no expected_count -> 'unknown'")

    print("\nALL CYCLE-10 RECONCILIATION ASSERTIONS PASSED ✅")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())