DEV Community

Auka Wave
Auka Wave

Posted on

From spreadsheet to template: the row that keeps a price table honest

A price table has to survive being dated, sourced, unit-checked and half-empty. Here is the row we ended up with, the one rule that carried the most weight, and three checks you can add to your own data product tomorrow.

AI-assisted writing; the figures come from our own product files.

The problem: a table with three ways to be wrong

Every price table starts as a spreadsheet: one row per thing, one column per side, one number typed in from wherever you last read it. That spreadsheet is fine on day one and quietly broken by month three.

A unit that does not match the others. In our table, the museum and park entrance line is CNY 60 on one side and USD 35 on the other — but the Chinese side is a ticket per person and the Western side is a pass per vehicle that admits everyone in the car. Their ratio is nonsense. There is also a rental deposit row where the quantity is not money: deposits are contract terms, so the figure is a number of months of rent. A column headed "price" that holds "months" is a unit error waiting to be read as a number.

A figure that moved and nobody noticed. Prices change; tables do not. Unless the row carries the date its source was read, a number from two years ago is indistinguishable from one from last Tuesday. Every row in our file carries a "last checked" date, and all 62 of ours carry the same one — 2026-11-05, the day the sources were read.

A cell filled in from memory. This is the one that costs a table its credibility. Eight of our 62 lines have no published tariff on either side: a personal training session, a common over-the-counter medicine, a rental deposit, a domestic flight, a dry-cleaned jacket, a night of pet boarding, an average online order value. None is impossible to price; all are impossible to price from a source you could cite. The trap is the blank cell — it feels like an embarrassment, so somebody fills it in from memory, and a remembered number is then identical to a sourced one. Nothing in the table can tell them apart.

None of those three is a research problem. They are schema problems, and you can design them away. Our table is 62 lines across 13 categories, drawn from 57 publishers and read on a single day; of those, 13 are filled on both sides, 41 on one side only, 8 on neither side.

The row

A spreadsheet lets a cell hold anything. A database does not, once you give it the right properties. Here is the row we settled on:

# one row = one figure
name:         "Household electricity, tier 1"      # the line this figure belongs to
measure:      "residential electricity, first tier"  # what the number measures
value:        0.4883
unit:         "per kWh"                             # the basis the value is counted in
currency:     "CNY"
source_url:   "https://..."                          # the page the number was read from
source_date:  "2026-11-05"                          # the date that page was read
status:       "verified"                            # verified | verified (one side) | verified (derived) | verified (indexed) | pending verification
note:         ""                                    # why the number reads the way it does
next_step:    ""                                    # what to open, and what to write down
Enter fullscreen mode Exit fullscreen mode

Four of those fields do the real work.

unit is the one people forget. "Per kWh", "per ride", "per pound", "per dozen", "per lease" — as long as the basis is a separate property instead of a word buried in the item name, two rows that measure different things cannot be silently averaged, and a derived column knows when it must refuse to compute.

currency is separate from value for the same reason: some rows hold a quantity that is not money. A rental deposit is measured in months of rent, so that row has to be allowed to leave the currency empty rather than guess one.

source_date is not updated_at. It is the day the source was read, not the day somebody edited the row. Editing a note does not refresh a price, and a schema that treats the two as one field will quietly do that for you.

status is the field almost every spreadsheet is missing, and it carries the most weight here. It is not a boolean: it says whether a figure is verified, verified on one side only, verified as an indexed or derived figure, or still pending — and "pending" is a legal value, not a failure state.

The same row, when the figure does not exist in a comparable form:

name:         "Rental deposit"
measure:      "deposit required, in months of rent"
value:        null
unit:         "per lease"
currency:     null
source_url:   null
source_date:  null
status:       "pending verification"
note:         "Deposits are contract terms, not published tariffs."
next_step:    "Multiply one month of rent by the reference city rent, or read the deposit months a named standard residential lease requires."
Enter fullscreen mode Exit fullscreen mode

That is not a hole in the row. It is a row that knows what it is missing and what to do about it.

The shipped template puts both sides in one row, so a comparison lives in a single place: China Price CNY, China Price USD, West Price USD, a Basis, and a source URL each. The currency therefore lives in the column name rather than in a field of its own, and everything else maps across: Item for the name, Basis for the unit, Last Checked for the source date, Source China and Source West for the URLs, Status, Note, Next Step. The one field with no place above is the derived one — "how many times dearer" the Western price is — and it is deliberately left empty in two of the 13 rows with both sides filled.

The one rule: a cell you cannot source is information

Everything above follows from one rule: a cell you cannot source is information, not a gap.

In the product, the price cells stay empty, the status reads Pending verification, the source type is market-reference so pending rows filter out of the settled ones in one click, and Next Step carries a written procedure, not a disclaimer. For the personal training row it names the route — quote a named chain's published single-session price and record the date you read it — and for the medicine row it names the national drug price registry on one side and the absence of any published retail price on the other.

Why that is a feature for the buyer and not a defect: the queue is visible, and the buyer can shrink it. Filter to the pending rows, follow the next step, fill the figure in, paste the source, set the date, move the status on. Every line they fill is a line they can defend.

Our own product notes put it more plainly: filling those cells with plausible-looking numbers teaches you to trust numbers nobody checked — this one teaches you to go and check.

The honest limit: we cannot source anything for you, and we cannot decide whether two figures are comparable. We can make both decisions visible.

Three checks you can add tomorrow

If you build a small data product — a price table, a benchmark sheet, a rate card — three checks will do most of the work, and none of them needs code.

1. A field that must be non-empty. If a row holds a value, it must hold a source URL and a source date. Not "the table should have sources somewhere" — a per-row rule, so one violation is visible and nameable. A price cell with an empty source cell beside it is the only thing we treat as a defect: that is the cell a reader takes as evidence.

2. A currency and a unit that must agree. The currency must match the publisher: a CNY figure from a Chinese publisher, a USD figure from a Western one. The unit must be the unit the source quotes — per kWh, per ride, per pound, per dozen — and a row may only carry a currency if its quantity is money. In our table 11 of the 13 rows with both sides filled carry a "how many times dearer" figure, and two deliberately do not: the museum and park line, per person against per vehicle, and the outpatient visit line, whose two benchmarks sit on deliberately different stated bases. Both are true numbers, and the ratio would be a category error, so the cell stays empty.

3. A source date within N days of the last verified date. Pick N, write it into the schema, and treat anything older as pending rather than as current. Our file reads every source on one day, 2026-11-05, so the table has one expiry date, and a buyer who re-checks on their own schedule gets a table with a visible shelf life instead of one that rots quietly.

None of these are implementation — filters in Notion, conditional formatting in a spreadsheet. The point is not the tool but that the rule is written down somewhere the data has to pass.

What we sell, and how it fits

The table is the product, and it is deliberately not a frozen snapshot.

China Cost Guide — $14, price listed on aukawave8.gumroad.com, Nov 2026.
https://aukawave8.gumroad.com/l/china-cost-guide
62 cost lines across 13 categories, 57 sources in a separate index, every line carrying its unit, its source and the date it was read.

Every Notion template we publish is built the same way: a written spec of every page, database and property, a paste-ready import block, and one CSV per database. Paste the block into a new Notion page, import the CSVs, and the structure is there in about three minutes — no paid plan, nothing hosted on our account.

Notion Budget Tracker Template — $16.
https://aukawave8.gumroad.com/l/budget-tracker
Planned spend and actual spend in separate columns, categories with limits attached — the same separation of fields, applied to a month.

Notion Research Wiki Template — $18.
https://aukawave8.gumroad.com/l/research-wiki
Sources, atomic notes and claims with citations attached, so a claim keeps its source — the same rule, applied to writing.

What we do not claim: the template cannot find a number for you, and it cannot decide whether two figures are comparable — only make those decisions visible.

If you take one thing away

A table is a schema with a reading order. If the schema has no way to say "not comparable" and "not checked yet", the table will fill both with numbers and you will never tell which is which. Give the row a unit, a currency, a source, a date and a status. Let a cell stay empty, and refuse to compute across two rows that are not measuring the same thing.

The empty cell is not the failure. The filled one with nothing behind it is.


This piece was written with AI assistance.

Top comments (0)