PYX data product agentic engineering
← Field notes·04·Design plane·6 min

Show the numbers before you have the tables

Six weeks of platform work to find out the requirement was wrong. There’s a way to find that out on day one.

The most expensive sentence in analytics is “that’s not what I meant”. And we’ve built a delivery method that guarantees nobody can say it until the work is finished.

Look at the order we all inherited. Gather requirements. Get access. Land the raw data. Build bronze, then silver. Model it. Then, finally, put a chart in front of the person who asked, who sees the number for the first time, and the first thing they see is that it’s wrong. Not broken. Wrong. Counting the accounts they wanted excluded, at a grain that doesn’t match how they report.

We file that under communication failure. It isn’t. It’s a sequencing failure. The feedback arrived last because we made the cheapest, most informative artifact, the number itself, depend on the most expensive precondition in the programme.

Complete long before it’s connected

A requirement can be complete before a single table exists. If you know the concepts, the grain, the exclusions and the checks that have to hold, you know everything needed to say what the answer means. What you’re missing is only where the bytes live.

Which makes physical binding a state, not a precondition. It can happen after the argument is settled, whenever someone finally hands you credentials , and the design doesn’t sit idle waiting for them. That’s not a trick. It’s the difference between a model that names concepts and one that names tables, and it’s why the whole design plane runs with nothing connected to it.

If the only thing between you and a reviewable number is a warehouse you don’t have yet, invent the rows. Just invent them from the rules, not from a random generator.

Adversarial sample data, not plausible sample data

Random rows prove nothing. Everything looks fine on data with no edges, which is exactly why mock dashboards have the reputation they do. Generate the sample from the checks instead, and generate it adversarially. A contract account with exactly three consecutive estimated reads, and one with two. A move-out executed on the boundary day of the window. A flat-rate account the exclusion rule says has to disappear. One of every shape the rules claim to handle, sitting deliberately on the line.

Now the mock isn’t decoration. It’s a test of the specification, rendered as a chart the business can read. If the excluded flat-rate account turns up in the total, either the exclusion is wrong or the model doesn’t implement it, and you found that out on day one, in something that costs nothing to change, instead of in week nine, in a pipeline that costs a sprint.

The practice

Gate on semantics, not connectivity. The question that decides whether you may proceed is “is this requirement answerable?”, not “is the source registered?”
Derive the fixtures from the checks. Every check implies at least two rows: one that passes on the boundary, one that fails by one. That’s your fixture, and it’s generated, not hand-typed.
Label seeded numbers loudly, everywhere. The moment somebody screenshots invented data into a steering pack, you’ve traded a sequencing problem for a credibility problem.
Rebind, don’t rebuild. When the real tables show up, the model doesn’t change: the binding does, and the same checks now run against real rows.

The behavioural change is the point. Your reviewer stops being asked to approve a document describing a number and starts being asked to reject a number. Those are very different jobs, and people are dramatically better at the second one.

Where this is today. The design plane really does run with nothing connected: concepts, controls and generated artifacts, offline. Seeded adversarial data carrying all the way through to Visualize is the direction we’re building to, not a shipped claim.
Start a conversation

Have a data problem in mind?