feat(backend): fix T4 index drift, lazy import, FK policy, expand test coverage

This commit is contained in:
Tyler
2026-06-19 21:42:11 -06:00
parent 0fdb760bc1
commit 2019a651ed
2 changed files with 96 additions and 6 deletions
+19 -6
View File
@@ -11,6 +11,7 @@ same engine.
from __future__ import annotations
import enum
import json
import os
from datetime import date, datetime
from decimal import Decimal
@@ -28,6 +29,7 @@ from sqlalchemy import (
String,
Text,
UniqueConstraint,
text,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, sessionmaker
from sqlalchemy.types import TypeDecorator
@@ -141,15 +143,11 @@ class JSONText(TypeDecorator):
def process_bind_param(self, value, dialect): # type: ignore[no-untyped-def]
if value is None:
return None
import json
return json.dumps(value)
def process_result_value(self, value, dialect): # type: ignore[no-untyped-def]
if value is None:
return None
import json
return json.loads(value)
@@ -172,8 +170,13 @@ class Batch(Base):
)
__table_args__ = (
Index("ix_batches_parsed_at", "parsed_at"),
# Migration is the source of truth: 0001_initial.sql declares this DESC.
# `text(...)` keeps the ORM in sync so fresh DBs built via `create_all`
# alone (no migration runner) get the same index direction as prod.
Index("ix_batches_parsed_at", text("parsed_at DESC")),
)
# TODO(T6): when ActivityEvent model is added, mirror this pattern for
# `ix_activity_events_ts` on activity_events(ts DESC) per 0001_initial.sql:86.
class Claim(Base):
@@ -196,7 +199,17 @@ class Claim(Base):
Enum(ClaimState, native_enum=False), nullable=True
)
matched_remittance_id: Mapped[Optional[str]] = mapped_column(
String(64), ForeignKey("remittances.id"), nullable=True
# ORM policy: SET NULL on remittance delete. The claim may outlive its
# remittance during reversal/reimport flows; without this, deleting a
# remittance with matched claims raises IntegrityError.
# Note: 0001_initial.sql predates this policy (no ON DELETE clause).
# SQLite ignores FK direction by default unless PRAGMA foreign_keys=ON,
# so the inconsistency is benign there; the ORM is the source of truth
# for PostgreSQL/test environments with FK enforcement. Amendment to the
# migration is deferred to post-SQLite rollout.
String(64),
ForeignKey("remittances.id", ondelete="SET NULL"),
nullable=True,
)
raw_json: Mapped[Optional[dict]] = mapped_column(JSONText, nullable=True)
+77
View File
@@ -78,3 +78,80 @@ def test_create_and_query_claim_with_default_state():
assert loaded.state == ClaimState.SUBMITTED
assert loaded.charge_amount == Decimal("124.00")
assert loaded.service_date_from == date(2026, 6, 1)
def test_claim_uses_db_default_when_state_omitted():
"""The `state` column has a DB-side DEFAULT of 'submitted' (per migration
and ORM `default=`). When the caller omits `state=`, the loaded value
must be ClaimState.SUBMITTED, not None."""
with db.SessionLocal()() as s:
s.add(Batch(
id="b1", kind="837p", input_filename="x",
parsed_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
))
c = Claim(
id="CLM-1",
batch_id="b1",
patient_control_number="CLM-1",
charge_amount=Decimal("0"),
)
s.add(c)
s.commit()
with db.SessionLocal()() as s:
loaded = s.get(Claim, "CLM-1")
assert loaded is not None
assert loaded.state == ClaimState.SUBMITTED
def test_state_before_reversal_round_trips():
"""`state_before_reversal` is nullable but stores a ClaimState enum.
Setting it to PAID must round-trip as ClaimState.PAID after commit+reload."""
with db.SessionLocal()() as s:
s.add(Batch(
id="b1", kind="837p", input_filename="x",
parsed_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
))
c = Claim(
id="CLM-1",
batch_id="b1",
patient_control_number="CLM-1",
charge_amount=Decimal("50.00"),
state_before_reversal=ClaimState.PAID,
)
s.add(c)
s.commit()
with db.SessionLocal()() as s:
loaded = s.get(Claim, "CLM-1")
assert loaded is not None
assert loaded.state_before_reversal == ClaimState.PAID
def test_batch_cascade_delete_drops_claims():
"""Deleting a Batch must cascade-delete its claims (FK is ON DELETE CASCADE
in migration; ORM relationship uses cascade='all, delete-orphan' which makes
SQLAlchemy emit the child DELETE at flush time even without
PRAGMA foreign_keys=ON)."""
with db.SessionLocal()() as s:
s.add(Batch(
id="b1", kind="837p", input_filename="x",
parsed_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
))
s.add(Claim(
id="CLM-1",
batch_id="b1",
patient_control_number="CLM-1",
charge_amount=Decimal("0"),
))
s.commit()
with db.SessionLocal()() as s:
batch = s.get(Batch, "b1")
assert batch is not None
s.delete(batch)
s.commit()
with db.SessionLocal()() as s:
assert s.get(Batch, "b1") is None
assert s.get(Claim, "CLM-1") is None