Lopifi Tools — the accountant's toolbelt
A Chrome extension (MV3) and a matching /tools section on the docs site, running the same
engine. Local only by default: nothing leaves the machine, and there's no account, server or
telemetry.
AI runs on Chrome's built-in models (Gemini Nano behind LanguageModel, Summarizer
and Proofreader, an on-device translation model behind Translator), so a client's bank
statement never touches a network.
Who this is for#
The person who spends their day between a PDF and a spreadsheet: a bank statement they must foot, an AP invoice they must key, a lease they must read, a TB that is out by 8,100 (a transposition), a deposit of 14,203.55 that has to be broken into the four invoices it paid.
Today that person does each job below with a desk calculator, a printout and a pen, or with a web tool that uploads the client's data to somebody else's server.
The three non-negotiables#
- Local only. IndexedDB and the file system. The only network call the extension can make is the one the user makes by clicking a link. The manifest requests no host permission that would allow exfiltration, and there is no remote code.
- Money is minor units. The same rules as the
@lopifisystem: integers, per-currency scale, accounting negatives,null ≠ 0. Every parser and every schedule generator insrc/tools/librounds once, at the end. Nothing assumes a scale of 2. Every money tool has a Currency field (ISO 4217, default USD) and reads, prints and copies at that currency's exponent fromcurrencyScale(): JPY 0 (1,500,000), USD 2, KWD 3 (1.234). FX conversion takes a from and a to currency and applies each one's scale. - Provenance survives. Every note, tick mark and tape entry records who, when, which page, which document hash. A note pack you send a colleague carries its own audit trail.
Part 1 — The PDF layer#
Chrome's built-in PDF viewer is a closed plugin you can't draw on, so the extension ships its own
viewer built on pdf.js and routes PDF navigation into it. The user can turn the takeover off and
open a PDF in it by hand at any time.
1.1 Selection sum — the reason this exists#
Drag a box over a column of figures on the page. The extension reads the text layer (no OCR
guessing), pulls every number in the marquee, and shows n, sum, average, min, max and the
count of negatives in a floating pill as soon as you let go. Shift-drag adds a second region. Click a
single number to add it to the tape.
Reading the text layer, it can tell what the printed page leaves implicit: (1,234.56)
is negative, 1.234,56 is European, 1,234.56- is a trailing-sign mainframe report, $ and
USD are noise, and a page number is not an amount.
1.2 The tape (adding machine)#
A running tape in the side rail. Every entry has an amount, an optional memo, a source (page +
coordinates, or typed), and a sign. It works like an adding machine: entries stay visible, the
subtotal is always live, and you can insert, strike and re-order without losing history.
=1200+340*2 works in the entry field, because accountants come from spreadsheets.
Tape actions: subtotal marker, sign flip, ×(-1), % of subtotal, copy as TSV (pastes into Excel as rows), copy the total alone, and "post to note" which drops the tape onto the page as an annotation.
1.3 Foot and cross-foot#
Select a column → Foot: compares your sum to the printed total nearest the bottom of the selection and tells you the difference. Select a block → Cross-foot: checks that the row totals and the column totals agree to the grand total, and shows which cell breaks it.
1.4 Tick marks#
The workpaper convention. A palette of marks (✓ ✗ ⌥ ∆ ○ ⊕ ¹ ² ³ and color
variants) placed by click on any figure. Each mark carries a legend entry you define once per
document ("✓ = agreed to bank statement"), and the legend prints on the export. Filter the page by
mark; jump between marks; count them ("14 of 22 lines agreed").
1.5 Notes, highlights, redaction#
- Sticky notes anchored to a text quad (they survive re-render and zoom).
- Highlight, underline, strike-through over selected text.
- Freehand and arrow for the screenshot-into-email case.
- Redaction that removes the text run on export, instead of drawing a black rectangle over it.
1.6 Extraction#
- Table → clipboard/CSV. Marquee a table; column boundaries are inferred from x-positions of the text runs, and you can drag the inferred boundaries before copying.
- Find all amounts / dates / invoice numbers / account codes on the document, as a list you can copy or send to the tape.
- Text layer check. A page with no text layer is a scan, and the extension says so up front instead of summing nothing.
1.7 Compare#
Two PDFs side by side with the numeric diff called out: same-label lines whose figures moved, lines present in one and not the other. The month-over-month TB, the redlined lease, the restated statement.
1.8 Document integrity#
SHA-256 of the file on open, shown in the workpaper header and stamped into every export. If the client sends a "corrected" copy, the hash changes and you see it.
Part 2 — Chrome built-in AI (on-device)#
Every AI feature is optional and local. Each output is labeled as machine-generated and is never written into a figure the user might read as the truth.
| Feature | API | What it does |
|---|---|---|
| Summarize | Summarizer |
Key points of a lease, contract, engagement letter |
| Extract | LanguageModel (JSON schema) |
Vendor, invoice №, dates, subtotal, tax, total, currency, terms — then checks the arithmetic itself and flags a total that does not equal subtotal + tax |
| Classify | LanguageModel |
Invoice / statement / receipt / contract / tax notice — drives the default tool layout |
| Explain | LanguageModel |
"What does this clause commit us to?" over a selection |
| Translate | Translator |
The foreign vendor invoice |
| Draft | LanguageModel |
The PBC request, the review note, the "please explain this variance" email — from your tick marks and notes |
| Proofread | Proofreader |
The note you are about to send the client |
Extraction output always appears as a form to confirm and is never auto-posted. Every AI field is marked with provenance. If the on-device model is unavailable, the feature is hidden instead of falling back to a cloud call.
Part 3 — Notes: local, portable, shareable#
Storage is an append-only operation log per document, keyed by the document's SHA-256, in
IndexedDB. Ops carry {id, authorId, authorName, at, kind, payload}.
- Export →
.ledgernotes.json: the op log plus a document fingerprint (hash, page count, page sizes, first-page text digest). - Import → merges by op id. Because the log is append-only and each op is idempotent, two people annotating the same statement can merge in either order and end up with the same notes. Striking someone else's note is itself an op, so nothing is lost, only superseded.
- Fingerprint mismatch is shown to the user: "these notes were made against a different file — 3 pages vs 4."
- Sharing means sending a file. Email it, drop it in the client folder, put it in the workpaper. There's no server, no account and no link that outlives the engagement.
- Export also to Markdown (a review-note summary) and annotated PDF (marks, notes, tick
legend and workpaper header burned in via
pdf-lib).
Part 4 — The quick tools#
The calculators live at /tools on the site. The extension doesn't render them: its side panel
drives the open workpaper and links out to them. Each one is a pure function in src/tools/lib with
unit tests, and the extension imports the same arithmetic for its own figures.
Selection sum anywhere. It works outside PDFs too. Highlight figures in Xero, QBO, NetSuite, a Gmail table or a CSV preview on GitHub, and a pill shows count/sum/average.
Reconciliation & investigation#
| Tool | Why |
|---|---|
| Difference diagnoser | Out by 8,100 → divisible by 9 → transposition, and difference ÷ 9 says how much the two swapped digits differ by. Also catches a line omitted or posted twice, a sign flip (half the gap is a figure in the data), a decimal slide (the "off by a factor of 10"), and per-line rounding. |
| Subset finder | A deposit of 14,203.55 against 40 open invoices: which combination sums to it? Meet-in-the-middle over minor units, so it's exact with no floating point. |
| Two-list matcher | Bank lines vs GL lines: exact (amount and day), then the same reference whatever the date (INV-0042 = inv 42), then amount within a date window, then many-to-one. Outputs the unmatched two ways. |
| Duplicate finder | The same reference whatever the date, same vendor + amount within N days (14 by default — under a month, so rent is not flagged), and same-amount/different-vendor. |
| Benford's law | First-digit distribution with a χ² result and Nigrini's mean absolute deviation over a pasted column. |
| Round-number test | Concentration at .00 / .50 / 000, the other cheap fraud signal. |
Schedules (all produce a journal-ready table)#
Straight-line, double-declining and sum-of-the-years'-digits depreciation — accelerated methods computed per asset-year (DDB at factor ÷ life in years on net book value, salvage only a floor, switching to straight-line when that gives more; SYD over whole years) and each year's charge spread evenly over its months — with mid-month, mid-quarter (10.5/7.5/4.5/1.5 months by in-service quarter) and half-year (six months of the first year's charge in the first calendar year, the rest after the life ends) conventions. Prepaid amortization even, or by days split at calendar month-ends. Deferred revenue release, accrual and reversal, loan amortization (payment solve, interest/principal split, balloon paid with the final installment, first payment one month after funding), lease PV and the ROU asset/liability roll-forward under ASC 842 / IFRS 16 — the liability's payment is fixed on every row and the final row's interest closes it, and the ROU asset amortizes straight-line for a finance lease or as the plug to a straight-line cost for an ASC 842 operating lease. Even spreads hand the rounding remainder out one minor unit at a time across the last periods; the other methods let the final period absorb it.
Money maths#
Penny-perfect allocation (largest-remainder, sums to the total exactly), VAT / sales tax gross↔net at any rate, markup vs margin, FX conversion with a rate you type between two named currencies, each at its own scale, plus realized / unrealized FX gain-loss (signed by whether the item is a receivable or a payable), NPV / IRR / XIRR, effective vs nominal rate, number to words for checks (US grammar, no "and" inside the number) or British cheques.
Dates & terms#
Fiscal period resolver (any FY start), payment-terms calculator (Net 30, 2/10 net 30 and 2/10, n/30, 2/10 EOM net 30, EOM and Net 10 EOM, 15 MFI) with the discount decision, day-count conventions (Actual/365 Fixed, Actual/360, 30/360 Bond Basis, 30/360 US end-of-month, 30E/360, and ISDA actual/actual — the period split at each January 1, leap-year days over 366 and the rest over 365), business-day arithmetic, and the day count that makes a proration exact.
Software spend#
| Tool | Why |
|---|---|
| Annual vs monthly billing | Paying a year up front is a loan to the vendor. The extra cash out on day one buys the eleven monthly bills that never come, and the rate that makes the two equal is what the discount pays. Monthly is billed at the start of each month; break-even is the fewest whole months at which the annual price is no more than the monthly one. |
| Prepaid software release | The payment debits prepaid expenses and credits cash, a card or payables; each month then debits the expense and credits prepaid. The release is even, in whole minor units, and the rounding remainder is spread one minor unit at a time across the last months so the schedule ties. |
| Unused seat cost | Idle seats are paid seats minus seats in use, priced for a year. The right-sized count is active seats plus a buffer rounded up, and a lock-in period holds back the months in which seats cannot come off. |
| Renewal uplift and notice date | Each renewal is rounded to a whole minor unit as an invoice would be, and the next rise applies to the invoiced price. The notice deadline is the renewal date minus the notice days. |
| Cancel mid-term cost | Unused value is the price paid over the term, times the months left. It is refunded only when the contract says so; otherwise cancelling stops the renewal, not the bill already paid. |
| Self-hosting vs SaaS cost | The yearly cost is hosting, backups and tooling, plus admin and restore-test hours at the hourly rate the reader enters. Break-even seats is that cost over the vendor's yearly price per seat. |
| Build vs buy break-even | Build cost is hours at the hourly rate. The built tool then costs upkeep hours plus hosting each year, and breaks even in the first month its cumulative cost falls to the purchase price's. Undiscounted. |
| Software spend per employee | Spend over the period is annualized by twelfths, then divided by people. A share of revenue is shown only when revenue is entered. |
Data hygiene#
Number cleaner: paste anything a report throws at you — (1,234.56), 1.234,56, 1,234.56-,
$ 1 234.56, 1.2k — get clean signed values, one per line, ready for the tape or Excel.
Journal entry balancer: paste or key lines, see the debit/credit totals, the out-of-balance,
and copy an import-ready CSV. Trial balance checker: paste a TB, get the balance check plus
sign anomalies against each account's normal balance (contra accounts carry the opposite one:
accumulated depreciation and allowances a credit; sales returns, allowances and discounts, treasury
stock and owner drawings a debit; bank and processing fees are read as expenses, not cash).
Identifiers#
IBAN (mod-97, check digits 02–98, registry length), ABA routing (3-7-1 checksum and assigned prefix), EIN (assigned prefix), VAT number format by country plus the national check digit where one is published, ISIN, LEI (ISO 17442), credit-card Luhn. All offline, and format/checksum only, with no lookup and no network.
Part 5 — What we deliberately do not build#
- No cloud sync, no account, no "share link". Sharing is a file.
- No OCR. A scan gets a clear warning instead of a plausible wrong number.
- No cloud AI fallback. If Gemini Nano isn't there, the AI panel isn't either.
- No auto-posting to a ledger. Every output is something you copy, look at and paste.
- No "confidence score" on extracted figures. You confirm the form or you don't.
What is built, and what is not#
This document is the design. v0.1 implements most of it, and the rest is specified but not yet
written. No user-facing page claims a feature that isn't shipped.
Shipped in v0.1
Selection sum · foot · the tape (with spreadsheet arithmetic, strike, subtotals, post-to-page) · tick marks with an editable legend · sticky notes with replies, clear and strike · highlight · redaction (drawn opaque, with an explicit warning about what that does not remove) · table inference to TSV · find text / regex / all amounts / all dates · scanned-page detection · SHA-256 integrity · the note-pack export/import merge with fingerprint checking · review-note Markdown · annotated-PDF export with a workpaper cover page and notes index · page-range extraction · on-device summarize / classify / extract-with-arithmetic-check / ask / draft-review-note · selection sum on any page · all 22 quick tools, on both surfaces.
Specified, not yet built
| Feature | Where it stands |
|---|---|
| Cross-foot | crossFoot() exists in extension/src/lib/pdf-text.ts and is tested; there is no UI for selecting a block as a grid yet. |
| Compare two PDFs | Not started. Needs a second document context and a line-matching pass over the two text layers. |
| Freehand and arrow annotations | Not started. The op log already carries anchors that would hold them. |
| True redaction | Export draws opaque rectangles. Removing the underlying text run means rewriting the content stream, which pdf-lib does not expose. Until then the export says plainly what it does. |
| Translate / proofread | Wrapped in extension/src/lib/ai.ts, not yet in the viewer. The translator needs a source language, and nobody has designed how the user picks one. |
| PDF merge from several files | mergePdfs() exists; there is no multi-file picker. |
| Subset finder over figures read off a PDF | The tool works on pasted lists. Feeding it a marquee selection directly is next. |
Layering#
src/tools/lib/*.ts pure, framework-free, unit-tested — the whole toolbelt
↑ ↑
src/app/tools/* extension/src/*
(Next.js, @lopifi UI) (MV3: viewer, overlay, side panel, notes store)