Engineering study
Double-Entry Ledger Engine
Colombian accounting core where the money math is provably exact.

- Role
- Solo build, full stack
- Context
- Senior technical assessment
- Year
- 2026
- Source
- Available
- Tests against real Postgres
- 107
- Money, end to end
- NUMERIC(18,2)
- PUC accounts seeded
- 65
- Tax report generated
- DIAN 1001
Tests against real Postgres
Money, end to end
PUC accounts seeded
Tax report generated
Stack
- Python
- FastAPI
- SQLAlchemy
- PostgreSQL
- Alembic
- Next.js
- React
- TypeScript
- TanStack Query
- Docker
The problem
Accounting software has an unusual property: a bug is not a degraded experience, it is a legal problem. Entries must balance, posted history must be immutable, voucher numbers must be unique and gapless under concurrency, and cents must never be approximated. Build it on floating point and you will be wrong by amounts too small to notice and too large to explain.
What I built
A double-entry engine implementing the Colombian PUC chart of accounts: journal entries, general ledger, accounting periods, third parties, and the DIAN información exógena format 1001 tax report. FastAPI and PostgreSQL underneath, a Next.js App Router frontend on top, wired so an evaluator can post a balanced entry end to end and watch it land in the ledger.
Engineering decisions worth defending
Decimal from the API boundary to the database column. Money is
NUMERIC(18,2) in Postgres and Decimal in Python, and the JSON layer
rejects floats rather than silently coercing them. There is no point in the
stack where a value passes through a binary float.
An ordered lock hierarchy for concurrent posting. Two users posting entries
to the same period at the same moment is the ordinary case, not the edge case.
Rows are locked with FOR UPDATE and FOR SHARE in a consistent, documented
order — which both prevents duplicate voucher numbers and makes deadlock
impossible by construction rather than by retry.
Immutability enforced by the database. Posted entries cannot be modified,
and that rule lives in PostgreSQL triggers, not in a service method. Application
code can be bypassed by the next developer in a hurry, a migration script, or a
psql session. Triggers cannot.
Ledger balances via window functions. Running balances are computed in SQL rather than accumulated in Python, so the ledger stays correct regardless of pagination and does not require loading a period into memory to render one page of it.
Reproducible tax reports. The generated 1001 XML is persisted, not regenerated on demand. A report filed in March must still produce byte-identical output in November, even after the chart of accounts has moved on.
Outcome
107 integration and unit tests running against a real PostgreSQL instance in CI — not mocks — plus six documented end-to-end scenarios. The concurrency and money-precision tests are the ones I would point at first.