feat(backend): add Remittance + CasAdjustment ORM models
This commit is contained in:
@@ -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"),
|
||||||
|
)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user