Factory

ledgerline

Map at a41c9e2 · 12 Oct 2026 · session 9

Invoices and the API are mapped and guarded by checks. The current bottleneck is PDF rendering. Reports and the old admin are still in the fog.

Win condition

Invoices, payments and the API reach assembler tier, and month-end runs in under 10 minutes.

by hand 22.0%burner 41.3%assembler 30.0%bots 6.7%

Since last time

Payments moved up to assembler: characterization tests pin refunds, and the suite runs in the pre-commit hook. reports went stale: 4 files changed since it was checked.

The map

APP JOBS SHARED app/invoices · 18,400 lines · 61 changes · tier 2 assembler · verified invoices 18.4k lines app/admin-legacy · 15,100 lines · 3 changes · tier 0 by hand · unknown admin-legacy 15.1k lines app/reports · 12,800 lines · 22 changes · tier 1 burner · stale reports 12.8k lines app/payments · 9,200 lines · 34 changes · tier 2 assembler · verified payments 9.2k lines app/api · 6,400 lines · 41 changes · tier 3 bots · verified api 6.4k lines app/auth · 2,100 lines · 5 changes · tier 1 burner · sketched auth jobs/month-end · 4,300 lines · 18 changes · tier 1 burner · sketched month-end 4.3k lines jobs/pdf-render · 3,900 lines · 27 changes · tier 1 burner · sketched pdf-render 3.9k lines jobs/imports · 2,600 lines · 2 changes · tier 0 by hand · unknown imports jobs/email · 1,200 lines · 6 changes · tier 2 assembler · verified emai shared/lib · 11,200 lines · 29 changes · tier 1 burner · sketched lib 11.2k lines shared/db · 5,300 lines · 12 changes · tier 1 burner · sketched db 5.3k lines shared/templates · 3,400 lines · 9 changes · tier 0 by hand · unknown templates
by handburnerassemblerbotsunmappedhotspotstale
Tap a tile.

Size is lines of code. Colour is the automation tier: how much an agent may do there alone. An orange outline marks an area that changed a lot recently.

Current bottleneck

PDF rendering runs one invoice at a time

sketchedMonth-end renders every invoice in a single loop and waits for each PDF before starting the next. That loop is about 30 of the 38 minutes.

Plan: Pin today's PDFs with byte-for-byte snapshot tests, then render in a small worker pool. Run it as the month-end-time hill climb.

jobs/month_end.php:212 · profile of the 3 Oct run: 31m 40s in render_invoice()

Questions for you

Does anyone still use admin-legacy, or did the new admin replace all of it?

admin-legacy · 15.1k lines · 3 changes in 90 days

Must invoice PDFs stay byte-identical, or only look the same?

Decides how strict the snapshot tests for pdf-render must be.

Why does month-end skip invoices created after 23:00 on the last day?

jobs/month_end.php:88 · no comment, no linked issue

Recommended hill climbs

Shorten the month-end run

It blocks finance for most of an hour, and PDF rendering is most of it.

metric
Wall time of month-end on the 2,000-invoice fixture
now
38 min
target
under 10 min
area
jobs/month-end, jobs/pdf-render
Ask your agent: /hillclimb month-end-time

Cut queries on the dashboard

The dashboard runs one query per customer. It slows as customers grow.

metric
SQL queries per dashboard load
now
214
target
under 10
area
app/invoices
Ask your agent: /hillclimb dashboard-queries

How it flows

How an invoice is sent

InvoiceController POST /invoices/:id/send InvoiceService totals, tax, lock pdf-render one at a time email job queue + retry

Orange marks the current bottleneck.

Findings

Tax is calculated in two places

highverifiedThe invoice screen and the PDF compute tax separately. They round differently on mixed-rate invoices.

app/Invoice/Totals.php:40 · jobs/pdf/Totals.php:18

Dashboard runs one query per customer

medsketchedA loop loads each customer's balance on its own.

app/Dashboard/Balances.php:61 · 214 queries on the demo data

The utils file is a junction

medverified38 files import it, and it mixes dates, money and HTML helpers.

shared/lib/utils.php · 1,840 lines

Import job has no tests

lowunverifiedIt ran last in March. It may be dead.

jobs/imports · last change 7 months ago

Upgrades

Why it's like this

Why invoices lock at 23:00

The old bank export ran at 23:30 and read the invoices table directly. Locking at 23:00 kept totals still while it ran. The export moved to the API in 2023, so the lock may no longer be needed.

From: engineer, session 4 · git blame on month_end.php

Two admins

The new admin was built beside the old one to avoid a big-bang switch. Finance still uses the old one for credit notes.

From: engineer, session 2

Hotspots

Size × recent changes. A small share of files usually holds most of the risk.