Spreadsheets Are Where the Real Requirements Live
Writing imports for twelve kinds of records showed me the business as it actually runs, which is never quite the business in the spec.
- Published
- Reading time
- 4 min read
- Length
- 656 words
- Topics
- SaaS, Data, Product
RentAxis launched this month. Among the parts I built is something no demo will ever show: imports for twelve kinds of records, so a property company can bring its existing data in from spreadsheets instead of retyping years of history.
Imports sound like plumbing. They turn out to be one of the best ways to learn how a business actually works, because a spreadsheet is a business described by the people who run it, with nobody in between translating it into a spec.
The Spreadsheet Is the Real Spec
A schema is a theory of a business. A spreadsheet is the business. Open the sheets companies actually keep, and the theory starts to meet resistance:
- A unit called “3B” in one sheet and “Apt 3-B” in another.
- Dates in three formats, sometimes within one column.
- Amounts written as “$1,200.00”, “1200” and “(150)”, the last one meaning negative, the way accountants write it.
- A status column that says “paid”, “Paid”, “pd” and “see note”.
- A row in the middle that isn’t data at all, but a heading someone typed to separate one building from the next.
None of this is carelessness. Each quirk is a decision someone made to get their work done, often a good one. The parenthesized negative is an accounting convention older than computers. The note in the status column is a real exception the business handles every month. A spreadsheet records the requirements nobody wrote down, because the people who live with them stopped noticing they were requirements.
Parse Honestly or Not at All
The first temptation is to be clever: strip anything that isn’t a digit and hope. The better instinct is to understand the conventions you can, and refuse the rest out loud. A parser for amounts, working in integer cents:
// "$1,200.00" -> 120000, "(150)" -> -15000, "see note" -> null
function parseAmount(raw: string): number | null {
const text = raw.trim();
const negative = /^\(.*\)$/.test(text) || text.startsWith("-");
const digits = text.replace(/[()$,\s-]/g, "");
if (!/^\d+(\.\d{1,2})?$/.test(digits)) return null;
const [whole, fraction = ""] = digits.split(".");
const cents = Number(whole) * 100 + Number(fraction.padEnd(2, "0"));
return negative ? -cents : cents;
}The null is the important part. When the parser meets something it doesn’t understand, it doesn’t guess. It hands the value back to a person, with the row and column attached.
What a Trustworthy Import Does
Because an import writes many records at once, its failures are quiet and wide. A bad form submission breaks one record. A bad import can seed a thousand wrong rows into a product people will run their books on. A few rules make the difference:
- Validate everything before writing anything. Read the whole file, collect every problem with its row and column, and show them together. Fixing a sheet one error at a time is how people learn to hate software.
- Write all or nothing. A file that half-imports leaves people unsure what exists, which is worse than an error. One database transaction per file keeps the answer simple.
- Import in dependency order. Properties before units, units before leases, leases before payments. Twelve record types form a graph, and the order is part of the design.
- Make it safe to run twice. People re-upload the same file after fixing one row. Stable keys turn that into an update instead of a duplicate.
- Keep the original value next to the parsed one. When someone asks why a balance looks wrong, “your sheet said (150) and we read it as −150” is an answer. A silent conversion isn’t.
The Errors Are the Interview
The most useful output of an import isn’t the rows it creates. It’s the rows it rejects. Each rejection is a question the product hasn’t answered yet. What is a unit called when it has two names? What does “see note” mean for a payment? Some answers become validation messages. Some become features.
That’s why I think of import code as a requirements interview, conducted with the customer’s own data. The spec tells you how the business is supposed to work. The spreadsheet, read carefully enough, tells you how it does.