I vibe coded a complex data visualisation and analysis dashboard. Here’s what I learned
A data visualisation and analysis dashboard for the Philippines' 2025 National Demographic and Health Suvey
The Philippines' 2025 National Demographic and Health Survey (NDHS) might be the most important health report in the country, with data on fertility, maternal care, contraception, childhood mortality, vaccinations, intimate partner violence, and more. Almost none of it has been reported. Can one journalist, with a week of vibe coding, build a tool that could possibly change that?
I spent one week building a data visualisation and analysis dashboard for the NDHS, a report that comes out every three years tracking key indicators in health, fertility, demography, and cultural attitudes. The survey, which was released by the Philippine Statistics Authority (PSA) at the end of March, contains a treasure trove of data not just for journalists but for policymakers and citizens as well.
But more than a month since its release, most news items about the report have only covered one data point: the Philippines’ total fertility rate, which has dropped to 1.7 making it below replacement level. While this was an important national story, the survey contained so many other valuable and interesting data that could be mined further for insights that would be useful for journalists, policymakers, and civil society advocates across different beats and fields.
With this project, I am hoping to make the data more accessible to audiences through maps, charts, and AI-generated insights so that they can explore it more deeply. Here’s how I created this dashboard by “vibe coding”: describing what I wanted to an LLM, and having the AI tool write the code for me.
I didn’t set out to build the NDHS dashboard when I started this journey. When I began, I only wanted to try to use a coding agent to build cool maps.
Filipino developer James Faeldon maintains a Github repository of maps of Philippine administrative boundaries, which allows anyone to build maps that a user could drill down from the regional to the provincial to the city/municipality, all the way to the lowest level of government in the country. I wanted to use his work for my project, but I did not have the programming chops to put it together until coding agents became available.
Working in Claude Code to plan the project, I simply gave it the address of the repository and described the behaviour I wanted: to load the whole Philippine map divided into regions. Clicking on a region would show the provinces within, and clicking on the provinces would show the cities and municipalities. I also wanted a data panel that would show up at each level.
We set off to a good start, with Claude Code reading the files from the Github repository and building a website similar to what I had in mind in about an hour.
Still, I ended up spending a whole day trying to get things just right. The issues I encountered seemed minor, but required a working knowledge of how local governments in the Philippines were structured. For example, there are cities that are classified as Highly Urbanised Cities (HUCs), which are geographically part of provinces but are independent from provincial governments. For example, Iloilo City is not part of Iloilo province politically, even though it is part of the province geographically. In practical terms, this meant that the Iloilo provincial map was rendered without Iloilo City, which may make sense for politicians, but does not make sense for users.
Even after giving Claude Code a list of Highly Urbanised Cities, it struggled to render the maps correctly. Over and over, it just drew the cities out in the sea, away from the rest of its province. Other times, it rendered HUCs on the map like mismatched pieces of a jigsaw puzzle.
The process of getting things right meant a lot of back and forth with Claude Code as it tried to debug the errors I spotted. Several times, I hit the limit of usage on my Claude account, which meant I had to take a break and pick things up after the limit reset.
This experience of working with a coding agent reminded me of that old programmer adage, “The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.” This would prove true time and again throughout this project.
But I finally got the maps to look acceptable, if not exactly perfect. Realising I and others could use this as a building block for other data visualisation projects, I asked Claude to upload the project to Github. Here’s a working demo.
1. Data cleaning and extraction with coding agents can feel like magic
After publishing my drill-down map template on Github, I wanted to test it out with various datasets. I started a new project in Claude Code to create a website that would show different choropleth maps – colour-coded maps to visualise data variation across different regions – to represent different datasets in the Philippines.
I looked through the PSA website to see what datasets were available that had geographical granularity. In the Philippines, getting data from the government means trawling through slow-loading websites to download data in Excel spreadsheets. Using the data for journalism and visualisation projects meant a lot of cleaning and conversion.
I began with a dataset containing poverty incidence in the Philippines. In terms of the format, it wasn’t the cleanest dataset, although it wasn’t the most complex either.
I instructed Claude Code to extract the data and plug it in to our template map. Since it only contained data down to the provincial level, I asked the coding agent to disable the ability to drill down lower.
It worked like magic — no cumbersome data cleaning or conversion required. It was even able to figure out that the dots in the spreadsheet meant hierarchy, and imported the data accordingly. A spot check of the imported data in the maps panel also showed it extracted everything accurately. I only needed Claude Code to tweak some presentation items.
I tried it out with other datasets I could find — annual family household income, GINI coefficient (which measures inequality in a given region), gross domestic product by region — and it handled everything just as well.
2. Coding agents make it easy to pivot and pursue new ideas
That’s when I stumbled onto the NDHS dataset. Unlike the other spreadsheets, this was more complex, with multiple sheets, as well as other indicators beyond geography. I asked Claude Code to extract geographical data and build a map from one sheet — total fertility rate — and it did it perfectly.
Building maps from other sheets wasn’t quite as simple. For example, the teenage pregnancy table had five different dimensions. I asked Claude Code to build a tab system for the user to navigate through these dimensions, and it got it in one try. After building five other maps from the dataset, I realised I could pivot and turn this into its own dashboard just focusing on the NDHS survey.
I started another new project on Claude Code for the NDHS dataset. Looking at the data in the report, I realised that the project could be much richer if it also represented other indicators beyond maps.
I asked it to create another tab system, this time so users can navigate to a charts page containing the other, non-geographic data. I then asked it to extract the non-geographic data from each sheet and render them into charts.
It was easy to pivot regarding design choices, too. I realised that I wanted the charts to be lollipop instead of bar charts because I thought it looked cleaner. It took just a single instruction for the coding agent to change everything.
3. You constantly have to make editorial and design choices
Of course, my training as a journalist required checking if it had imported the data accurately each time I generated a map or a chart. But the nature of the dataset also requires decisions to be made that affect both the editorial and design aspects of the project.
For example, the data on contraception preferences among women includes 16 different dimensions. If I just lifted that into the dashboard, it would have made for a terrible user experience. So I decided to split up that particular table into three different sections: one page for contraceptive use (showing the breakdown between those who use any method, any modern method, any traditional method, and those not using), one for modern contraception (breaking down different methods used), and another for traditional contraception.

I also needed to figure out an elegant way to visualise this data in a chart. I decided to experiment with using a waffle chart, which worked very well in showing the distribution of data across all these dimensions, but that required some iteration to get the coding agent to figure out exactly what I wanted.
I also had to make similar editorial decisions for vaccination data, all of which were grouped in one sheet in the original dataset but ended up in five different pages on the dashboard.
These design and editorial decisions affect the coding agent in other ways too. For example, when I lifted an entire sheet wholesale, it just took the description from that sheet directly. But when I split that sheet into different pages, the coding agent came up with its best guess for the description of the data on that page. Aside from needing to check its accuracy, I also had to check for style and length so that the different page descriptions were uniform.
Most of the other work involved making sure the small things worked correctly. For the most part, the coding agent imported the data accurately, but it sometimes missed out on things like table notes, which I had to catch. I also had to check if the charts rendered well on mobile. I had to ask to put a nationwide average in the data panels on each map, and after that was done, to put it above the ranked list of regions.
As a former editor-in-chief who had to lead major data visualisation projects in the past, the experience was not unlike working with graphics artists and web developers, giving them notes to get things just right. Except this time, since I was the lone human working on it, there was no danger of beating my colleagues into exhaustion with endless tweaks and revisions.
4. Vibes can take you far, but eventually you need to think about architecture
While it’s true that AI coding agents never get tired, they still need breaks in the form of usage limits. Because of my endless need for revisions, I would burn through my usage quickly. Since I had subscriptions to both Claude and ChatGPT, I wondered if one coding agent could pick things up where the other left off.
I simply copied the latest CLAUDE.md (which contains instructions for Claude Code projects) into AGENTS.md (its counterpart for OpenAI’s Codex) and the coding agents were able to figure it out, and I could continue vibe coding.
Of course, it’s all fun and games until you look under the hood and discover what a mess the coding agents have been making. In my case, I was trying to debug an element when I discovered the reason updates weren’t reflecting on the front end: there were two map renderers in one of the pages. While the agent was updating one renderer, the server was delivering the other.
That was the impetus for me to start thinking about the architecture of the project more seriously. I wasn’t starting from scratch since the agents had already done plenty of work. Understanding what they had done, I standardised everything into a simple architecture:
Presentation layer. I asked Codex, ChatGPT developer OpenAI’s coding tool, to create standard blank templates for maps, charts, and data tables, which I should have had from the beginning since they allow me to quickly check the accuracy of the data extraction, and give users a more transparent way to check the provenance of the data. Later, I added another section for AI-generated insights. Whenever a new data page is created, the coding agent simply copies a template and customises it for the available data. After I did this, I asked the coding agent to standardise all the pages according to the templates.
Extraction layer. For every page on the dashboard, the coding agent writes a script to extract the data from the original spreadsheet and save it into a JSON file, a text file format for storing structured data. This means that if an error is found in the extraction of the data, the coding agent would just update the extraction script to correct the error, run it again, and update the data on the dashboard. When I implemented this architecture, most but not all of the existing pages already had this.
Data layer. Each page has a JSON file that contains the data, acting as the handoff between extraction and presentation. It stores the structured content for that page (map values, chart blocks, table blocks, notes, and insights), so the code for the user interface can be copied across different pages even as the in the page changes. Before I implemented this, a handful of pages still wrote data directly in HTML files.
Having this structure makes coding agents more efficient in a few ways. Because they mostly update existing scripts and files, they do not need to generate large chunks of code from scratch, which reduces token usage. Standard templates also make updates easier to implement, since the same patterns are reused across pages. The separate data layer helps with debugging by making it easier to tell whether an issue is in the rendering code or in the extracted data. Despite these, there are still plenty of inconsistencies across pages and it would probably need to be refactored to be more maintainable and scalable.
I found that using an older model — in this case GPT-5.3-codex medium on Codex — was less expensive in terms of usage, while still being good enough to take the project to the finish line. I ended up using Codex for most of the rest of the project, only using Claude Code for some debugging.
5. AI-generated insights can add a lot of value to a data project
I wanted to experiment with using AI to generate insights from the data. I had the coding agent generate a prompt and save it into a prompt library. I then reviewed the prompt before having the agent run it. The output was stored before being inserted into the data page.
I wanted to be able to review and edit the prompt manually before running and check the output individually, to see if the prompt was working. Once, when generating insights on teen pregnancy, the AI declared as a big discovery that 19-year-olds were much more likely to get pregnant than 15-year-olds. It was accurate, of course, but hardly a groundbreaking insight. The prompt needed further revision.
Generating the prompts for review first also prevented overfitting, where the agent’s instruction already contains its analysis of the data, which prevents it from finding more insightful patterns. It also allowed me to use well-performing prompts as templates, again allowing me to save on token usage.
The insights themselves were quite valuable, in terms of presenting information that would be hard to glean from maps, charts, and data tables.
5. Fact-checking workflows can help the auditing process, but strong guardrails are necessary
The first question in journalists’ minds whenever they’re presented with anything AI-generated is, “Is it accurate?”
To address this, I built a fact-checking workflow inside the coding agent to check the insights against the data. At first, the fact-checking agent pulled out individual claims, verified them, and suggested rewrites. But checking claims in isolation often stripped away context already present in the same insight block, so the rewrites became repetitive.
I then shifted to block-level fact-checking, where each insight block is checked as a whole against the data. Each block is scored as supported, mostly supported, partly supported, or unsupported. Rewrites are only required for blocks that are not fully supported; supported blocks are left unchanged.
In practice, that still wasn’t enough. When runs got long, agents tended to take shortcuts: they reused familiar evidence patterns, cited nearby but wrong values, or pointed to fields that looked right but did not exactly match the source cell. The output could sound convincing while still being wrong.
So I added stronger guardrails. Every evidence line had to point to an exact data pointer and exact value, and validator scripts checked both structural validity and evidence quality before a result was accepted. Failed outputs were rejected and rerun fresh. This turned the process into a stricter audit pipeline rather than a one-pass AI check.
For each insight page, the fact-checker agent generated a scorecard report, showing not just its ratings, but also the data points on which it based its judgment. It also gave an explanation for items with "mostly supported," "partly supported," and "unsupported" verdicts. In the end, the fact-checking workflow checked 273 blocks and found 258 were supported, 12 were mostly supported, two were partly supported, and one was unsupported.
While it still wasn't a polished tool, this made the manual checking process easier. If I had to do it all over again, I would have run the fact-checker as soon as the insights were generated, so that it would have been easier to check at a per-page level, rather than one big batch of blocks.
6. Early testers saw real-world applications right away
Stakeholders who tested the dashboard immediately saw its potential value to their work.
Romelei Camiling-Alfonso, a physician and health innovation expert, said the dashboard will be useful for provincial health officers, municipal health officers, and members of the “doctors to the barrios” program, who are physicians deployed across the country to deliver primary healthcare services to underserved and remote communities.
Athena Presto, a sociologist who studies gender and policy at the Australian National University, said the project makes the survey data instantly understandable without looking at the numbers.
“I think having a low-sensory website is also great for those who just quickly look in and need information right away,” she said.
Tricia Aquino, the co-founder and content chief of Manila-based podcast outfit PumaPodcast who has reported on the NDHS in the past, was excited by the possibilities of collaboration among newsrooms based on the data and insights from the dashboard. “This project makes it so much easier to see the data, interact with it, and analyze it. It's easier to find patterns and connections, too,” she said.
7. The possibilities are just beginning
A possible next step is layering a chatbot on top of the dashboard. Rather than navigating through maps and charts to find an answer, a user could simply ask, “Which region has the highest rate of unmet need for family planning?” and get a response grounded directly in the data. The infrastructure for this already exists in the way the data is structured; it's mostly a matter of building the right interface on top of it. Of course, this would also require a built-in fact-checking layer.
The dashboard could also grow richer by adding other datasets from the PSA and other sources. Cross-referencing these could surface connections that no single dataset could show on its own.
This entire dashboard – the maps, the charts, the AI-generated insights, the fact-checking pipeline – was built in a week by one person.
Vibe coding made that possible. But as this project also showed, vibes alone are not enough.
In every email we send you'll find original reporting, evidence-based insights, online seminars and readings curated from 100s of sources - all in 5 minutes.
- Twice a week
- More than 20,000 people receive it
- Unsubscribe any time