Two parse bugs that between them made 218 of 776 captured messages
unreadable. Neither was the "old template" they were filed as.
Uber writes the currency three ways and only two were handled. "Total
A$54.87" is what it sends for ordinary Australian orders — A$ misses
[A-Z]{3} by one character — so 98 of 275 Uber Eats mails and 78 of 275
trip mails failed with "no Total found" while the amount sat in plain
sight. Most were 2024-2025, i.e. current mail. NZ$, US$, S$, HK$, C$ and
a bare rupee/euro/pound symbol are handled the same way. A bare "$" is
still left unresolved on purpose: a dozen currencies use it, and the
body-wide scan that reads the receipt's own stated code should win.
A DoorDash order paid from credits states "Total Charged $0.00"
truthfully, above a real subtotal. Read literally that is a $0 order, and
validateOrderTotals rejected 88 of them as non-positive — discarding
exactly the credit-funded spend this pipeline exists to surface. The
order's value is its subtotal; recording zero would show the order and
hide what it cost. Guarded on the receipt actually saying credits, so an
empty mail still fails rather than inheriting a stray subtotal, and the
header cross-check stands down for these or it rejects the figure the
parser deliberately overrode.
Measured A/B over all 776 captures: 254 parsed by both parsers with zero
change to any amount or currency, 0 lost, 214 newly readable. Both fixes
mutation-tested — reverting the regex fails 4 tests, removing the credits
branch fails 3.
Fixtures are real captured receipts, per the 2026-07-26 rewrite: the
earlier synthetic suite passed while the parser could not read a real
email.
No history replay — I7 idempotency refuses re-reads and that needs an
explicit update mode. This fixes ingest from here on.
This commit is contained in:
@@ -330,3 +330,85 @@ describe("orderDescription", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Two parse failures that between them accounted for 218 of the 287 unreadable
|
||||
* messages in the 776-message capture set. Neither was an "old template": the
|
||||
* Uber one fails on current 2024-2025 mail, and the DoorDash one fails on every
|
||||
* order paid from credits, in every year.
|
||||
*/
|
||||
describe("currency notations Uber actually sends", () => {
|
||||
const uber = (f: string, subject: string) =>
|
||||
parseOrderHTML(html(f), meta({ subject, sender: "Uber Receipts <noreply@uber.com>" }));
|
||||
|
||||
it("reads a symbol-prefixed Australian total", () => {
|
||||
// "Total A$54.87". The old pattern allowed a 3-letter ISO code or a bare
|
||||
// "$", so A$ — which is what Uber sends for ordinary domestic orders —
|
||||
// matched neither and 98 of 275 Uber Eats mails were unreadable.
|
||||
const o = uber("ue-aud-prefix", "Your Friday evening order with Uber Eats");
|
||||
expect(o.totals.total_charged).toBe(54.87);
|
||||
expect(o.currency).toBe("AUD");
|
||||
});
|
||||
|
||||
it("reads NZ$ as New Zealand dollars, not Australian", () => {
|
||||
// The prefix is the only thing distinguishing them, and getting it wrong
|
||||
// books a Queenstown dinner at the wrong rate rather than failing loudly.
|
||||
const o = uber("ue-nzd-prefix", "Your Saturday evening order with Uber Eats");
|
||||
expect(o.totals.total_charged).toBe(22.83);
|
||||
expect(o.currency).toBe("NZD");
|
||||
});
|
||||
|
||||
it("reads a bare rupee symbol on a trip", () => {
|
||||
const o = uber("ut-inr-symbol", "Your Friday evening trip with Uber");
|
||||
expect(o.totals.total_charged).toBe(622.74);
|
||||
expect(o.currency).toBe("INR");
|
||||
});
|
||||
|
||||
it("still reads the space-separated ISO form", () => {
|
||||
// The [Family] LKR receipts depend on this and must not regress.
|
||||
const o = uber("ut-nzd-prefix", "Your Sunday afternoon trip with Uber");
|
||||
expect(o.totals.total_charged).toBe(10.83);
|
||||
expect(o.currency).toBe("NZD");
|
||||
});
|
||||
});
|
||||
|
||||
describe("credits-funded orders are orders", () => {
|
||||
const credits = () =>
|
||||
parseOrderHTML(
|
||||
html("dd-credits-zero"),
|
||||
meta({ subject: "Order Confirmation for Siddharth from Chilli India" })
|
||||
);
|
||||
|
||||
it("records the subtotal when the card was charged nothing", () => {
|
||||
// The receipt says "Subtotal $71.86 ... Total Charged $0.00" — truthfully,
|
||||
// because credits covered it. Reading that as a $0 order threw away the
|
||||
// credit-funded spend this pipeline exists to surface.
|
||||
const o = credits();
|
||||
expect(o.totals.total_charged).toBe(71.86);
|
||||
expect(o.totals.subtotal).toBe(71.86);
|
||||
expect(o.flags).toContain("credits_funded_zero_charge");
|
||||
});
|
||||
|
||||
it("books the amount as credits, not as a card charge", () => {
|
||||
const o = credits();
|
||||
expect(o.payment.credits_amount).toBe(71.86);
|
||||
expect(o.payment.card_amount).toBeNull();
|
||||
expect(o.payment.card_last4).toBeNull();
|
||||
});
|
||||
|
||||
it("passes validation instead of being rejected as non-positive", () => {
|
||||
// Both stated totals are $0.00 and agree, so the header cross-check has to
|
||||
// stand down here or it rejects the very figure the parser overrode.
|
||||
const o = credits();
|
||||
expect(validateOrderTotals(o, html("dd-credits-zero"))).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("does not invent a total when the receipt never says credits", () => {
|
||||
// The guard that keeps this from becoming "any zero total borrows the
|
||||
// subtotal" — a genuinely empty receipt must still fail.
|
||||
const notCredits = html("dd-credits-zero").replace(/Paid with/gi, "Charged to");
|
||||
const o = parseOrderHTML(notCredits, meta({ subject: "Order Confirmation for Siddharth from Chilli India" }));
|
||||
expect(o.totals.total_charged).toBe(0);
|
||||
expect(validateOrderTotals(o, notCredits).ok).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user