Who really controls Nigeria’s companies is reported in scattered news stories. I wanted a system that reads that news on its own, pulls out the ownership facts as records, and writes a brief a person can read in minutes.
How I built it
01
Schedule
GitHub Actions runs the pipeline four times a day, then exits — nothing to keep alive or pay for.
02
Collect
Google News RSS plus NewsAPI, GNews, NewsData and The Guardian.
03
Extract and write
An LLM cascade: extraction tries Ollama then NVIDIA NIM; the brief tries Gemini, NVIDIA NIM, Ollama, then OpenAI.
04
Store
Google Sheets is the database (7 tabs), so non-technical reviewers can audit and correct records.
05
Publish
Each run exports static JSON; an Astro site on Cloudflare Pages publishes the brief, a register and entity pages.
// how the data moves
aura_pipeline — live
How it worksdata flows ↓
01
News sourcesRSS + 4 news APIs, every run
02
LLM cascadeextract, then write the brief
03
Google Sheets7 tabs, auditable by people
04
Live siteAstro on Cloudflare Pages, $0
[ DECISION LOG ]
What went wrong, and what I decided.
// 4 decisions
D1
It once published junk while every run showed green.
what happened
An earlier version silently fell back to keyword heuristics and, for days, published sports and celebrity stories as corporate intelligence.
decision
Fail loudly: if every provider fails, the run exits non-zero and publishes nothing. Every record now stores which model produced it.
result
No more quiet bad data; failures are visible.
D2
A pinned model was withdrawn mid-flight.
what happened
gemini-2.5-flash started returning 404s.
decision
Made model ids configurable and moved the default to the rolling gemini-flash-latest alias.
result
Provider changes no longer break runs.
D3
Off-topic stories kept coming back.
what happened
Re-analysing the same irrelevant articles wastes calls.
decision
Off-topic stories are recorded as “Filtered” and their URLs cached.
result
Each article is judged once.
D4
Article text is untrusted input to an LLM.
what happened
A story could carry instructions aimed at the model.
decision
Article text is wrapped with an instruction to ignore embedded directives, and all model output is escaped before rendering.