Skip to content

fix(invoices): keep the full Anthropic receipt number - #130

Merged
studert merged 2 commits into
mainfrom
fix/anthropic-invoice-number
Nov 2, 2026
Merged

studert merged 2 commits into
mainfrom
fix/anthropic-invoice-number

Conversation

@studert

@studert studert commented Nov 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Anthropic Ireland receipt numbers were truncated to the 8-character prefix (9BF0758D-10147934 stored as 9BF0758D), so every later ingest returned 409 against the first row. The parser now keeps an 8-character prefix plus a numeric suffix of any length and stores the canonical PREFIX SUFFIX form already used by older rows (K6EV5YRI 0101).
  • A label with no suffix is a 422 instead of a partial number. Duplicate detection keys on the full number, and a previously truncated prefix only conflicts when vendor, date, and amount also match.
  • Migration 0034 rewrites a dashed number to the single-space form and corrects invoice 216 to 9BF0758D 10147934. It does not guess a suffix for invoice 209. Not applied to production from this branch.

Test plan

  • pnpm test — parser fixtures (dash, space, no separator, missing suffix), ingest of two 9BF0758D receipts with different suffixes (both 200) and a re-post (409), 422 when the suffix is missing
  • pnpm typecheck and pnpm lint
  • On a non-production database, run pnpm db:migrate and confirm invoice 216 is 9BF0758D 10147934 only when id, amount ($67.08), and date (2026-10-24) still match the truncated row
  • Re-post a new Ireland receipt PDF and confirm the stored number includes the numeric suffix
  • Open the source PDF for invoice 209 (K6EV5YRI, $4.27, 2026-08-11) and correct the suffix by hand

Ireland receipts split the invoice number on a space, and the parser stored only the 8-character prefix, so later ingests all collided. Keep the canonical PREFIX SUFFIX form and reject a missing suffix.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI balanced review requested due to automatic review settings November 2, 2026 11:13
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ai-developer-hub Ready Ready Preview Nov 2, 2026 11:29am UTC

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Duplicate detection can miss legacy invoice numbers and manual re-uploads, allowing duplicate invoices and billed costs, while Anthropic-specific parsing can reject or alter other suppliers’ invoice numbers.

3 open issues
What changed in this PR

This PR updates the Hub’s invoice ingestion to preserve full Anthropic receipt numbers and distinguish receipts that share a prefix.

Changes:

  • Adds receipt-number parsing, canonicalization, duplicate matching, and regression tests.
  • Treats Anthropic’s PBC and Ireland names as equivalent in vendor filters and credit-purchase queries.
  • Adds migration 0034 to normalize dashed numbers and repair invoice 216, leaving invoice 209 for manual correction.
File Description
tests/​unit/​vendor-identity.test.ts Tests Anthropic vendor aliases and filtering.
tests/​unit/​invoice-number.test.ts Tests number formats and missing suffixes.
tests/​unit/​invoice-extraction.test.ts Tests extraction overrides and partial-number rejection.
tests/​unit/​invoice-duplicate.test.ts Tests canonical and truncated-number conflicts.
tests/​unit/​api/​invoice-ingest.test.ts Tests distinct receipts, reposts, and extraction errors.
src/​lib/​vendor-identity.ts Centralizes Anthropic vendor comparisons.
src/​lib/​mcp/​data.ts Expands Anthropic invoice filtering across aliases.
src/​lib/​invoice-number.ts Adds receipt parsing and canonicalization helpers.
src/​lib/​invoice-extraction.ts Integrates full-number parsing into PDF extraction.
src/​lib/​invoice-duplicate.ts Adds shared invoice conflict detection.
src/​lib/​ingestion-filters.ts Uses vendor equivalence for ingestion rules.
src/​lib/​db/​migrations/​meta/​0034_snapshot.json Records the migration’s schema snapshot.
src/​lib/​db/​migrations/​meta/​_journal.json Registers migration 0034.
src/​lib/​db/​migrations/​0034_fix_truncated_invoice_numbers.sql Normalizes stored references and repairs invoice 216.
src/​app/​api/​invoices/​ingest/​route.ts Uses shared conflict detection and extraction errors.
src/​actions/​invoices.ts Uses shared conflict detection for manual checks.
src/​actions/​credits.ts Includes Ireland receipts in credit-purchase candidates.

Review effort: Balanced

Comment thread src/actions/invoices.ts Outdated
Comment on lines +58 to +60
invoiceDate: "",
amountCents: -1,
vendor: null,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5e2f48. checkInvoiceDuplicate now takes the invoice number, date, amount, and vendor. The upload form passes those after extraction and again when the invoice number blurs. saveInvoice runs the same identity check before insert, so a re-upload of invoice 209 (K6EV5YRI) is rejected when vendor, date, and amount match instead of creating another invoice and billed cost. The truncated-prefix match only runs when those three fields are actually present; the empty date / -1 / null sentinels are gone.

Comment on lines +106 to +107
like(invoices.invoiceNumber, `${prefix} %`),
like(invoices.invoiceNumber, `${prefix}-%`),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5e2f48. The candidate query now also loads separator-free stored numbers with invoice_number ~* '^PREFIX[0-9]+$' (for example K6EV5YRI0095, which migration 0034 does not rewrite). chooseConflict already treats that as the same receipt as K6EV5YRI 0095, so a re-ingest matches the existing row instead of inserting another invoice and billed cost.

Comment thread src/lib/invoice-extraction.ts Outdated
Comment on lines +188 to +190
const parsedNumber = parseInvoiceNumberFromText(text);
if (parsedNumber.status === "error") {
return { success: false, error: parsedNumber.error };

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5e2f48. The strict prefix/suffix parser and the canonical rewrite run only after the receipt is identified as Anthropic: the PDF text contains "Anthropic", or the extracted vendor is an Anthropic legal name. Another supplier's 12345678 stays on the generic path and is not a 422, and 1234567890 is left as-is instead of being rewritten to 12345678 90. A missing Anthropic suffix is still a 422.

Manual re-uploads pass vendor, date, and amount, so a truncated row such as invoice 209 is still a duplicate, and save rejects that identity. The lookup also loads separator-free stored numbers. Strict prefix/suffix parsing runs only once the receipt is identified as Anthropic.

Co-authored-by: Cursor <cursoragent@cursor.com>
@studert
studert merged commit 9578e29 into main Nov 2, 2026
7 checks passed

This branch was successfully deployed

1 active deployment
Preview — c5e2f48e Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants