feat(orders): withdraw the ShopBack transfer guard

Tests 12 & 13 asserted that every 'ShopBack Gift Cards' row becomes a transfer.
Resolved against the ShopBack purchase emails, 3 of the 14 matching rows are
Airbnb, 1 Shell, 1 Amazon — the bank descriptor's trailing token is a sequence
counter, not a brand code, so the description cannot identify what was bought.
Only $313.66 of $3,411.16 was ever reclassifiable.

Withdrawn rather than narrowed: making it safe needs ShopBack purchase-email
ingestion, brand resolution and an approval gate, to correctly handle 2
transactions in 20 months. Those two rows get handled by hand.
This commit is contained in:
2026-07-26 22:09:31 +10:00
parent 9f168cf4c8
commit 6d3b6e1a9d
@@ -84,38 +84,20 @@ describe("Order Ingestion - Integration Tests", () => {
expect(res1.transactionId).toBe(res2.transactionId); expect(res1.transactionId).toBe(res2.transactionId);
}); });
it("12 & 13. ShopBack rows carry category_override='transfers' and monthly spend delta matches (I2)", async () => { // Tests 12 & 13 (ShopBack -> transfers reclassification, old invariant I2) were
// Ensure at least one ShopBack transaction exists for isolated test runs // REMOVED 2026-07-26. The guard they asserted is withdrawn from the slice.
await queryRaw(` //
INSERT INTO transactions (transaction_date, description, amount, category, owner_id) // The bank descriptor `ShopBack Gift Cards <TOKEN>` cannot identify the card's
VALUES ('2025-01-05', 'ShopBack Gift Cards SP Australia AUS', 300.00, 'gifts', NULL) // brand -- the trailing token is a sequence counter, not a brand code. Resolved
ON CONFLICT DO NOTHING; // against the ShopBack purchase emails, 3 of the 14 matching rows were Airbnb,
`); // 1 Shell, 1 Amazon. Of $3,411.16, only $313.66 was ever reclassifiable; the
// rule was 91% wrong by value and would have deleted real travel/fuel/shopping
await queryRaw(` // spend. These tests asserted that wrong behaviour and would have gone green
INSERT INTO transaction_overrides (transaction_id, category_override) // doing it.
SELECT id, 'transfers' FROM transactions WHERE description ILIKE '%ShopBack Gift Cards%' //
ON CONFLICT (transaction_id) DO UPDATE SET category_override = 'transfers'; // Rationale for withdrawing rather than narrowing: only 2 transactions in 20
`); // months qualify. See memory-bank/order-ingestion-slice-plan.md and
// memory-bank/order-ingestion-review-artifacts.md §3.1z.
const sbRows = await queryRaw<{ id: number; category_override: string }>(
`SELECT t.id, o.category_override
FROM transactions t
JOIN transaction_overrides o ON o.transaction_id = t.id
WHERE t.description ILIKE '%ShopBack Gift Cards%'`
);
expect(sbRows.length).toBeGreaterThan(0);
sbRows.forEach((r) => expect(r.category_override).toBe("transfers"));
// Verify EXCLUDE_NON_SPEND excludes them
const excluded = await queryRaw(
`SELECT t.id FROM transactions t
LEFT JOIN transaction_overrides o ON o.transaction_id = t.id
WHERE t.description ILIKE '%ShopBack Gift Cards%'
AND NOT (${EXCLUDE_NON_SPEND})`
);
expect(excluded.length).toBe(sbRows.length);
});
it("14. Split on a $42 credits order => participant share computed on 42.00 (I8)", async () => { it("14. Split on a $42 credits order => participant share computed on 42.00 (I8)", async () => {
const txn = await queryRow<{ id: number }>( const txn = await queryRow<{ id: number }>(