← Work

Work / Agent Prototype

Ireland Open Data Analyst: a daily briefing built from open government data

Ireland Open Data Analyst picks a live dataset from data.gov.ie, profiles it in code, then has Claude write a short plain-English briefing and a second Claude call fact-check every number. Charts are chosen by code, not by the AI. It publishes a static report site on a daily schedule.

“86% of Irish bathing sites achieved excellent water quality in 2025, with Mayo accounting for 28% of all monitored beaches.” A pipeline wrote that headline from a live dataset on data.gov.ie, and every figure behind it comes from numbers computed in code. Irish public bodies publish a lot of live data and almost nobody reads it. I wanted to see how far a pipeline could get on its own: find a dataset, work out what is interesting in it, write it up for an intelligent non-statistician and publish the result without me in the loop. That is Ireland Open Data Analyst.

The archive page of Ireland Open Data Analyst listing four generated reports with source agency, date, confidence dots and dataset age
Figure 1. The archive page. Each entry carries the source agency, the report date, a confidence indicator and how old the underlying dataset is. The reports are pipeline output built from public open data.

At a glance

  • Status: prototype.
  • Built: 9 and 10 August 2026, seven commits, with a few later notes recorded in the project’s status files.
  • Platform: a Python pipeline that publishes a static website on GitHub Pages.
  • Stack: Python, pandas, httpx, Jinja2, SQLite, Observable Plot for charts, GitHub Actions, and a small Cloudflare Worker.
  • Built with: Claude Code.
  • AI inside the product: yes. Claude, through the Anthropic API, drafts the narrative, fact-checks it, and optionally adds web-searched context.

The published site

Video 1. A 47-second run through the published site: the archive, the bathing water report and its “Source and method” panel, then the pedestrian counter report.
Text version of the video
  1. The archive lists four generated reports with source agency, date, a confidence indicator and dataset age. A "Run now" button sits top right.
  2. The bathing water report opens with a finding-style headline, a "Why this matters" note with linked news coverage, and bullet observations.
  3. Scrolling shows ranked charts, an explicit caveat and a collapsed "Source and method" panel.
  4. The "Source and method" panel opens to show the dataset page, the API endpoint, the licence, the automatic selection score and the publication time.
  5. Back on the archive, the pedestrian and cycle counter report opens with its headline, context note and first observations.

Open data portals are built for people who already know what they are looking for. data.gov.ie lists thousands of datasets, and a visitor who does not know what to ask gets a catalogue, not an answer. This tool works in the opposite direction: the system chooses a dataset, finds the story in it, and says so in a sentence. An AI will happily write a fluent paragraph around a number it has made up, so the design question was not “can Claude write about data” but “how do I stop it saying anything the data does not support”.

What a report contains

Top of the bathing water report: a finding headline, a Why this matters note with news links, and observations that cite counts such as 43 of 50
Figure 2. The top of a generated report. The headline states a finding, the shaded box adds real-world context from a web search with links, and each bullet is grounded in a count from the data (43 of 50 sites, 14 of 50 in Mayo).

Each report has a headline, a short context note, three to five observations, up to three charts, an explicit caveat and a collapsible method panel that links back to the source dataset, the exact API endpoint and the licence. Field names are translated into plain English, so a column called county_name becomes “county” and the reader never sees the database.

The pedestrian and cycle counter report with its observations, a ranked chart of counter types, an outlier histogram and a caveat
Figure 3. A full report. The ranked chart and the histogram come from the data itself, and the caveat at the bottom names a data-quality problem the publisher has acknowledged.

Giving the AI only the judgement

I split the work so that the AI does only the part that needs judgement.

First, code does the analysis. A profiling step computes statistics for every column. An insight engine then ranks what is interesting: the biggest change over time, how concentrated a category is, strong correlations and outliers. The AI does not summarise raw rows. It sees a computed profile and a ranked list of insights, and its instructions say to treat every number in them as ground truth.

Build decision. Claude drafts the narrative from the profile and the ranked insights, but the first one is only a suggestion. It can choose a lower-ranked insight if that makes a better story, and it is told to say plainly when the data is dull. The editorial call is the AI’s. The numbers are not.

Second, a separate Claude call acts as fact-checker. It receives the draft, the profile and the insights, and returns a verdict for every sentence: pass, corrected with the true value, or strip. A stripped sentence is removed outright; the pipeline never publishes an open flag. If every observation is stripped, the run fails instead of publishing an empty report.

Build decision. The original design had a third AI pass that proposed which chart fields to plot. It produced charts bound to columns that did not exist. I replaced it with a small piece of ordinary code that maps each kind of insight to a chart type and binds the real dataframe columns directly. A wrong field name is now impossible, not merely unlikely, and the AI’s job shrank to deciding which insight is the story.

Where the context note comes from

The “Why this matters” box is the one place the pipeline looks outside the dataset. A third call uses Claude’s web search tool to find recent Irish coverage connected to the finding and writes a note of under 60 words with up to three links.

Build decision. The fact-check covers claims about the dataset, so the note is handled differently: the prompt forbids invented numbers, tells the model to write more generally when nothing relevant turns up, and the page shows the source links. If the search fails, the report publishes without the box.

Top of the Dublinbikes report with a context note that cites a news story about stationless bike licences
Figure 4. A context note with its source link. The note is attributed to coverage, kept visually separate from the data findings and not covered by the fact-check.

Inside the system

I wrote two specifications first, one for the system and one for presentation and storytelling rules, then built the pipeline against them with Claude Code as a Python application that publishes a static site. The project keeps three decision records. The decisions that mattered were removing the AI chart pass, moving from Netlify to GitHub Pages, and committing the SQLite database and generated reports back to the repository so the archive accumulates across runs.

LayerWhat I used
PipelinePython, pandas, httpx
Discovery and connectorsCandidate pool of live-API datasets, with connectors for DataStore, PxStat, ArcGIS and generic JSON
StorageSQLite, committed with the generated reports so the archive grows run by run
RenderingJinja2 templates and Observable Plot charts, published as a static site
Runtime AIClaude through the Anthropic API: draft, fact-check and optional web-searched context
SchedulingGitHub Actions on a daily schedule, committing the generated site
HostingGitHub Pages, plus a small Cloudflare Worker behind the “Run now” button
Architecture: discovery, connectors and profiling in code, three Claude calls for draft, fact-check and optional context, a code-only chart selector, then a static site publisher
Figure 5. How a report is made. Boxes outlined in teal are plain code, amber boxes are Claude calls. The chart selector is code on purpose.

Discovery keeps a candidate pool of datasets that expose a live API, probes each one, and scores it on whether it has a time dimension, numeric fields and categories. It then picks one by weighted random choice, excluding datasets published in the last 30 days. A failure on one candidate is logged and rerolled once, so a bad dataset cannot crash a run.

A GitHub Actions workflow runs the whole thing on a daily schedule and commits the generated site. The API key is a repository secret available to the run step only. The Cloudflare Worker holds the GitHub token as a Worker secret and allows one trigger per 15 minutes, for everyone together.

What it shows

The most useful move was shrinking the AI’s job. Every time I let the model choose something that code could choose, a bug followed. The chart field problem is the clear example, but the same logic applies to the numbers: computing them in code and asking the AI only for editorial judgement made the fact-check a tractable task instead of an open-ended one. The live site is at geraco.github.io/GovDataIRL.