The work
Where the money actually goes missing
A contracting firm's losses are rarely dramatic. A security deposit nobody claimed after the defect liability period ended. A bank guarantee that lapsed because its expiry sat in one person's memory. An earnest money deposit still with the department eight months after the tender was lost. A bill that has been at the Executive Engineer's desk for six weeks, which everyone assumes someone else is chasing.
None of these is a hard problem. All of them are invisible in a spreadsheet, because a spreadsheet has no opinion about what today is. The firm ran on Excel, WhatsApp and a Tally installation that is the statutory book of record and was never meant to be an operations tool.
Decision 01
A running-account bill is not lines plus GST
This was the first thing built and the first thing rebuilt. A departmental bill looks like an invoice and behaves nothing like one. It is cumulative — it states the value of everything built to date, then subtracts every earlier bill — and the percentages come off a figure the bill prints separately from its own total.
The department's arithmetic wins. Where their figure differs from ours, our bill must match theirs, so every computed value can be overridden with a recorded reason. Correcting a department is not a feature anyone wants.
Decision 02
Cash and a credit facility are different facts
A cash credit account carries a negative balance, because it is money owed to the bank. Summing it with the current accounts produced a dashboard that was not slightly wrong but inverted — it reported a firm badly short while it had money to spend and a facility it had barely touched.
One number
Current accounts plus the cash credit balance. Reads as ₹5.57 crore short. Nobody can act on it, and the people who know the business stop opening the page.
Three facts
₹3.55 crore in the bank. ₹91 lakh drawn on the facility. ₹5.88 crore still available to borrow. Only the first two belong in a forecast.
The same distinction runs through the thirteen-week forecast. A negative week does not mean insolvency; it means this is the week you start drawing on the facility, which is a thing a person can plan for.
An advance is not a cost. Money handed to a site supervisor has moved, not been spent — book both the advance and the voucher against it and every work that supervisor touched is overstated by the whole advance, silently.
Decision 03
Never print "overdue" against a promise nobody made
Most supplier accounts here have no agreed due date at all. Settlement is by cash availability, discussed with the supplier — that is not a gap in the data, it is the arrangement. An early version aged those balances against invented terms and printed them as overdue.
The first time somebody checks that column against the shop, they stop believing the report, and a report nobody believes is worse than no report. Such a balance is outstanding, aged from the bill date — and it still enters the forecast at full value, because a liability priced at zero days is the comfortable answer and the wrong one. Leaving them out had made thirteen weeks optimistic by ₹56 lakh in week one alone.
Rules that only exist here
Domain, not configuration
Roughly forty rules like these are encoded as pure functions with tests. They are the reason the system cannot be a generic project-management tool with the words changed.
-
अतिरिक्त सुरक्षा अनामत
Bid more than 10% below the estimate and the department demands an additional security deposit before the agreement — of the whole difference, not the part past 10%. The threshold is departmental, and an unrecorded rule is not zero. The screen has to say which it is.
-
Nomination capacity
An Educated Unemployed Engineer registration receives work without tender, inside one district, capped per work and per year, for a five-year window — and sub-letting carries a two-year suspension. Choosing the wrong vehicle for a bid is how a registration gets cancelled.
-
Mukadam settlement
A gang leader is engaged by the firm, not by a project, and works a few days at each site. One periodic payment splits across projects by the register's money — never by assumption, and never by head count.
-
194C
TDS is charged on the amount before GST, and the cost booked includes it. The rate follows the payee, not the expense: 1% to an individual, 2% to a company, nil for a transporter who has filed a declaration.
-
Offsets count back
A reminder is anchored on the date the thing is due, never on the event that started the clock. Anchored the other way, three warnings fired twelve to fifteen days before the work order existed — and looked like they were working.
-
Statement chain
An imported bank statement must carry its own running balance forward, exactly, to the paisa. That one check catches a truncated download, a mis-parsed sign and a duplicated page — none of which look wrong row by row.
Constraints
Built for a 25-person firm, not for a demo
Every architectural choice came from the same three facts: the firm is small, the data is theirs, and nobody is going to administer this.
- ₹1,200 / month
One VPS in Mumbai running Postgres, MinIO, Caddy and the app in Docker. No managed services, no per-seat billing, nothing that can be repriced.
- Data in India
Documents and site photographs never leave the box; they are served by short-lived signed URLs, never streamed through the app.
- Boring on purpose
No microservices, no event sourcing. A system that must still be maintainable in 2036 by whoever is there then.
- Site is mobile
Site engineers get a separate one-column interface with large tap targets and no money on the screen at all. Access is enforced server-side; hiding a nav item is not access control.
- मराठी
The paperwork is Marathi, so the field labels use the words that are actually on the forms, and the user guide is written in Marathi.
See it
A working instance, and the code
demo.owner@sahyadriinfra.example · demo1234
Every firm, person, supplier and identifier in it is invented, and it resets nightly at
03:00 IST — so enter a bill and watch the deductions compute. Nothing you do there
survives the night.
Try demo.site1@… with the same password for the site-engineer view: one
column, big tap targets, and no money on the screen at all.
The domain layer, published
The billing, cashflow, tax and compliance rules are pure functions with no database and no third-party runtime dependency at all: 43 modules, and 732 of the 861 tests above cover this layer alone — the rest test the application around it, which is private. This part is MIT-licensed and public, and runs in about a second.
Colophon
Stack
Next.js App Router and TypeScript. PostgreSQL 16 with row-level security as defence in depth,
every table firm-scoped, every server action authorising before it touches data. Money is
bigint paise throughout — never a float, and a branded type so rupees and paise
cannot be added by accident. MinIO for documents behind signed URLs, Caddy for TLS, a
token-protected cron for the nightly alert run, web push for the five alerts that cannot wait
for a digest. Backups nightly to object storage, with the restore rehearsed rather than
assumed.