From e8b452d255eb16fb14a88ad201df5242f8de8366 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 20 Jun 2026 19:23:14 -0600 Subject: [PATCH] =?UTF-8?q?feat(sp7):=20migration=200006=20=E2=80=94=20ser?= =?UTF-8?q?vice=5Fline=5Fpayments=20+=20line=5Freconciliations=20+=20FKs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration renumbered from 0005 to 0006 because 0005_create_ta1_acks.sql (another agent's WIP, unmerged) already claims version 5. Bumping our migration to version 6 avoids stepping on it; user_version will land at 6 once both migrations run. --- backend/src/cyclone/db.py | 27 ++++++++++++++----- ...ation.sql => 0006_line_reconciliation.sql} | 6 ++--- 2 files changed, 23 insertions(+), 10 deletions(-) rename backend/src/cyclone/migrations/{0005_line_reconciliation.sql => 0006_line_reconciliation.sql} (89%) diff --git a/backend/src/cyclone/db.py b/backend/src/cyclone/db.py index c0142ed..1282cfa 100644 --- a/backend/src/cyclone/db.py +++ b/backend/src/cyclone/db.py @@ -260,6 +260,9 @@ class Remittance(Base): 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")) + claim_level_adjustment_amount: Mapped[Decimal] = mapped_column( + Numeric(12, 2), nullable=False, default=Decimal("0"), server_default=text("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) @@ -292,11 +295,17 @@ class CasAdjustment(Base): 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) + service_line_payment_id: Mapped[Optional[int]] = mapped_column( + Integer, + ForeignKey("service_line_payments.id", ondelete="SET NULL"), + nullable=True, + ) remittance: Mapped["Remittance"] = relationship(back_populates="cas_adjustments") __table_args__ = ( Index("ix_cas_adjustments_remittance_id", "remittance_id"), + Index("ix_cas_adjustments_service_line_payment_id", "service_line_payment_id"), ) @@ -346,10 +355,15 @@ class LineReconciliation(Base): """One row per matched (or explicitly unmatched) 837 service line within a claim. SP7. Created during ``reconcile.run()`` after the claim↔remit match. - At least one of ``claim_service_line_id`` / ``service_line_payment_id`` + At least one of ``claim_service_line_number`` / ``service_line_payment_id`` must be non-null; the application enforces this. - See spec §3.2. + Implementation note: 837 service lines live in ``Claim.raw_json.service_lines[]`` + keyed by ``line_number`` (1-based). They are NOT a separate ORM table, so the + claim-side FK is the line number itself; lookups join via the parent ``claim_id`` + + this column to read the line from the JSON blob. + + See spec §3.2 (with this implementation note appended during T3). """ __tablename__ = "line_reconciliations" @@ -357,10 +371,8 @@ class LineReconciliation(Base): claim_id: Mapped[str] = mapped_column( String(64), ForeignKey("claims.id", ondelete="CASCADE"), nullable=False ) - claim_service_line_id: Mapped[Optional[int]] = mapped_column( - Integer, - ForeignKey("service_lines.id", ondelete="SET NULL"), - nullable=True, + claim_service_line_number: Mapped[Optional[int]] = mapped_column( + Integer, nullable=True ) service_line_payment_id: Mapped[Optional[int]] = mapped_column( Integer, @@ -376,7 +388,8 @@ class LineReconciliation(Base): __table_args__ = ( Index("ix_line_reconciliations_claim_id", "claim_id"), Index( - "ix_line_reconciliations_claim_service_line_id", "claim_service_line_id" + "ix_line_reconciliations_claim_service_line_number", + "claim_service_line_number", ), Index( "ix_line_reconciliations_service_line_payment_id", diff --git a/backend/src/cyclone/migrations/0005_line_reconciliation.sql b/backend/src/cyclone/migrations/0006_line_reconciliation.sql similarity index 89% rename from backend/src/cyclone/migrations/0005_line_reconciliation.sql rename to backend/src/cyclone/migrations/0006_line_reconciliation.sql index 687f8f7..0252b55 100644 --- a/backend/src/cyclone/migrations/0005_line_reconciliation.sql +++ b/backend/src/cyclone/migrations/0006_line_reconciliation.sql @@ -1,4 +1,4 @@ --- version: 5 +-- version: 6 -- SP7: per-service-line adjustment audit -- See spec: docs/superpowers/specs/2026-06-20-cyclone-line-reconciliation-design.md @@ -24,7 +24,7 @@ CREATE INDEX ix_service_line_payments_procedure_code_date ON service_line_paymen CREATE TABLE line_reconciliations ( id INTEGER PRIMARY KEY AUTOINCREMENT, claim_id VARCHAR(64) NOT NULL REFERENCES claims(id) ON DELETE CASCADE, - claim_service_line_id INTEGER REFERENCES service_lines(id) ON DELETE SET NULL, + claim_service_line_number INTEGER, service_line_payment_id INTEGER REFERENCES service_line_payments(id) ON DELETE SET NULL, status VARCHAR(16) NOT NULL, match_score INTEGER, @@ -32,7 +32,7 @@ CREATE TABLE line_reconciliations ( ); CREATE INDEX ix_line_reconciliations_claim_id ON line_reconciliations(claim_id); -CREATE INDEX ix_line_reconciliations_claim_service_line_id ON line_reconciliations(claim_service_line_id); +CREATE INDEX ix_line_reconciliations_claim_service_line_number ON line_reconciliations(claim_service_line_number); CREATE INDEX ix_line_reconciliations_service_line_payment_id ON line_reconciliations(service_line_payment_id); ALTER TABLE cas_adjustments ADD COLUMN service_line_payment_id INTEGER REFERENCES service_line_payments(id) ON DELETE SET NULL;