frollo: don't import what the statements already cover
ci / lint-test (push) Successful in 45s

"We should not be importing from Frollo what we already have from statements"
(owner). The amount+direction guard could not deliver that, because the two
sources decompose the same event differently: Frollo bundles the Wise fee into
the transfer (10001.13) where the statement itemises it (10000.00 + 1.13). So 38
Wise USD rows passed the amount guard as new while being the same money, and were
the reason the transactions view showed a USD figure where every neighbouring row
showed AUD.

A statement's billing_end_date is a hard watermark: everything on that account up
to that date is already in the ledger, itemised and converted. Guard 1 now drops
any feed row at or before its account's newest statement. Guard 2 (amount +
direction) stays as the net for accounts that have no statement at all.

Note this is the coverage test the first import needed and got wrong. That one
asked whether a row's date fell inside a statement's min-max window, which for
periods spanning 182 to 460 days swallows a year and answers nothing. The
watermark asks a question that has an answer: up to what date is this account
complete?

Matching is on last4, verified against the live statement set — the eight
in-scope accounts with statements each map to one bank, no cross-bank collision.
The watermarks are printed by the CLI so a wrong boundary is visible rather than
inferred from a row count.

Re-imported from empty: 425 covered by statement, 15 amount twins, 110 inserted.
Exactly one row now carries a foreign currency with no AUD figure — the
2026-08-12 HDR salary, which is the genuinely-new pre-statement row this feed
exists for. Was 39.
This commit is contained in:
2026-08-13 12:36:08 +10:00
parent afd75d3f09
commit b82c4570bd
3 changed files with 133 additions and 22 deletions
+11 -4
View File
@@ -99,10 +99,17 @@ console.log();
console.log("LEDGER");
console.log(` already imported : ${String(report.alreadyImported).padStart(5)}`);
// Printed even when zero. This is the count that was missing on 2026-08-13,
// when the whole file was written on top of a ledger that already held 77% of
// it; a number you have to go looking for is a number nobody looks at.
console.log(` already on ledger: ${String(report.ledgerDuplicates).padStart(5)} (statement twin within ${LEDGER_MATCH_DAYS} days)`);
// Printed even when zero. These are the counts that were missing on
// 2026-08-13, when the whole file was written on top of a ledger that already
// held 77% of it; a number you have to go looking for is a number nobody looks
// at. The watermarks are printed too, so a wrong coverage boundary is visible
// rather than inferred from a row count.
console.log(` covered by stmt : ${String(report.coveredByStatement).padStart(5)} (dated on or before the account's newest statement)`);
console.log(` amount twin : ${String(report.ledgerDuplicates).padStart(5)} (same amount + direction within ${LEDGER_MATCH_DAYS} days)`);
if (report.statementWatermarks.length > 0) {
console.log(" statements cover:");
for (const w of report.statementWatermarks) console.log(` ${w.last4} up to ${w.coveredTo}`);
}
console.log(` new to insert : ${String(report.toInsert).padStart(5)}`);
if (report.anomalies.length > 0) {