rdmsm4x - changelog - RDReceipt receipt inventory 1641 unique files labeled and tagged - claude - TASK-20260926-73 - RDReceipt - 20260926-2311
Inventoried and labeled every receipt candidate with RDReceipt's pipeline; fixed a parser date bug; Finder-tagged 4,835 source files in place (no content/mtime change). Notes entry PENDING: headless Background session.
RESULT — TASK-20260926-73: RDReceipt receipt inventory
[2026-09-26 23:10:21 EDT · rdmsm4x] · run 22:57:51 → 23:10 EDT ·
claude@rdmsm4x (headless, session
expenses-receipt-inventory-20260926) · worktree
~/dev/_worktrees/rdreceipt-inventory-20260926, branch
expenses/rdreceipt-inventory-20260926
The answer Rich wanted: current expense state
- Inside Arista's 90-day window (since 2026-06-28), the only unsubmitted business items are recurring bills. The claimable ones are 3 Starlink invoices and 3 AT&T bills: $860.00 (AT&T capped at $110 per the draft's convention). That is proposed report R001. There are also 2 Verizon Wireless Business payments ($881.60; a third on 9/01 is a near-duplicate of the 8/27 one). Rich's draft marked those "borderline, claim $0", so they are listed but not grouped.
- No one-off receipt or invoice (meal, parking, travel) exists in 2026-06 through 2026-09. Either there were none, or they never reached these folders. The newest one-off receipts are Eataly on 6/15 (reservation only) and Joe's Ginger on 5/18.
- Everything older than the window, newest first: R002 (2025-12-09 → 2026-06-25, $2,942.62) and R003 (2025-09-10 → 2025-12-09, $2,937.66) cover the same ground as the draft's Batch 1 and Batch 2. Since 2024-08-17 (after the last Concur package), 84 grouped items come to $10,894.28. All of them are past 90 days.
- In all, 21 proposed reports (each ≤ $3,000, or a single item over the cap, flagged) hold 245 items worth $62,926.01. Most of the pre-2024 items are old enough that submission is doubtful. They are grouped so they're visible, and each group is flagged "older than 90 days".
Outputs (all in
~/dev/finance/expenses/inventory/)
| file | what | committed |
|---|---|---|
inventory.csv |
1,641 rows, one per unique sha256, with every path (zip members as
zip!member) and all fields |
finance e385911 |
summary.md |
counts by kind, category, submitted status and month; gaps; top duplicates; groups; policy flags; low-confidence list | finance e385911 |
report_groups.csv |
245 rows in 21 groups R001…R021, ≤ $3,000 each, newest first, split at the 90-day boundary | finance e385911 |
rdreceipt-inventory.sqlite3 |
RDReceipt's own store (the app's
ReceiptDatabase schema): 1,183 OCR'd receipts, plus the
additive table inventory_labels (1,641 rows) |
untracked on purpose: binary, rebuilt in ~4 min, holds raw OCR text |
Finder tags kind, YYYY-MM,
category and submitted|unsubmitted, plus xattr
com.eastcoastscience.rdreceipt.label (JSON), are on
4,835 on-disk files, with 0 errors. The 594 zip members
are not tagged, and every one of them also exists as a loose file.
Counts
| files read (all 7 roots, including 1,336 extracted zip members) | 6,171 |
| unique by sha256 | 1,641 (1,183 receipt-shaped and OCR'd, 458 other types) |
| exact duplicate copies | 4,530 |
| near-duplicates (same total, ≤ 7 days apart, same merchant or category) | 141 |
| ingest failures / hash failures | 0 / 0 |
| kind | receipt 616 · not-a-receipt 653 · recurring bill 318 · invoice 33 · statement 11 · order confirmation 10 |
| category | other 775 · meals 331 · phone/internet 312 · ground/parking/tolls 133 · hotel 39 · airfare 39 · supplies 11 · mileage 1 |
| submitted | submitted 181 · likely-submitted 460 · unsubmitted 1,000 |
| low-confidence (< 0.5) receipt-like files | 107 (none inside the 90-day window) |
| policy flags | meals need attendees 328 · meals missing an itemized receipt 287 · hotel missing an itemized receipt 21 · over $75 with a confirmation only 3 · phone/internet "wholly work-related" 312 |
| draft cross-check | 37/37 of expenses.csv found; 35 match
date and amount exactly; 2 (Eataly reservation, Joe's Ginger photo) have
no OCR'd total, so Rich's curated amount from the filename is used |
Positive control
~/Documents/_receipts/_q3-2024_receipts/24_09_starlink_092524_INV-USA-29503623-76769-94.pdf
→
kind=recurring bill, date=2024-09-25, merchant=Starlink, total=120.00 USD, tax=0.00, itemized=yes, category=phone/internet, confidence=0.85, unsubmitted, within_90_days=no, group R005.
Its Finder tags as read through URLResourceValues.tagNames
(the API Finder uses):
["recurring bill", "2024-09", "phone/internet", "unsubmitted"].
All fields match the invoice text (Invoice Date September 25, 2024;
Total Charges USD 120.00; Total Tax USD 0.00).
This control caught a real bug. Before the fix it
read 2026-09-25. The invoice's year wraps to the next
line, and NSDataDetector fills in the current year, which would have put
a two-year-old bill inside the 90-day window. The fix is in the shipped
parser, commit 9156fe3.
Second control, in the window:
bills/starlink/starlink_3764_2026-09.pdf → 2026-09-25,
Starlink, $140.00, R001, matching the draft's Batch 1 line exactly.
Negative control: rich_arista_W2_2017_TAX.pdf →
not-a-receipt with no total. Before the tax-form rule it
was a "receipt" dated today for $116,250.
Verification that nothing was modified
- After tagging, all 4,835 on-disk source files were re-hashed and re-stat'ed against the pre-tag backup: mtime changed 0, size changed 0, sha256 changed 0.
- The 5
~/Documents/arista_expenses*.zipfiles were read only (extracted to/tmp/rdr-inv-20260926/zips), and their sha256 values are unchanged (6f915e02…,cc2073c6…×2,8b809263…,b92827ff…). ~/dev/aristaand~/dev/apps/autoCEwere never opened. The harness refuses them (a~/dev/aristaroot exits 2 withREFUSED), and the labeler asserts that no manifest path contains either one.- Re-running the labeler is idempotent: on the second run all 4,835
tags came back
unchanged. - Undo:
python3 ~/dev/_handoff/rdreceipt-inventory-20260926/untag.pyrestores each file's pre-run tag xattr (none existed) and removes the label xattr. Tested on a scratch copy, where tags went from 4 to none. A--dry-runreports 4,835.
Code changes (worktree, committed by explicit path, not merged, not pushed)
| commit | change |
|---|---|
69d1f2a |
Apps/ReceiptRoo/Tools/inventory/InventoryHarness.swift:
a multi-root, persistent-store harness that compiles the
production engine
(ReceiptScanner/FileHasher/ReceiptReader/ReceiptClassifier/ReceiptFieldParser/ReceiptDatabase),
with an exclusion guard and a manifest of every file |
9156fe3 |
Parser fix in
Shared/Engine/ReceiptFieldParser.swift: wrapped-year dates,
and a year-less date now drops to weak confidence. New fixture
invoice-date-year-wrapped-to-next-line: 17/17
pass, and the previous parser fails exactly that case.
--reparse mode added: re-parsing 1,183 stored rows changed
29 dates. macOS app ** BUILD SUCCEEDED **
with the fix |
84a63c6 |
tools/inventory/label_inventory.py: labels, the
inventory_labels table, the CSVs and summary, and Finder
tags |
"ReceiptScannerKit" does not exist in this repo. The pipeline is
Apps/ReceiptRoo/Shared/Engine, and that is what ran.
Decisions made (DEC-style, reversible)
- Submitted = a byte copy in
_archive_submitted060424/,Receipts0816/, or DropboxReceipts (ALL)/2024-08/. The Dropbox folder is a copy of the Aug 16, 2024 Concur package: same filenames, 72 byte-identical files, created 2024-08-21. A near-duplicate of a submitted file inherits submitted. Measured effect: the Dropbox folder alone changed no row, because each of its unique files also has a copy in one of the brief's two folders. Near-duplicate propagation raisedsubmittedfrom 110 to 181. The Dropbox rule stays in as a guard for future files. likely-submittedis a CSV status only (dated ≤ 2024-08-16 and filed in an expense-archive folder, or carrying the 06/08/2023 export filename). The Finder tag staysunsubmittedfor these. Only proven submissions get thesubmittedtag, so a missed claim can't hide behind a guess.- Store location: a dedicated
rdreceipt-inventory.sqlite3using the app's schema, not the app's live Application Support store. The SQLCipher vault swap (STATUS item 6, DEC-20260901-02) is still pending, and writing thousands of rows into the plain store now would create data that has to be migrated. - Report grouping follows Rich's draft conventions: AT&T is claimed at a $110 cap, and Verizon and Google Fi are kept out of groups ("borderline, claim $0"). Groups split at the 90-day line so a within-window report never mixes with stale items.
- Sanity guards: a date later than the file's own
save time is rejected. A total over $10k is dropped and noted in
sanity_notes. Scan-timestamp filenames are used only as a 0.3-confidence fallback, flagged "date is the scan date".
Known limits
- These labels are heuristics. The 90-day-window rows were checked by
hand against the draft. Older rows were not. The 107 rows under 0.5
confidence are listed in
summary.md. - "Itemized" means the parser found at least 2 line items. It undercounts on photographed receipts, so the 287 "meals missing an itemized receipt" flags are an upper bound.
.txt/.emlbill transcriptions inbills/are inventoried asnot-a-receipt(a file-type rule) even though they record real bills.- The Apple Notes changelog is pending: this headless
session is
launchctl managername = Background, andnotes_changelog.zshrefuses to run from that. The file copy is in~/dev/LLM/Claude/changelogs/.
Verbatim commands
S=/tmp/rdr-inv-20260926
unzip -q -o ~/Documents/_receipts/arista_expenses.zip -x '__MACOSX/*' -d $S/zips/zip-cc2073c6
unzip -q -o ~/Documents/arista_expenses_folder.zip -x '__MACOSX/*' -d $S/zips/zip-8b809263
cd Apps/ReceiptRoo
swiftc Tools/inventory/InventoryHarness.swift Shared/Engine/ReceiptScanner.swift Shared/Engine/FileHasher.swift \
Shared/Engine/ReceiptReader.swift Shared/Engine/ReceiptFieldParser.swift Shared/Engine/ReceiptClassifier.swift \
Shared/Engine/ReceiptDatabase.swift Shared/Models/Receipt.swift -o /tmp/rdreceiptInventory -parse-as-library -lsqlite3
/tmp/rdreceiptInventory ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3 $S/manifest.json \
~/Documents/_receipts "$HOME/Library/CloudStorage/Dropbox-richh/Receipts (ALL)" $S/zips/zip-cc2073c6 $S/zips/zip-8b809263 \
~/dev/finance/expenses/receipts ~/dev/finance/expenses/bills ~/dev/finance/expenses/source_files
# → DONE files=6171 ingested=1183 resumed=0 dupes_in_run=4464 failures=0 excluded_entries=0 store_rows=1183 (23:00:31→23:04:36)
/tmp/rdreceiptInventory2 --reparse ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3 # built from 9156fe3
# → REPARSED rows=1183 dates_changed=29
/tmp/receiptFixtureCheck ../../core/fixtures/rdRECEIPT_field_parsing.json ../../core/fixtures/rdRECEIPT_document_classification.json
# → fixtures: 17 cases across 2 file(s), all pass
cd ../..
uv run tools/inventory/label_inventory.py --store ~/dev/finance/expenses/inventory/rdreceipt-inventory.sqlite3 \
--manifest $S/manifest.json --outdir ~/dev/finance/expenses/inventory \
--zipmap "$S/zips/zip-cc2073c6=<5 identical zips, | separated>" --zipmap "$S/zips/zip-8b809263=$HOME/Documents/arista_expenses_folder.zip" \
--drafts ~/dev/finance/expenses/expenses.csv --today 2026-09-26 --tag
# → unique=1641 groups=21 eligible=245 tags {changed/unchanged: 4835, zip-member-skipped: 4008} tag_errors=0 hash_errors=0Evidence files in this folder: ingest-run.log,
manifest.json (paths, sizes and hashes only, with no OCR
text), pretag-xattr-backup.json, and
untag.py.