Plots are rarely paid for by one person. Plot Ledger records who funded what, works out the ownership that follows from it, and splits every sale across investors on their own agreements — down to the last cent, with nothing left to work out by hand.
The problem
One plot can be funded by several people at once, each on different terms, each splitting their profit differently between reinvestment and cash. Every sale means recalculating all of it, and a single missed reinvestment leaves the capital figures wrong from then on.
Who put money in, and how much?
What share of this plot does each of them own?
What did the plot make when it sold?
Who gets how much of that?
How much goes back to work, and how much is paid out?
What is free to invest again right now?
Ownership follows from what each position actually paid, so a jointly bought plot needs no special handling.
Reinvested profit grows the pools automatically — capital never stays frozen at the opening figure.
Cash paid out is profit only. Returned principal is never mistaken for a payout.
Losses fall on whoever provided the capital. The owner takes a share of profit, not of loss.
How a sale resolves
Each stage divides what the one above it produced. Every stage adds back to its parent exactly, which is what makes the final row trustworthy.
1 · what the plot made
Sale price less buy price
$18,000.00
divided by how much each position put in
2 · each position's part
Owner capital
$9,000.00
50% of the plot
Investor A
$4,500.00
25%
Investor B
$4,500.00
25%
each position split on its own agreement
3 · owner and investor shares
Owner total
$13,050.00
Investors total
$4,950.00
each share split between reinvestment and cash
4 · where it ends up
Back into owner pool
$9,787.50
Owner cash
$3,262.50
Back into investor pools
$3,465.00
Cash paid to investors
$1,485.00
Worked example
An $80,000 plot sold for $98,000. Every figure below came out of the real calculation engine; the amounts are illustrative.
Buy price
$80,000.00
Sale price
$98,000.00
Plot profit
$18,000.00
| Position | Invested | Owns | Its part | Owner gets | → reinvested | → cash | Investor gets | → reinvested | → cash |
|---|---|---|---|---|---|---|---|---|---|
| Owner capitalall profit retained · 75% reinvested | $40,000.00 | 50% | $9,000.00 | $9,000.00 | $6,750.00 | $2,250.00 | — | — | — |
| Investor A50 / 50 · they reinvest 70% | $20,000.00 | 25% | $4,500.00 | $2,250.00 | $1,687.50 | $562.50 | $2,250.00 | $1,575.00 | $675.00 |
| Investor B40 / 60 · they reinvest 70% | $20,000.00 | 25% | $4,500.00 | $1,800.00 | $1,350.00 | $450.00 | $2,700.00 | $1,890.00 | $810.00 |
| Total | $80,000.00 | 100% | $18,000.00 | $13,050.00 | $9,787.50 | $3,262.50 | $4,950.00 | $3,465.00 | $1,485.00 |
The engine
The calculation engine is pure and database-free: it takes what a plot cost, what it sold for and who funded it, and returns where every cent went. Nothing else in the codebase does arithmetic on money.
// Give every position the floor of its exact share,
// then hand out the leftover cents, largest remainder first.
for (let i = 0; i < weightsCents.length; i += 1) {
const exactNumerator = magnitude * weightsCents[i]!;
const share = exactNumerator / totalWeight;
base.push(share);
distributed += share;
remainders.push({ index: i, remainder: exactNumerator % totalWeight });
}
let leftover = magnitude - distributed;
// Largest remainder first; ties resolved by original order,
// so the same inputs always give the same answer.
remainders.sort((a, b) =>
a.remainder === b.remainder
? a.index - b.index
: a.remainder > b.remainder ? -1 : 1,
);
for (const entry of remainders) {
if (leftover <= 0n) break;
base[entry.index] = base[entry.index]! + 1n;
leftover -= 1n;
}Splitting a total by weight almost always leaves a remainder. Floors alone lose those cents; rounding each share independently can invent them. The leftover is handed out one cent at a time, largest fractional remainder first, so the parts always sum to the original — and ties break on position, so the result never changes between runs.
// Round one side, subtract for the other,
// so no cent can appear or vanish.
const ownerProfitCents =
multiplyCentsByBps(positionProfitCents, ownerProfitBps);
const investorProfitCents =
positionProfitCents - ownerProfitCents;
const ownerReinvestCents =
multiplyCentsByBps(ownerProfitCents, ownerReinvestBps);
const ownerPersonalCashCents =
ownerProfitCents - ownerReinvestCents;Within a share, only one side is ever rounded. The other is whatever is left after subtracting it. Two independently rounded halves can drift a cent apart from the whole they came from; a half and a remainder cannot.
Engineering
Money is an integer count of cents in a bigint, stored in BIGINT columns. Percentages are integer basis points, so 68.2% is the number 6820. A cent cannot be lost to binary rounding because no float ever touches the ledger.
Profit is divided by the largest remainder method, so the parts always add back to the whole. Within a share, one side is rounded and the other derived by subtraction — a cent can never appear or vanish between them.
Every distribution is verified before it can be written, and the database enforces the same balances with CHECK constraints. A split that does not reconcile is physically unstorable.
The profit agreement is copied onto the funding row at the moment a plot is funded. Renegotiate later and past plots keep the terms they were funded on. A finalised sale is a snapshot, never recomputed.
There are no mutable balance columns to drift out of sync. Every pool, every available figure and every payout is recomputed from immutable capital transactions, funding rows and sale snapshots.
Middleware, every page and every server action independently verify the session and scope each query to its owner. Financial writes take row locks, so two requests can never both finalise one sale.
Built with
Next.js
App Router, Server Components
TypeScript
strict, no implicit any
PostgreSQL
hosted on Neon
Drizzle ORM
typed schema and migrations
Auth.js
credentials, bcrypt, owner-only
Tailwind CSS
with a small component kit
Zod
one schema, client and server
Vitest
101 tests on the money engine
Statements, not spreadsheet dumps
Portfolio, plot and investor statements are laid out for A4, repeat their table headers across pages, and never split a plot's figures over two sheets.
Built for a phone as much as a desk
Installable, with every table becoming a readable card rather than a sideways scroll, and the four screens used most reachable from the bottom bar.