LedgerMap: what we could track
All tracking stays in our own PostgreSQL. We do not use PostHog now. Each item below says what we would record, which question it answers, where we capture it, and how important it is. The pictures are mockups with made-up numbers. They show what the report could look like. They are not screenshots of the app.
1. What we track today
Each entry has one row in statement_entry_ledgers. Each new choice overwrites it. We keep the final state, not the path to it. A failed parse leaves no record.
| Question | Today | What exists |
|---|---|---|
| What did we suggest? | Partly | Only exact matches are saved (source='matched', match_kind). Other suggestions are computed live and are not saved. |
| Auto-accepted or manual? | Partly | matched / auto (a suggestion was picked) / manual / books. We do not record the method (Ctrl+Enter, picker, bulk, same payee). |
| What did the user change? Twice? | No | We keep only the last value. A changed exact match leaves a rejection row with the ledger ID and no time. |
| Changes in the review phase? | No | We do not record the step. statement_entry_exports stores only the export time. |
| Searches, empty searches, gave up? | No | The search text in the picker never leaves the app. |
| Suspense: a decision or neglect? | Partly | The user can save the suspense ledger. An entry with no ledger also goes to suspense at export, and we cannot tell the two apart. |
| Parser failures | No | The server returns the error code and saves nothing. Only successful statements (with their parser JSON) are saved. |
| Which app versions people run | No | The app does not send its version to the server. |
| App errors and speed | No | The server logs only database and start-up errors. The app sends no crash reports. |
2. Decision trail for each entry
These are your items, plus the missing parts around them. Together they give a full history for each entry, from the first suggestion to the export.
A1 · What we showed
The suggestion and the “Possible” list (up to 3). For each: ledger, matcher kind, situation, the % likely shown, and its rank.
Was the right ledger in front of the user?
A2 · Each choice, with the method
Old ledger → new ledger, source, and method: Enter twice, Ctrl+Enter, a typed search, a suggested option, a bulk selection, same payee (Alt+G), contra Accept, or books Accept.
Auto-accepted, or chosen by hand?
A3 · Change count and flip-backs
We keep every overwrite, so we see “changed 2+ times” and A → B → A.
Where is the user unsure, or are we confusing them?
A4 · The step of each change
Contra, Suggestions, Suspense, Ledgers (review), Export, or All entries. Also changes after an export, from a later import of the same PDF.
How much does the review phase correct?
A5 · Silent acceptance
An exact match that the user never touched and exported as it was. Compare it with one the user opened and kept, and with one the user changed.
Are exact matches trusted, or checked?
A6 · Picker search sessions
The picker opened. For each query step: the match count and the rank picked. Also queries with no match, Escape without a choice, and the time spent. We would not save the full query text unless you approve it (§8).
Did the user search, fail, and give up?
A7 · How an entry reached suspense
The suspense ledger saved on purpose, or no ledger at export. Whether a search or a suggestion came before it.
Is suspense a decision, or a dead end?
A8 · Clears, unpairs, “don’t map”
A cleared choice, an unpaired contra, and an entry left out of mapping, with what it held before.
Which automatic results do users undo?
3. Parser completeness
The largest gap is failures. Today we see only the PDFs that parsed. We cannot say “X% of uploads work for bank Y”.
B1 · Every parse attempt
Each upload, failures too: upload SHA-256, page count, size, outcome code (validated, needs_review, scanned_pdf, unknown_format…), reason codes, duration, queue wait, and parser build hash.
What share of uploads work, for each bank and each build?
B2 · The route that read it
Saved recipe, layout detection, a recipe Codex learned, or codex-kept-reading. Also connector_id + version, match_score, and the Codex result (installed, failed, timed out, model).
Which layouts depend on Codex? Does Codex pay off?
B3 · What happens after needs_review
The reason codes, the rows marked to check, how many of those the user opened in the PDF, and the result: kept it, or Leave this PDF out.
Are review results usable, or thrown away?
B4 · Balance evidence for each statement
Reconciled steps compared with checked steps, and warnings (rows moved, repeated rows, notes skipped). This is already in the saved parser JSON. We need only a summary.
Which statements pass narrowly?
B5 · “Show in PDF” checks
Alt+O and Alt+Enter opens for each entry, and whether a change follows. Many opens for one recipe suggest the user doubts its reading.
Where do users not trust the parser?
B6 · Validator findings
For each statement and recipe: how many entries Validator found missing or repeated in Miracle. We record counts only, never book content.
Do some recipes drop or duplicate rows?
B7 · Bank confirmation
The bank ledger that the file name suggested, compared with the one the user confirmed.
Is the file-name guess right?
B8 · Password flow
Locked or not, whether a saved password worked, how many prompts, and how many wrong tries. We never record the password.
How much friction do locked PDFs cause?
B9 · Re-uploads and scans
The same PDF uploaded again, and scans per user or bank.
Do users retry failures, or bring the wrong file?
4. Auto-matcher
Today bun run matcheval measures the matcher on past books. These items measure it on real use, so the two can be compared.
C1 · Real precision of each exact matcher
Matched entries kept at export, compared with matched entries the user overturned, for each kind (upi, account, narration, ledgerName).
Is “exact” really exact in use?
C2 · Calibration of “% likely”
For each situation: the % shown, compared with how often the user accepted it. Real use could then update ledger_confidence_table.go, which today comes only from the replay.
When we say 80%, are we right 80% of the time?
C3 · Rank of the final ledger
The first suggestion, the 2nd or 3rd, “Possible”, found by search, or never listed.
How often was the answer close, but not first?
C4 · Coverage funnel for each import
Entries → auto contra → exact match → in books → suggested → suspense, then the result at export.
How much work is left to the user?
C5 · Contra results
Possible contra accepted or rejected, books transfers accepted or set aside, manual Alt+C pairs (the matcher missed them), and unpairs (the auto pair was wrong).
What does contra detection miss, and what does it get wrong?
C6 · Matcher version tag
Each suggestion has the matcher version (a commit or a number). After a change, we compare before and after on live data, as matcheval does on books.
Did the last matcher change help real users?
C7 · Learning effect
The “N other entries matched” count after a choice, and how many of those entries the user later changed.
Does one choice teach good matches or bad ones?
C8 · Miracle-history suggestions
Only the clue kind (same day of month, always this amount) and the result. Book content stays on the PC.
Are book-based suggestions worth their cost?
C9 · Effort for each entry
The time from shown to decided, the keys pressed, and entries per minute in each step.
Where does the user spend time?
C10 · Suggestions that changed
An entry’s suggestion changed between two views, because of other choices.
Do suggestions jump around under the user?
5. General app
D1 · App version and platform
The app sends a version header with each request (desktop or browser, OS). Also the update steps: checked, downloaded, installed, restarted.
Who still runs an old app? Is it safe to drop an old API?
D2 · Import funnel and drop-off
Started, PDFs added, banks linked, each step entered and left, export saved. Also where imports stop, and the time in each step.
Where do users stop or get stuck?
D3 · Errors
App crashes (the error boundary), API errors by code and route, 409 setup and ledger_revision conflicts, and offline-gate hits.
What breaks that users do not report?
D4 · Speed
API time for each route, parse time and parser_busy waits, and on the desktop the table render time and key-repeat lag (WebKitGTK).
Is it fast on real PCs, not only ours?
D5 · Keyboard or mouse
How often each shortcut is used, actions done with the mouse that have a key, and opens of the shortcuts help.
Is keyboard-first working? Which shortcuts are unknown?
D6 · Export results
Files saved, entries exported, entries left out (in books), entries exported again, and whether Validator later finds them in Miracle.
Does the export reach Miracle completely?
D7 · Ledger sync
Sync runs, ledgers added or changed, failures, and revision conflicts.
Are ledger lists fresh when users map?
D8 · Parties, tabs, storage
Parties for each user, open tabs, party switches, and storage used compared with the quota.
How do users work across parties?
6. What the report could show
These are four views from one report. All names and numbers are made up.
Decision trail for one entry
A1–A8 together: the full history of one entry.
- 10:02:11Suggested Shree Ganesh Traders · similarName · 62% likely · rank 1 of 3 (also: Ganesh Enterprises 21%, Shree Traders 9%)
- 10:02:40Accepted Shree Ganesh Traders · Ctrl+Enter · step Suggestions · 29 s after shown
- 10:02:41Taught 4 other entries matched (UPI ID)
- 10:09:05Changed → Ganesh Enterprises · typed search (3 queries, 1 with no match) · step Ledgers (review) · change 2
- 10:09:20Opened in PDF Alt+O
- 10:11:52Exported as Ganesh Enterprises · file 1 of 2
Is “% likely” honest?
C2 · first suggestions, last 30 days
The bar is the accepted share. The black mark is the % shown. In this example, category · many promises 41% but delivers 22%.
Show as a table
| Situation | Shown | Accepted | n |
|---|---|---|---|
| upi · all | 96% | 94% | 812 |
| narration · some | 78% | 71% | 344 |
| similarName | 62% | 58% | 290 |
| category · many | 41% | 22% | 518 |
| recurring | 45% | 49% | 96 |
Where entries end up
C4 · 2,140 entries in 31 exported imports
Show as a table
| Result | Entries | Share |
|---|---|---|
| Auto contra | 128 | 6% |
| Exact match, kept | 813 | 38% |
| Exact match, changed | 43 | 2% |
| Suggestion accepted | 578 | 27% |
| Suggestion changed | 235 | 11% |
| Chosen by search | 193 | 9% |
| Suspense, saved | 86 | 4% |
| Suspense, no ledger | 64 | 3% |
Parser scoreboard by recipe
B1 + B2 · all uploads in the last 30 days, failures included · build 9c41…
| Recipe | Uploads | Validated | Needs review | Failed | Codex sent | Median time | State |
|---|---|---|---|---|---|---|---|
| hdfc-savings v4 | 212 | 98% | 2% | 0% | 0 | 1.4 s | ✓ Good |
| sbi-current v2 | 147 | 91% | 6% | 3% | 2 | 2.1 s | ✓ Good |
| layout detection | 63 | 71% | 19% | 10% | 18 | 3.8 s | ▲ Watch |
| codex learned (new) | 21 | 62% | 24% | 14% | 21 | 48 s | ▲ Watch |
| no recipe (failed) | 17 | — | — | 100% | 9 | 31 s | ✕ Missing |
Today we could not build the “Failed” column or the “no recipe” row, because failures are not saved.
One picker search session
A6 · entry #4182/52 · step Suspense · ended in suspense after 41 s
This shows the case you asked about: the user searched, found nothing, and left the entry in suspense. With text masked (§8), the report would show “3 chars → 7 chars → 10 chars” and keep the match counts.
7. Rules for the data
| Storage | Append-only tables in our PostgreSQL, for example entry_events, parse_attempts and app_events. We never edit rows. Rows are scoped to the owner, like statements. |
| No copies of customer text | Events refer to the statement ID and the row number. The narration and the PDF are already saved, so the report joins them. We store no extra copies. |
| Miracle books stay on the PC | From book-based steps we send only the ledger the user chose and the clue kind. We send no postings, GSTIN, dates, or counts from the company files (this is an existing project rule). |
| No secrets | We never record passwords, session tokens, or file paths. |
| No slowdown | The app queues events in memory and sends them in batches every few seconds and when a tab closes. A key press never waits for a send. A failed send drops the batch. The app does not retry forever. |
| Old apps | The server accepts requests without events or a version header. Old apps keep working, and the report shows them as “version unknown”. |
| Size | About 5–10 events per entry, of about 200 bytes each, so about 1–2 KB per entry. 100,000 entries need about 100–200 MB. This is small for PostgreSQL. |
| Where you read it | Production serves only the API, so we add no web pages there. Suggestion: a local command (like bun run matcheval) that reads the data and writes an HTML report into .local/. |
8. Decisions for you
- Which items to build. Choose from A1–D8. My suggestion: all “Core” items first.
- Search text (A6). (a) Save the full query. This is best for finding missing ledgers, but it stores payee words a second time. (b) Save the length and match counts only. (c) Save the query only when it has no match.
- Retention. Keep events forever, or remove them after N months, with the statement, or with the party.
- Whose data. Production users only, or also local development. Mark or exclude your own test imports.
- Where you read the report. The local command (my suggestion), an admin-only screen in the desktop app, or later a tool such as Metabase.
- PR split. My suggestion is four small PRs:
- Tables and server-side capture: B1, B2, and A1–A5, A8, C1, C3–C6 from the server’s view, plus the D1 version header.
- App events: one batched endpoint, plus A2 method, A4 step, A6 search, A7, B3, B5, D2, and D3.
- The report command and HTML: the trail, calibration, funnel, and parser scoreboard.
- Later: D4 speed, D5 keyboard use, D6 export check, C8, C9.
- Today we keep only each entry’s final ledger. Failures, history, searches, the step, and app versions are not recorded.
- The largest gaps for completeness: parse failures (B1), real matcher precision and calibration (C1, C2), and the decision trail (A1–A8).
- Everything stays in our PostgreSQL. Events point to existing rows and copy no customer text. Book data stays on the PC.
- Your decisions: the items, the search text, retention, the report location, and the PR split.