Skip to main content

CASE STUDIES / OWN PRODUCT / DATA PIPELINE WITH AN AGENT LAYER

A scoring pipeline that tests websites against AI models

18 deterministic checks Measured and 4 live AI engines Measured feed a single readiness score and a written report, with 139 test files Measured guarding every change.

The problem

The question behind the product: can AI assistants read a given website, and do they recommend it when a buyer asks? Answering that by hand means pasting the same questions into several chat apps, copying the answers into a spreadsheet and guessing at a score.

The pipeline does it end to end: it crawls the site, runs the checks, asks each engine the same questions, scores what came back and writes the report a business owner reads.

Constraints

  • Every score comes from a test that actually ran.
  • An engine that errors out is left out of the score, never counted as a miss.
  • A result set with gaps gets no score at all instead of a partial score.
  • Spend is capped per client and per engine, because every scan calls paid model APIs.
  • The report has to make sense to a business owner, not only to an engineer.

Who did what

As the product owner

  • Set the question the product answers.
  • Set the acceptance bar: no score without a test that ran.

I built

  • The crawler and the deterministic checks
  • The query layer across the AI engines, with spend caps and caching
  • Scoring, the tier rules and the PDF report
  • The job queue, retries, rate limits and error monitoring
  • The test suite and CI

My own product, so I was the client. No partner was involved in this build.

Step through a run

Sample run on a made-up bakery website

Crawl and check

Plain web requests, no AI calls. Redirects and response size are capped before anything is parsed.

  • Crawler rules OK:allow AI crawlers
  • Sitemap OK:found and valid
  • Structured data Fails:business details missing
  • Readable without scripts OK:yes

Ask the engines

The same questions go to every engine as background jobs. A question asked recently comes back from cache at no cost.

  • Perplexity OK:recommends the bakery
  • Claude Fails:recommends a competitor
  • OpenAI OK:recommends the bakery
  • Gemini Note:engine error, left out

Score

Only engines that answered count toward the score. A result set with gaps gets no score instead of a partial score.

  • Checks OK:most pass
  • Engines counted Note:the error is excluded
  • Tier Note:partly ready

Report

A PDF a business owner can read: what failed, why it matters and the fix, in priority order. Each result record stores the hash of the record before it, so an edited result shows.

  • Fix first Fails:add business details to the structured data
  • Then Note:ask the engines again after the fix ships

Timeline

Engine adapters to the first full run
2 days Measured
First commit to a working self-serve purchase
7 weeks Measured
Running in production
6 months and counting Measured

Scope receipt

Build record

Milestone tier
Multi-step agent or workflow
Stack
TypeScript, Next.js and React, Postgres with Drizzle, Convex for the job queue, Upstash Redis for rate limits, Vercel scheduled jobs, Sentry, react-pdf, a Python worker
Evidence basis
Counted from the source and git history of the production repository.

Done-checklist

  • Every check runs without an AI call.
  • Every engine call sits under a spend cap that callers cannot raise.
  • An engine error is excluded, never scored as a miss.
  • A result set with gaps gets no score.
  • Repeated questions are served from cache.
  • A test mode returns fixed answers at no cost, so CI never pays for a model call.
  • Result records are hash-chained.
  • Lint, a type check, unit tests, a build and browser tests run on every change.

What I would do differently

A placeholder adapter for a fifth engine shipped inside the live set. It returned made-up answers, and they were counted as real until I caught it. Placeholders are now excluded from scoring by name. I would keep them out of the live set from the start.

A parsing bug stopped reading an error code at its first underscore, so real errors were scored as misses and some scores came out at half their true value. The fix was a test for each error format. I would write those tests before the parser, not after the bad numbers.

Have a client who needs this?

I build work like this for agencies and consultants, under your brand, at a fixed partner price per milestone.

Book a partner call