feat(backend): add Remittance + CasAdjustment ORM models

This commit is contained in:
Tyler
2026-06-19 21:47:18 -06:00
parent 2019a651ed
commit 0cc74dc173
2 changed files with 108 additions and 6 deletions
+46 -5
View File
@@ -20,11 +20,13 @@ from typing import Optional
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import ( from sqlalchemy import (
Boolean,
Date, Date,
DateTime, DateTime,
Enum, Enum,
ForeignKey, ForeignKey,
Index, Index,
Integer,
Numeric, Numeric,
String, String,
Text, Text,
@@ -223,11 +225,6 @@ class Claim(Base):
) )
# Minimal stub for forward relationship/FK resolution. The full Remittance
# model (with payer_claim_control_number, status_code, total_charge, etc.)
# is defined in Task 5 and will replace this stub. The stub declares only
# the columns needed for `Batch.remittances` relationship resolution and
# `Claim.matched_remittance_id` FK metadata resolution.
class Remittance(Base): class Remittance(Base):
__tablename__ = "remittances" __tablename__ = "remittances"
@@ -235,4 +232,48 @@ class Remittance(Base):
batch_id: Mapped[str] = mapped_column( batch_id: Mapped[str] = mapped_column(
String(32), ForeignKey("batches.id", ondelete="CASCADE"), nullable=False String(32), ForeignKey("batches.id", ondelete="CASCADE"), nullable=False
) )
payer_claim_control_number: Mapped[str] = mapped_column(String(64), nullable=False)
claim_id: Mapped[Optional[str]] = mapped_column(
String(64), ForeignKey("claims.id"), nullable=True
)
status_code: Mapped[str] = mapped_column(String(4), nullable=False)
status_label: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
total_charge: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False, default=Decimal("0"))
total_paid: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False, default=Decimal("0"))
patient_responsibility: Mapped[Optional[Decimal]] = mapped_column(Numeric(12, 2), nullable=True)
adjustment_amount: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False, default=Decimal("0"))
received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
service_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
is_reversal: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
raw_json: Mapped[Optional[dict]] = mapped_column(JSONText, nullable=True)
batch: Mapped["Batch"] = relationship(back_populates="remittances") batch: Mapped["Batch"] = relationship(back_populates="remittances")
cas_adjustments: Mapped[list["CasAdjustment"]] = relationship(
back_populates="remittance", cascade="all, delete-orphan"
)
__table_args__ = (
UniqueConstraint("batch_id", "payer_claim_control_number", name="uq_remits_batch_pcn"),
Index("ix_remittances_claim_id", "claim_id"),
Index("ix_remittances_payer_claim_control_number", "payer_claim_control_number"),
Index("ix_remittances_status_code", "status_code"),
)
class CasAdjustment(Base):
__tablename__ = "cas_adjustments"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
remittance_id: Mapped[str] = mapped_column(
String(64), ForeignKey("remittances.id", ondelete="CASCADE"), nullable=False
)
group_code: Mapped[str] = mapped_column(String(4), nullable=False)
reason_code: Mapped[str] = mapped_column(String(8), nullable=False)
amount: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False)
quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(10, 2), nullable=True)
remittance: Mapped["Remittance"] = relationship(back_populates="cas_adjustments")
__table_args__ = (
Index("ix_cas_adjustments_remittance_id", "remittance_id"),
)
+62 -1
View File
@@ -9,7 +9,7 @@ from decimal import Decimal
import pytest import pytest
from cyclone import db from cyclone import db
from cyclone.db import Base, Batch, Claim, ClaimState from cyclone.db import Base, Batch, CasAdjustment, Claim, ClaimState, Remittance
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@@ -155,3 +155,64 @@ def test_batch_cascade_delete_drops_claims():
with db.SessionLocal()() as s: with db.SessionLocal()() as s:
assert s.get(Batch, "b1") is None assert s.get(Batch, "b1") is None
assert s.get(Claim, "CLM-1") is None assert s.get(Claim, "CLM-1") is None
def test_create_and_query_remittance():
with db.SessionLocal()() as s:
s.add(Batch(
id="b1", kind="835", input_filename="x",
parsed_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
))
r = Remittance(
id="CLP-1",
batch_id="b1",
payer_claim_control_number="CLP-1",
status_code="1",
total_charge=Decimal("124.00"),
total_paid=Decimal("124.00"),
received_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
service_date=date(2026, 6, 1),
is_reversal=False,
)
s.add(r)
s.commit()
with db.SessionLocal()() as s:
loaded = s.get(Remittance, "CLP-1")
assert loaded is not None
assert loaded.status_code == "1"
assert loaded.total_paid == Decimal("124.00")
assert loaded.is_reversal is False
assert loaded.adjustment_amount == Decimal("0") # default
def test_cas_adjustment_aggregation():
"""Sum of CAS rows for a Remittance equals expected total."""
with db.SessionLocal()() as s:
s.add(Batch(
id="b1", kind="835", input_filename="x",
parsed_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
))
r = Remittance(
id="CLP-1", batch_id="b1",
payer_claim_control_number="CLP-1", status_code="1",
total_charge=Decimal("124.00"), total_paid=Decimal("62.00"),
received_at=datetime(2026, 6, 19, tzinfo=timezone.utc),
)
s.add(r)
s.flush()
s.add_all([
CasAdjustment(remittance_id="CLP-1", group_code="CO", reason_code="45",
amount=Decimal("30.00")),
CasAdjustment(remittance_id="CLP-1", group_code="PR", reason_code="1",
amount=Decimal("32.00")),
])
s.commit()
from sqlalchemy import select, func
with db.SessionLocal()() as s:
total = s.execute(
select(func.sum(CasAdjustment.amount))
.where(CasAdjustment.remittance_id == "CLP-1")
).scalar_one()
assert total == Decimal("62.00")