Skip to content
All projects

AI-built product2026Live

Moduly Homes: an AI-built catalog that has to prove its numbers

A live comparator of prefab and modular homes in Spain, built and run with Claude Code and a set of versioned agents. Generating pages was the easy part. The work was making every agent show where each figure came from.

Built and operated with AI agents, under human review

Role
Sole builder: product, data and AI operations
Timeline
July 2026 to today
Stack
Next.js 15, React 19, TypeScript, SQLite, Node
AI
Claude Code, Claude agents, OpenAI Codex
Scope
1,137 models, 149 makers, 91 comparison pages
  • Claude Code
  • AI agents
  • Prompt engineering
  • LLM evaluation
  • Data quality
  • Web scraping
  • Next.js
  • SEO
modulyhomes.com
Moduly Homes home page: the headline "Compara casas prefabricadas y modulares con datos reales" (compare prefab and modular homes with real data) above the live catalog figures
Problem
Prefab home prices and specs in Spain sit on 149 manufacturer sites, each counting its own way. A comparator with wrong numbers is worse than none.
Approach
Claude Code as the engineering team, versioned agents for scraping and enrichment, and a verification layer: closed vocabularies, batch audits tested with planted controls, self-testing checks and my approval before production.
Outcome
Live since July 2026 with 1,137 models. Enriched records with source citations went from 5 to 277 in eight days. Google sends almost no one; ChatGPT sends twelve times more.
commits co-authored by Claude Code
355/359
7 Jul – 10 Sep 2026
models from 149 manufacturers
1,137
live site, 18 Sep 2026
guardians that must prove they can fail
48
18 Sep 2026
enriched records with source citations
5 → 277
10 → 18 Sep 2026

Git history, the live home page, the project’s check scripts and its enrichment files (831 records on 10 Sep, 844 on 18 Sep).

A comparator is only as useful as its numbers

Moduly Homes is an independent comparator of prefab and modular homes in Spain. It does not sell houses: it gathers models, prices and specs from makers’ sites, links each figure to its source and puts buyers in touch with makers.

Every maker publishes differently. A “from” price may be a kit or a turnkey house, a floor area may include the porch, and a page title can quote a different price from the cart. An agent that fills gaps with plausible values builds exactly the page nobody should trust.

So agent output is untrusted until checked, an empty field beats a guessed one, and every check must prove it can fail.

Every figure passes six gates before it is published

Agents sit inside a deterministic pipeline, not at the end of it.

  1. 01

    Sources

    203 seeds with an approval state

    • rate limit per domain
    • source URL on every price
  2. 02

    Extractors

    Structured data first

    • 70 per-domain profiles
    • approved after a dry run
  3. 03

    Enrichment agent

    Prompt v30, in batches

    • pages, PDFs, configurators
    • a source per figure
  4. 04

    Lint and audit

    Before anything is applied

    • typed answer to each warning
    • tested with planted controls
  5. 05

    Guardians

    48 self-testing checks

    • sitemap and noindex agree
    • cited URLs re-checked
  6. 06

    Gated deploy

    My approval

    • start-up and disk checks
    • one restart per hour

Counts as of 18 Sep 2026.

What the agents produce, as a buyer sees it

Model page block "Qué entra en el precio": what the price includes, what is paid separately and what the maker does not state; each unstated item shows how often the rest of the catalog charges it separately, for example 81% for foundations
Room-by-room floor areas of a two-storey model drawn as a treemap, with a note that it is not a floor plan

Left: what a price includes; when the maker is silent, the page says so and shows how often the rest of the catalog charges that item separately. Right: room areas read from the maker’s plans, drawn only when the whole house adds up. Live site, 18 Sep 2026.

The agents do the work; I decide what ships

Claude Code co-authored 355 of 359 commits. The recurring jobs split like this.

Task
Application code
What the AI did
Wrote the app, the scrapers and the checks.
What I decided
Set priorities; decide what ships.
Guardrail
Strict TypeScript and 24 build checks; a red check stops the build.
Task
A new maker’s site
What the AI did
Maps where each figure lives, with a second spot to cross-check the price.
What I decided
Approve the profile after its dry run.
Guardrail
Born “proposed”; a promoter script measures it and hunts for page elements that lie.
Task
Enriching a model
What the AI did
Reads pages, PDFs and configurator code; returns specs, a scoped price and a source per figure.
What I decided
Spot-check live pages; define what the auditor must catch.
Guardrail
Typed answer to every warning and writes to one folder only. The audit was tested with planted known-good records.
Task
Categories
What the AI did
Suggests 4 to 8 per model.
What I decided
Curate in a private console; my edits win.
Guardrail
Closed list of 24 slugs and 83 spec keys, generated from code. Free-form keys had reached 378.
Task
Buyer enquiries
What the AI did
Drafts each reply.
What I decided
Read, edit and send every email.
Guardrail
Drafts only; the server rejects a maker email carrying the buyer’s personal data.
Task
Traffic diagnosis
What the AI did
Codex audits read-only, Claude re-measures, 15 agents research in parallel.
What I decided
Decide which hypotheses stand.
Guardrail
Every claim needs a measurement and a date; TempoParking is the control group.
Task
Deploys
What the AI did
Runs the deploy script.
What I decided
Approve every production restart.
Guardrail
Stops on a failed start-up test, low disk or a restart in the last hour.

Check counts as of 18 Sep 2026.

A scraper profile must name the parts of a page that lie

data/extractors/<host>.json (excerpt)jsonc
{
  "profile_version": "v2",
  "_estado": "propuesto",        // proposed: not binding until measured and approved
  "_revisar_el": "2026-12-10",   // every claim carries a review date
  "campos": {
    "precio": {
      "principal": { "capa": "dom_visible", "selector": ".product-price[data-price]" },
      "contraste": { "capa": "data_attr", "selector": "#toWishlist[data-price]" },
      "si_discrepan": "gana_principal",   // closed list, no default
      "PROHIBIDO": [{
        "capa": "head_title",
        "por_que": "the <title> shows exactly 70% of the visible price",
        "casos": 7
      }]
    }
  },
  "_acierto": null               // filled in by the promoter script, never by the agent
}

Profile format from the extractor agent’s prompt, comments translated. A prohibition binds as soon as it is written.

Rule 0: fix a defect where it is born

The first section of the instructions Claude reads in this repository. A wrong record is a sample of a mechanism.

  1. Measure the mechanism, not the case

    3 → 17 → 131

    Reported: 3 broken URLs. Retired pages missing a redirect: 17. The whole mechanism: 131.

  2. Fix what is already live

    Published damage does not heal itself.

  3. Find the origin at file and line

    A suspicion is not an origin.

  4. Close the door

    If nothing remembers the previous state, build that memory.

  5. Add a guardian that can fail

    Its :perturbar mode breaks what it watches and must turn red. One checker once passed 88 records with a broken price function.

  6. Verify live and keep the lesson

    Check the real URL past the cache, then add a one-line lesson to the agents’ prompts.

What the agents taught me about agents

Each lesson is a one-line “pill” in the repository, quoted in the prompts and in the guardians that enforce it.

  1. 01

    A field, not a paragraph

    An agent saw a maker’s page title advertise a lower price than its cart, and said so in prose. Nothing read it, and the wrong price stayed live.

    812 of 831 enriched records kept their flags only in prose (10 Sep).

  2. 02

    Evidence from the audited page verifies nothing

    A batch of 41 price corrections used each page’s own title as proof. The title is marketing, written by the same hand as the price.

    14 of the 41 (34%) were still wrong.

  3. 03

    An objection beats an obedient mistake

    The brief is a hypothesis. One agent found a certificate the brief said did not exist; five pages had published the false claim.

    One batch: 33 objections, 4 of them real bugs in the pipeline code.

  4. 04

    Brief a swarm like a colleague

    A 15-agent research swarm went out without the control group or the list of ruled-out causes, and rediscovered dead hypotheses.

    Four agents read a Google CAPTCHA as evidence against the domain.

One model audited the diagnosis; another audited the audit

I asked OpenAI Codex for a read-only critical review of my diagnosis of the July traffic drop, written with Claude. Then Claude re-ran every measurement.

Claim in the first diagnosisCodexClaude’s re-checkStatus
No deploy on 20 Jul, so we did not cause itA deploy went out on 19 Jul, and Search Console days run on Pacific timeConfirmed: its own errorWithdrawn
Outside our brand, Google sends no oneOnly 4 of 20 clicks map to a visible queryConfirmedBrand split unknown
Only the long tail vanishedRankings fell on comparable page–query pairs18 of 25 pairs lost more than 5 positionsReal ranking loss
Google stopped rendering JavaScriptCrawl-stats samples are not exhaustiveConfirmedWithdrawn
Content quality is ruled outA province page listed a maker that says it works elsewhere20 such pages; a two-line fixFixed

Both reports dated 10 Sep 2026.

The review pushed back on Codex too

Codex made the mobile collapse (−95.3%, against −81.4% on desktop) its main lead. Split by page, it was a few brand searches of one to six a day. Neither Codex nor the first diagnosis noticed that the 21 Aug drop coincided with Google restoring Search Console data. And the crawl fell first: 9,262 Googlebot requests on 12 Jul, 34 on 20 Jul.

Offer Google less, and let the build enforce it

One module decides what each page type offers Google. The sitemap and the robots tag both read it, and the build fails if they disagree. Pages leave with noindex, follow, so they stay linked.

The sitemap went from 1,192 to 775 URLs on 9 Aug. On 1 Sep, 300 model pages that had never had an impression went to noindex, with 346 kept as a control. The quality score was audited too: “has a photo” carried 40% of its weight and correlated 0.02 with real quality.

modulyhomes.com/casas-prefabricadas
The comparison hub on modulyhomes.com: 91 comparison pages grouped by manufacturer tops, guides, material, budget, type and province, each link with a live count of models or brands
91 comparison pages grouped by criterion, each with the live count behind it. Captured 18 Sep 2026.

The honest outcome: indexed, but barely shown

Search Console holds 66 days of history: 6,919 impressions and 80 clicks from 5 Jul to 8 Sep 2026. Impressions peaked at 666 on 13 Jul, fell to 113 on 20 Jul and have not recovered, although 957 of 984 sitemap URLs were indexed on 1 Sep.

My working hypothesis, not a proven cause: Google tried the site as an intermediary on makers’ own brand searches (76% of July’s queries named a maker) and withdrew it. Bing still ranks the same pages, and TempoParking, my other 2026 site, dipped and then grew.

Buyers still arrive, just not from Google. Of 32 enquiries between 10 Jul and 8 Sep, 7 came via ChatGPT and none via Google.

sitemap URLs indexed
97%
957 of 984, 1 Sep
impressions after 20 Jul
−85%
10-day windows: 4,555 → 669
Google clicks in 30 days
20
10 Aug – 8 Sep
buyer enquiries in 2 months
32
7 via ChatGPT, 0 via Google

Search Console API and index report; the site’s own analytics, measured 10 Sep 2026.

ChatGPT sends twelve times more visitors than Google

ChatGPT sends twelve times more visitors than Google
ChatGPT365 visitsBing, Yahoo, DuckDuckGo117 visitsGoogle30 visits
ChatGPT sends twelve times more visitors than Google
CategoryVisits
ChatGPT365 visits
Bing, Yahoo, DuckDuckGo117 visits
Google30 visits

Visits by referrer, 30 days to 10 Sep 2026. Direct visits with no referrer (2,513) are not shown.

Source: Site analytics in production

Next case study

Estimating asking prices from 500+ listings collected with n8n

Get in touch

Working on something similar? I am happy to compare notes.