Repository navigation
normalising values across sites
A list task returns forty answers and they are not the same kind of answer. One price is monthly, one is annual-billed-monthly, one says "from", one is a range, one is per seat, one says contact us. Averaging that column produces a number that means nothing.
Normalisation is the step between collection and use, and most of the difficulty is deciding where it happens.
Every field that needs interpretation gets two columns: what the page said, verbatim, and what you concluded.
It costs nothing at write time and it is the difference between a dataset you can repair and one you have to re-collect. When the interpretation turns out wrong, and on a forty-site run it will be wrong somewhere, the raw column lets you fix it with a pass over the file instead of forty page loads.
This is the same discipline as the source-and-confidence columns in filling a CRM record from a company's website and the raw-status column in checking order and delivery status. It keeps recurring because it is the one habit that makes collected data survivable.
Dates. 03/04/2026 is two different days and the string does not say which.
Take the reading from evidence on the page, the site's language, a long-form date
elsewhere, and record which evidence you used.
Money. Three questions, not one: which currency, what period, and whether tax is included. A number without all three is not comparable to another number. Symbols are ambiguous across countries, and the period is frequently in small text next to the figure rather than in it.
Quantities with units. 500 g against 0.5 kg against "half a kilo". Decide the canonical unit up front and store the original.
Anything that was a dropdown. "Medium", "M" and "Size 2" are the same selection on three sites. This is the field where a normalised value is most useful and most likely to be silently wrong.
Not in the model, per row, during the run. That is the tempting option and it is the worst of the three: the rule is applied forty times by something that can apply it slightly differently each time, and it is not written down anywhere you can inspect.
In the task, as an explicit instruction, when the rule is simple and stated once: "record the monthly price; if only an annual price is shown, record it divided by twelve and note that you did". Now the rule is visible and uniform.
In a pass over the file afterwards, for anything harder. You have the raw column, so a deterministic script can do it, you can read the rule, and it can be changed without re-collecting.
The general principle: the model reads, you interpret. A model deciding per row what "from EUR 9" means is a judgement repeated forty times with no record; a rule applied to a column is one decision you can audit.
The failure that ruins these runs is an agent that will not return empty. Asked for a monthly price on a page that says "contact us", it produces a plausible number.
The instruction is one sentence and it belongs in every list task: write the raw string and leave the normalised column empty rather than inferring a value. Then the awkward rows are visible and countable instead of hidden among the good ones. The general form of the escape hatch is in giving an AI browser agent a stopping condition.
One number, and it is the honesty check on the whole dataset: how many rows have a raw value and no normalised one.
Two out of forty is a usable column with a caveat. Fifteen out of forty means the thing you are comparing is not comparable across these sites, and the right response is to change the question rather than to fill the gaps. That is a finding, and it is one you only get if the empty cells were allowed to stay empty.
How do I make data from different sites comparable? Two columns per field: the page's exact string, and your normalised value. Normalise in a pass over the file, not per row inside the run.
Why not let the model normalise as it reads? Because it applies the rule slightly differently each time and the rule is not written down. A rule applied to a column is auditable and uniform.
What do I do about "contact us" and "from EUR 9"? Record the raw string and leave the normalised cell empty. Never let the agent infer a number into it.
Which fields go wrong most? Dates, money, units, and anything that was a dropdown. All four are ambiguous in the source, not in the reading.
How do I know the column is usable? Count the rows with a raw value and no normalised one. A large count means the comparison itself does not hold.
See also: deduplicating what an AI agent collects, the step before this one, and validating an AI agent's output, the step after.
- No external figures are cited. The four ambiguous field types and the two-column rule are practice from running list tasks with this browser, and the page says so rather than dressing them in a statistic.
The model reads, you interpret. If you remember one line from this page, that is the one, and the two-column rule is just what it looks like in a file.
- OpenAI Operator alternatives
- Open-source Operator-style agents
- Is OpenAI Operator still available?
- OpenAI Operator vs Claude computer use
- browser-use alternatives
- Choosing an AI browser agent
- Open-source AI browser agents
- Open-source computer-use agents
- What is an AI web agent?
- AI browser agents vs traditional scraping
- Cloud browser infrastructure for AI agents, explained
- Browserbase alternatives
- Firecrawl vs an AI browser agent
- Skyvern alternatives
- Stagehand vs browser-use
- Project Mariner is gone: what replaced it
- Manus alternatives
- Gemini computer use vs Claude computer use
- A stealth browser MCP, reviewed honestly by its own wiki
- AI browser vs AI browser agent: which one do you want?
- AI browser agent vs RPA: which one fits the job
- AI browser agent vs n8n, Zapier and Make
- Vercel agent-browser alternatives, compared honestly
- What is an agentic browser? Definition and the two kinds
- Open-source agentic browsers: the three layers, compared
- Choosing an MCP server for browser automation: four axes
- Stealth MCP servers compared: Camoufox, nodriver, Patchright
- Playwright MCP alternatives, and the three you don't need
- Autonomous browser agents: the four rungs of autonomy
- What is actually free in the AI browser agent stack
- browser-use on GitHub: what the repo actually gives you
- Playwright MCP vs Chrome DevTools MCP: different jobs
- How to choose among MCP servers: a map by category
- Which MCP servers are worth adding to Claude Code
- MCP on GitHub: finding servers and judging them fast
- MCP vs an API: the decision, and what the wrapper costs
- MCP alternatives: when the protocol is the wrong shape
- Why does my AI agent get blocked?
- The timing signal AI agents give off
- Agent retry loops trip rate limits, not fingerprints
- Claude computer use detected as a bot
- browser-use getting blocked: what you can and cannot change
- Playwright MCP session blocked: four causes, four fixes
- Playwright MCP and captchas: what actually gets you past
- Cloudflare and a browser MCP server: what is being read
- Can an AI agent solve a captcha? The honest answer
- Getting an AI agent to fill out forms
- The best model for an MCP browser agent, and what it really costs
- Browser problem or model problem?
- How to use a Playwright MCP server with Claude Code
- Extracting data to a CSV with an AI agent
- Monitoring a page for changes with an AI agent
- How to let Claude Desktop control a browser
- How to add a browser to Cursor as an MCP server
- Using an AI agent to hunt for apartments
- Getting website data into Google Sheets with an AI agent
- Using an AI agent to download invoices from portals
- AI agents for web research
- Using an AI agent to test your own website
- How to add a browser to Cline as an MCP server
- Posting to social media with an AI agent
- Posting to Facebook with an AI agent
- Posting to Instagram with an AI agent
- Posting to X with an AI agent
- Automating LinkedIn posts: read this first
- Appointment bots: what they are and what an agent can legitimately do
- Track prices across sites with an AI agent
- Build a lead list with an AI browser agent
- Run an AI browser agent on a schedule
- AI browser agent with a local LLM: what changes
- Should you log your AI agent into your accounts?
- How to write a task an AI browser agent can follow
- Move data between two web apps with an AI agent
- The MCP server
- How the tools are shaped, and why
- Playwright MCP vs the Playwright CLI: which fits when
- Playwright MCP: browser is already in use, and the fix
- Playwright MCP best practices: four decisions that matter
- Playwright MCP with a proxy, and the three leaks it leaves
- A browser MCP server in GitHub Copilot: setup and limits
- Using a browser MCP server for web scraping: the pattern
- Which LLM for browser automation: the four properties
- How to build a browser agent, and what to take instead
- Getting an AI agent to log into a website: three routes
- MCP tools, resources and prompts: who controls each
- How many MCP tools is too many? The context arithmetic
- How to build an MCP server: the decisions, not the scaffold
- Local or remote MCP server: what changes, and what does not
- Writing an MCP client in Python: the thirty-line version
- Self-hosted AI agent: what one actually costs to run
- How long an AI browser agent takes per step, measured
- Text, HTML, snapshot or screenshot: what the agent should read
- Giving an AI browser agent a stopping condition
- Keeping an AI browser agent out of destructive actions
- Why did the AI agent click the wrong thing
- When the page changes under the AI agent
- Running one AI agent task across a list of sites
- Seeing a page as it appears in another country
- Getting data out of a dashboard with no export button
- Two browsers in one session: main and support
- Finding the dead links on a site with an AI agent
- Filling a CRM record from a company's website
- One form submission per spreadsheet row, with an AI agent
- Dated screenshots of a page as evidence
- Checking order and delivery status with an AI agent
- Reading a PDF that opens inside the browser
- Summarising a long page or thread with an AI agent
- Collecting every image on a page with its caption
- Collecting event and course listings with an AI agent
- Cancelling a subscription with an AI agent
- What an AI agent can and cannot do inside an iframe
- Shadow DOM and an AI agent: you can click it, you cannot read it
- How to add a browser to Codex as an MCP server
- What a page snapshot costs, per control
- How to let Gemini CLI use a browser
- Native selects and the ones that only look like selects
- Clicking by selector or by coordinates
- How long the agent waits before it gives up
- What a second browser costs
- Uploading a file with an AI agent, and why this one cannot
- Watching the agent work, and when it is worth it
- When not to use an AI browser agent
- Agent or script: deciding once instead of every time
- Using the keyboard instead of the mouse
- Secrets in an agent task: where they end up
- What an agent run should log
- Deduplicating what an AI agent collects
- Normalising values across sites
- Validating an AI agent's output
- Reading a table with an AI agent
- Driving a site's own search and filters
- The task works headed and fails headless
- How to let the AdaL CLI use a browser