> ## Content Index
> Fetch the complete content index at: https://www.livain.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# I built a client performance dashboard in a day with Claude Code — the hard part was not calling enquiries revenue
- URL: https://www.livain.com/blog/client-performance-dashboard-enquiry-value-not-revenue/
- Published: 2026-09-11T07:00:00.000Z
- Updated: 2026-09-11T06:59:59.000Z
- Description: A lead-generation business that takes no payment online. Four APIs disagreed with their own documentation, but the decision that mattered was what to call the biggest number on the page.
- Author: Remco Livain
- Tags: Analytics, Marketing Tech, Dashboards, Google Ads, Claude Code

I had the dashboard finished on a Tuesday evening — built with Claude Code in a day, four Python fetchers into one data file into a single self-contained HTML app, published as a private Claude artifact with a stable URL. Five tabs, live data, the client's own typeface and greys. One empty label left, on the biggest number on the page.

The number was thirty-six point eight. As in: thirty-six francs back for every franc of advertising. I sat there for a while trying to decide what to call it, because the honest answer was that nobody had paid this company a single franc through their website.

### The business doesn't take money online

This is a natural-stone company. Slabs, paving, table tops cut to size. The website has a shop-shaped thing on it, but nothing that happens there is a sale. You configure what you want, you send it, and a human writes you a quote. Payment happens weeks later, off the internet, sometimes for a different amount than the configurator suggested.

So the number my pipeline had computed was the total value of enquiries divided by advertising spend. Every tool in the chain wanted to call the top half of that fraction *revenue*, because that is the field name in the e-commerce API it came out of. Every dashboard template I have ever built calls the resulting ratio *ROAS*.

Both would have been lies, and the second one is the dangerous kind — the kind a director repeats in a board meeting nine months later.

> Thirty-six to one is a spectacular return on advertising. It is a fairly ordinary quote-to-spend ratio in a business where roughly nine in ten quotes never become an order.

So the label says *Anfragewert* — enquiry value. And the ratio is labelled "expected ROAS, blended", with the word blended doing real work, and a note underneath saying what it is made of. Not because a compliance officer made me. Because in six months somebody is going to screenshot that card, and the screenshot has to survive being separated from me.

Google's own documentation is unusually clear about this distinction, incidentally. Its guidance on value-based bidding tells you to report ["real economic values, like revenue, or proxy values, like a lead score"](https://support.google.com/google-ads/answer/15099424?hl=en&ref=livain.com) — two different categories, named as two different categories. And its [note on estimating conversion value](https://support.google.com/google-ads/answer/2796446?hl=en&ref=livain.com) is essentially an instruction to do the multiplication honestly: what fraction of leads close, at what average size. The platform is happy to optimise against a proxy. It just needs you to know it's a proxy.

The causal view lives in a separate block on the same page — actual conversions attributed to actual campaigns, nine of them at the time, which is a small and unglamorous number sitting a few centimetres from a very glamorous one. Keeping those two apart is most of what the design is for. It's the same discipline as refusing to [collapse four sales channels into one number](https://www.livain.com/blog/four-sales-channels-into-one-number/) and hoping nobody asks.

### Four APIs, four different ways of lying

The rest of the day was less philosophical. Claude Code writes the integration faster than I can, which means the bottleneck moves entirely to whether the APIs behave as documented. I had budgeted an afternoon and spent most of it discovering that documentation and reality had drifted apart in four separate systems.

The orders endpoint I wanted returned 403\. Not a permissions problem I could fix — just closed. The analytics reporting endpoint next to it, same site, same credentials, returned everything I needed including per-enquiry line items and the referral source. If I had trusted the first refusal I'd have concluded the data wasn't reachable and built something worse.

The form plugin's submissions endpoint is documented one way and lives at another path. The obvious guess returns a clean, confident 404\. Half an hour, gone, on a wrong noun.

The ads tool has no field called `cpc`. It's `averageCpc`. Fine. Less fine: impression share arrives as a *string* when it hits the extremes — the literal characters `<10%` where a number should be. Which does not throw. It quietly poisons an average four steps downstream, and you find it by staring at a chart thinking "that can't be right." The same species of bug as [a zero that isn't a zero](https://www.livain.com/blog/which-zeros-were-real/).

And then the stupidest one: my local preview server didn't send a charset header, so the whole German interface rendered as mojibake and I spent five genuine minutes wondering whether I'd corrupted the data file. It was a missing meta tag.

None of that is interesting on its own. What's interesting is that it's the tax on every integration, always, and it is the reason "just pull the data" is never the afternoon anyone estimates. Twenty years in and I still under-budget it.

### The two changes that came from showing it to someone

I sent it over and got feedback the same day, and both notes were about honesty rather than features.

First: the current week was rendering as a collapse. Of course it was — it was Tuesday. A bar covering two days of data sits next to bars covering seven and looks like the floor fell out. Now any bucket containing today renders at thirty-eight per cent opacity, with a legend saying "lighter = current, incomplete period." A design decision that exists purely to stop the chart from telling a lie about the present.

Second: I had a series called "Landingpage" for enquiries from a particular form. But that form *is* a contact enquiry — it just happens to sit on an advertising landing page. Splitting it out made the category list describe where the form lives rather than what the customer did. It's folded into contact enquiries now, and the three genuinely different routes in stay [separate, as they should be](https://www.livain.com/blog/three-ways-in-none-of-them-a-funnel/).

Both of those are the kind of thing I would not have caught alone, because I built the categories and so the categories look obvious to me. That is the argument for [putting something in front of a person early](https://www.livain.com/blog/i-designed-an-app-in-claude-before-building-it/), while changing it still costs nothing.

### What I'd tell anyone building one of these

Decide what the top-line number is *called* before you decide how to compute it. The label is the part that escapes into other people's heads, gets quoted in meetings, and outlives the dashboard.

And if your business doesn't take payment on the website, resist every default in your tooling that wants to call an enquiry revenue. It will offer, constantly. The field is literally named that.

The dashboard took a day. The word took twenty minutes and was worth more.

### Sources & further reading

**External**  
[Google Ads Help — About Smart Bidding using value-based bidding](https://support.google.com/google-ads/answer/15099424?hl=en&ref=livain.com)  
[Google Ads Help — How to estimate conversion value](https://support.google.com/google-ads/answer/2796446?hl=en&ref=livain.com)  
[Google Ads Help — Best practices for generating high-quality leads](https://support.google.com/google-ads/answer/13489421?hl=en&ref=livain.com)

**Related posts**  
[Three ways in, none of them a funnel](https://www.livain.com/blog/three-ways-in-none-of-them-a-funnel/)  
[Four sales channels into one number](https://www.livain.com/blog/four-sales-channels-into-one-number/)  
[Which zeros were real](https://www.livain.com/blog/which-zeros-were-real/)  
[I designed an app in Claude before building it](https://www.livain.com/blog/i-designed-an-app-in-claude-before-building-it/)