Projects / Case 02 of 02 FastAPI · SQLite · pytest / In progress

It was already a ledger.

The documents a small business drowns in, from supplier invoices to marketplace payouts and shipping records, turned into a multi-step income statement with margins and a sales-tax summary for any period you ask for.

Source on GitHub · not yet public Why ↓
01The accident

It became the ledger without anyone deciding it should be one.

The tool that splits supplier invoices was writing a line-items CSV as a byproduct. Within a couple of months, that file was the thing I actually opened to answer questions. So I promoted it: one shared data spine, with intake, pricing, invoicing and export all reading from it instead of from each other's output.

Compare it with the KPI system at work: many surfaces, one store, nothing keeping its own copy. I reached the same answer twice without noticing, once for federal compliance and once for a card business, which is the strongest argument I have that it isn't a preference.

02One spine, four tools Local-first, no cloud in the path
In
Document intake
Supplier invoices split into line items
Drag and drop
Marketplace, shipping and payment exports
Review inbox
Anything it doesn't recognise waits for a person
The ledger · 1 SQLite file
Voided, never deleted
The number is retired and never reused
Every row links its source
Back to the PDF it was read from
Category rules persist
Decided once, applied to every future import
Out
Income statement
Revenue, less COGS, less opex, down to net
Accounting export
The ledger in the format a bookkeeper expects
Invoices and receipts
Generated from the same rows they report on

A bad invoice is voided, never deleted. The number is retired, its revenue reversed, and it is never reused. Deleting would have been one line of code, and nobody would have noticed until the one moment it mattered.

It runs local-first on a single file, no cloud service in the path. For financial records that's a decision, not a limitation: the books don't stop working because someone else's outage says so, and nothing leaves the machine. Every row carries the business it belongs to, so one install keeps several sets of books that never touch.

03Four modules, one set of rows
1 Intake

Splitting the pile

A supplier PDF holding a dozen invoices comes apart into individual documents, then line items. Duplicates are caught on import, category rules decided once apply to everything after, and anything unrecognised waits in a review inbox rather than guessing.

2 Ledger

The rows themselves

Searchable and editable, every row linking back to the PDF it was read from. One file, backed up in a click.

3 Pricing

What the inventory is worth

Live market prices per item, a strategy applied across the inventory rather than card by card, and reviewed changes pushed back to the live storefront. Marketplace fee rates are worked into what a sale actually nets, because a price that ignores them isn't a price.

4 Reporting

The statement

Revenue less cost of goods to gross profit, less operating expenses to net, with margins and a sales-tax summary for any period. The same rows export to the shape an accountant expects, and generate outbound invoices and receipts.

None of the modules own the data. They read and write the same rows. That's what makes it a system rather than four scripts sharing a folder, and it's why a fifth module is an afternoon rather than a rewrite.

← Deck Odds Back to Projects →
← Home matt@matthewbraner.com © 2026 Matthew Braner