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.
This commit is contained in:
@@ -221,6 +221,10 @@ def main() -> int:
|
|||||||
ap.add_argument("--verbose", action="store_true")
|
ap.add_argument("--verbose", action="store_true")
|
||||||
ap.add_argument("--file", help="only this CSV (substring match)")
|
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("--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()
|
args = ap.parse_args()
|
||||||
|
|
||||||
paths = sorted(glob.glob(os.path.join(DUMP_DIR, "*SplitMyExpenses*.csv")))
|
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"Of the matched, {already} already carry a split and would be left alone;")
|
||||||
print(f"{len(rep.matched) - already} would gain one.")
|
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:
|
if args.verbose:
|
||||||
print("\n--- ambiguous ---")
|
print("\n--- ambiguous ---")
|
||||||
for row, cands in rep.ambiguous[:40]:
|
for row, cands in rep.ambiguous[:40]:
|
||||||
|
|||||||
Reference in New Issue
Block a user