Data engineering and RAG
DeutschlandApp
Seven official sources in one database, plus an AI that answers from them through RAG, with sources and charts.
Local prototype, never deployed
111,103,767
official data points in a single PostgreSQL database
- 8.6Mtime series
- 2.4MBundestag documents
- 6,691GENESIS tables
- 30 GBdatabase size
110.8M rows in under 44 hours
The first import, July 2026. Peak: 10.8M rows in one hour.
Data from 1871 to 2070
95% of the values are from 2000 to 2029. From 2030 they are official projections.
Where the 30 GB sit
The observations live in a TimescaleDB hypertable with 202 chunks.
The largest single tables
9 tables hold over 1 million values, 242 over 100,000.
Turning the data into answers: RAG
The model answers from the data it fetched. Retrieval runs through tools and full-text search.
- Question“How have consumer prices changed?”
- RetrievalFull-text search finds the statistic among 13,164 catalogue entries.
- Fetch19 MCP tools fetch the values straight from the database.
- AnswerText and a chart from the fetched values, with the source.
All values as a table
| Hour | + | total |
|---|---|---|
| 12 July, 03:00 | 411,050 | 411,050 |
| 12 July, 04:00 | 528,359 | 939,409 |
| 12 July, 05:00 | 53,308 | 992,717 |
| 12 July, 13:00 | 1,181,428 | 2,174,145 |
| 12 July, 14:00 | 47,067 | 2,221,212 |
| 12 July, 17:00 | 19,264 | 2,240,476 |
| 12 July, 20:00 | 485,862 | 2,726,338 |
| 12 July, 21:00 | 42,396 | 2,768,734 |
| 12 July, 22:00 | 108 | 2,768,842 |
| 12 July, 23:00 | 99,074 | 2,867,916 |
| 13 July, 00:00 | 62,764 | 2,930,680 |
| 13 July, 03:00 | 81,612 | 3,012,292 |
| 13 July, 04:00 | 3,219,071 | 6,231,363 |
| 13 July, 05:00 | 7,849,671 | 14,081,034 |
| 13 July, 06:00 | 10,771,356 | 24,852,390 |
| 13 July, 07:00 | 7,315,336 | 32,167,726 |
| 13 July, 08:00 | 1,359,011 | 33,526,737 |
| 13 July, 09:00 | 459,639 | 33,986,376 |
| 13 July, 10:00 | 1,667,677 | 35,654,053 |
| 13 July, 11:00 | 6,770,268 | 42,424,321 |
| 13 July, 12:00 | 6,726,307 | 49,150,628 |
| 13 July, 13:00 | 7,132,230 | 56,282,858 |
| 13 July, 14:00 | 5,029,853 | 61,312,711 |
| 13 July, 15:00 | 8,702,537 | 70,015,248 |
| 13 July, 16:00 | 7,247,192 | 77,262,440 |
| 13 July, 17:00 | 8,902,121 | 86,164,561 |
| 13 July, 18:00 | 9,718,384 | 95,882,945 |
| 13 July, 19:00 | 5,449,487 | 101,332,432 |
| 13 July, 20:00 | 3,533,825 | 104,866,257 |
| 13 July, 21:00 | 4,491,783 | 109,358,040 |
| 13 July, 22:00 | 1,369,868 | 110,727,908 |
| 13 July, 23:00 | 64,131 | 110,792,039 |
| Data from 1871 to 2070 | values |
|---|---|
| 1870s | 3,636 |
| 1880s | 4,040 |
| 1890s | 4,040 |
| 1900s | 4,040 |
| 1910s | 4,054 |
| 1920s | 4,056 |
| 1930s | 4,060 |
| 1940s | 4,120 |
| 1950s | 18,294 |
| 1960s | 71,607 |
| 1970s | 172,376 |
| 1980s | 371,240 |
| 1990s | 3,689,756 |
| 2000s | 26,711,131 |
| 2010s | 46,561,292 |
| 2020s | 32,144,978 |
| 2030s | 325,917 |
| 2040s | 324,352 |
| 2050s | 324,180 |
| 2060s | 324,180 |
| 2070s | 32,418 |
Measured in the app's database, data as of July 2026. A local prototype, not an official service.
Core skills
- Data engineering
- LLMs and agents
- Backend and APIs
- Reliability
- Mobile and native
- Integrations
Tech stack
- PostgreSQL 16
- TimescaleDB
- JSONB
- Full-text search
- Playwright
- NestJS 11
- TypeORM
- Zod
- Swagger
- RAG
- OpenAI Responses API
- MCP
- Whisper
- Expo
- React Native
- NativeWind
- Reanimated
The hardest part
The numbers that supposedly couldn't be fetched
“Impossible” is only a guess until you've tried the path a real browser takes.
The whole story
What it looked like
On Dashboard Deutschland, the values are there for anyone to see. The program still couldn't get them: the JSON held no values, and the hidden endpoints answered 403.
What was really going on
The site has no documentation, and the obvious path led nowhere. So the notes first said: unreachable.
The fix
Open the site in a real browser and watch what it loads in the background. That's where the CSV download IDs turned up. This is also how the pipeline now picks up new topics automatically.
- 7
- official and public data sources in one database
- 19
- MCP tools for retrieval
- 49,766
- retroactive corrections to old values detected
- 92
- HTTP routes, 18 entities and 14 migrations
In plain words
How has unemployment changed? How did the Bundestag vote? The answers are public but scattered across many government sites. This app puts seven sources into one database, and an AI answers from them instead of guessing. It isn't an official service, just a personal project.
Official data is public but hard to find, and an LLM likes to guess. The idea: the model has to fetch every number from a database and show the source. For that, the data first had to be in one place.
How it works
Step 1 of 5
Sources
A daily cron job refreshes every source in turn behind a lock. Dumps can continue after a restart.
What was built
- Seven mirrored sources, including GENESIS (Federal Statistical Office), the Bundestag's DIP and abgeordnetenwatch
- A time series store in a TimescaleDB hypertable, with 111 million observations
- Change detection with separate hashes and a log of retroactive corrections (49,766 entries)
- An MCP server with 19 tools for search, series, comparisons, votes and legislation
- An assistant with visible tool steps, voice input and interactive charts inside its answers
- An app with 16 screens, 14 of them built: chat, votes, legislation, statistics search, politician cards
How those skills were used
- Data engineering
- Idempotent ingestion, revisions, resumable bulk loads and daily syncs on over 100 million rows
- LLMs and agents
- RAG through 19 self-written MCP tools and German full-text search, connected to a hosted model, with prompt rules against invented values
- Backend and APIs
- 92 HTTP routes, 42 of them a typed facade over the GENESIS API, 18 entities and 14 TypeORM migrations
- Reliability
- Semaphores and token buckets against external limits, and one failing source doesn't stop the others
- Mobile and native
- An Expo app with typed routes and interactive SVG charts, built from a design handoff
- Integrations
- Seven external data sources, each with its own formats, limits and failure modes
My role
Concept, architecture, directing the agents and verification. Claude Code wrote most of the code, inside an agent workflow I developed myself.
See the Context Observer project