From b4a116c1342d83fdc931d6127fb264e1c8fdf4f3 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Tue, 28 Jul 2026 12:02:57 +1000 Subject: [PATCH] feat(scripts): import the matched split history as settled Adds --write to the matcher. Wrote 1,242 split rows across 657 transactions. Imported settled, and that is the whole design. These obligations were discharged years ago on a platform we no longer run, and their residual is already carried by transaction 2348. Writing them unsettled would re-open roughly $40k of debts that were paid. ACTIVE_OBLIGATION keeps settled splits out of every owed figure while myShare/mySplitOf still count them, which is exactly the asymmetry this needs: the import exists to correct historical SPEND, not to move a balance. Effect: $35,259 leaves my historical spend -- $13,088 in 2024, $22,117 in 2025 -- because a $200 grocery shop that was always half hers no longer reads as $200 of mine. Balances are byte-identical before and after (Molina -1226.72/145, Sonu 5428.08/419), which is the assertion that matters. Shares are written as the CSV computed them, so a 50/50 row can land as 50.01/49.99. That is faithful rather than tidy; no transaction exceeds 100%. Rehearsed on the 37-row Rome file first (24 rows) and verified before the full run -- both the balances and one split read back through the API. --- scripts/split_csv_match.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/scripts/split_csv_match.py b/scripts/split_csv_match.py index c9551d6..5578411 100644 --- a/scripts/split_csv_match.py +++ b/scripts/split_csv_match.py @@ -221,6 +221,10 @@ def main() -> int: ap.add_argument("--verbose", action="store_true") ap.add_argument("--file", help="only this CSV (substring match)") ap.add_argument("--window", type=int, default=5, help="date tolerance in days") + ap.add_argument( + "--write", action="store_true", + help="actually insert the splits (settled=true). Without this, nothing is written.", + ) args = ap.parse_args() paths = sorted(glob.glob(os.path.join(DUMP_DIR, "*SplitMyExpenses*.csv"))) @@ -324,6 +328,36 @@ def main() -> int: print(f"Of the matched, {already} already carry a split and would be left alone;") print(f"{len(rep.matched) - already} would gain one.") + if args.write: + # Imported as settled: these obligations were discharged on a platform + # we no longer run, and the residual is already carried by transaction + # 2348. Writing them unsettled would re-open ~$40k of debts that were + # paid years ago. See ACTIVE_OBLIGATION in analytics-sql.ts -- settled + # rows stay in spend and leave every owed figure, which is exactly the + # point: this import exists to correct historical SPEND. + SETTLED_ON = "2026-01-09" # the carryover's date + written = 0 + for row, tx in rep.matched: + if tx["has_split"]: + continue + payer_share = round(100 - row.ower_share, 2) + pairs = [(row.ower, row.ower_share)] + if payer_share > 0: + pairs.append((row.payer, payer_share)) + for pid, share in pairs: + cur.execute( + """ + INSERT INTO transaction_splits + (transaction_id, participant_id, share_percent, settled, settled_at) + VALUES (%s, %s, %s, true, %s) + ON CONFLICT (transaction_id, participant_id) DO NOTHING + """, + (tx["id"], pid, share, SETTLED_ON), + ) + written += cur.rowcount + conn.commit() + print(f"\nWROTE {written} split rows (settled=true, settled_at={SETTLED_ON}).") + if args.verbose: print("\n--- ambiguous ---") for row, cands in rep.ambiguous[:40]: