naiasno.bg is an open-source platform for exploring how Bulgaria votes and how it is governed. It began with parliamentary election results from 2005 onward and has grown into nine connected modules that share one rule: every figure comes from a public source, and every page links back to it.
The interface is available in Bulgarian and English.
- Elections — parliamentary, presidential and local results from the national total down to the individual polling section; turnout, candidate preferences, vote flows, comparisons across cycles, the officials-vs-CEC reconciliation, and an election-risk screening index.
- Parliament — roll-call votes of the National Assembly with per-MP attendance, party cohesion, dissent and vote-similarity metrics, bills, governments and party history.
- The state budget — revenue and spending by ministry, programme and month, the consolidated fiscal programme, municipal finance, and a tax-policy simulator that scores a rate change against both budget revenue and one worked payslip.
- Public procurement — contracts and tender procedures from four source feeds, reconciled and de-duplicated, with amendments, supplier and buyer profiles, CPV and settlement breakdowns, a per-contract risk index, and the appeals filed with the competition authority.
- EU funds and subsidies — ISUN and Interreg projects, agricultural payouts, currently open calls, and base rates for what a project of a given kind typically receives.
- People and connections — public figures, their asset and interest declarations, their roles in the Commerce Registry, and the links between them — resolved through a unified person layer that refuses a shared name rather than grading it.
- Local government — municipal council decisions, capital programmes, local taxes, mayor pay, and a per-settlement dashboard that gathers everything about one place.
- Prices and consumption — retail prices by chain, product and town, fuel, electricity and gas, and comparisons against the rest of the EU.
- Indicators and context — macroeconomic and regional series from Eurostat, the World Bank and the national statistics institute, plus demographics, education, healthcare and sector dashboards.
Two more things are part of the product rather than the data:
- Наясно AI — a chat interface over the same corpora. It answers from tool calls against the real data, so the figures it returns are computed rather than generated, and each one carries a link to the page it came from.
- The data map — every source traced to the datasets built from it and the features they power, with downloads, refresh cadence and an update log.
Risk flags are screening signals, not findings of wrongdoing. See METHODOLOGY.md and LICENSE for the methodology and reuse disclaimer.
The application is a hybrid static and database-backed React app:
scripts/fetches, parses, validates, and joins upstream public data.- Static and precomputed artifacts are written under
data/; selected trees are served from Google Cloud Storage and resolved in the browser throughsrc/data/dataUrl.ts. - Large, relational, and search-oriented corpora are loaded into PostgreSQL.
The Firebase
dbfunction serves them under/api/db/**and also provides server-rendered metadata for page families that are not statically prerendered. - Vite builds the React application. Firebase Hosting serves the bundle, prerendered HTML, sitemaps, fonts, images, and other public assets.
There is therefore both a static data layer and a runtime API. A change may need a data publish, a PostgreSQL load or migration, a Cloud Function deploy, a Hosting deploy, or a combination of them.
- React 19, strict TypeScript, Vite 6 with SWC, and React Router 7
- TanStack Query and TanStack Table
- Tailwind CSS, CSS Modules, Radix UI, Recharts, D3, and Leaflet
- PostgreSQL 16 locally in Docker and in production on Cloud SQL
- Firebase Hosting and Functions, plus Google Cloud Storage
- Vitest for unit, component, and data-integrity tests; Playwright for browser, SEO, and performance checks
src/ React application, routes, data hooks, UI, and translations
functions/ Firebase runtime API and server-rendered page handlers
scripts/ Data ingests, transforms, PostgreSQL loaders, build tools, tests
data/ Processed static data and committed pipeline outputs
raw_data/ Raw and cached source material; many large inputs are gitignored
public/ Hosting assets, generated sitemaps, articles, fonts, and images
state/ Source-watcher and successful-ingest state
.agents/skills/ Project data-update and audit runbooks
docs/plans/ Dated implementation plans and design records
CLAUDE.md is the canonical repository and operations guide. Read it before changing data loaders, identity logic, PostgreSQL migrations, or deployment flows; it records correctness constraints and ordering requirements that do not belong in this introductory README.
The frozen news project moved out of this repo in November 2026, to a private local repo.
The root uses pnpm (the exact version is pinned in package.json's
packageManager field; corepack enable provides it). functions/ is deployed
separately and keeps its own npm lockfile. Install dependencies and start Vite:
pnpm install
pnpm run devThe development server mounts local data/ files at the same paths used by the
application. Many generated artifacts are committed, while large source caches
and some derived inputs are intentionally gitignored.
To rebuild the static data pipeline:
pnpm run data # election/data pipeline with explicit flags as needed
pnpm run prod # full static pipeline: --all --prodDatabase-backed pages need the local PostgreSQL instance. The full refresh is large and skips with a warning where an optional gitignored input is absent:
pnpm run db:pg:up
pnpm run db:refreshBoth .env.local and .env.production are gitignored. Local development can
leave VITE_DATA_BASE_URL unset so dataUrl() uses same-origin paths. A
production build needs the public data origin, either in the environment or in
the local .env.production file:
VITE_DATA_BASE_URL=https://storage.googleapis.com/data-electionsbg-comSome operator-only ingests also require API keys or manually downloaded source
files. Their script headers and the matching .agents/skills/*/SKILL.md runbooks
are authoritative for those prerequisites.
pnpm run dev # Vite development server
pnpm run build # typecheck, production build, prerender, SEO artifacts
pnpm run lint # ESLint; Prettier is enforced through ESLint
pnpm run format # ESLint autofix
pnpm run test:unit # Vitest unit and component tests
pnpm run functions:test # Node tests for the Firebase functions package
pnpm run test:data # PostgreSQL data-integrity gates; skip when PG is absent
pnpm run test:build # build, then Playwright browser/SEO/performance tests
pnpm run sitemap # regenerate sitemap files
pnpm run watch # check registered upstream sources for changes
pnpm run db:pg:up # start local PostgreSQL on port 5433
pnpm run db:refresh # rebuild the local database in dependency orderCI runs lint, unit tests, Function tests, a production build, and Playwright.
The refresh system has two parts:
pnpm run watchfingerprints registered upstreams at their configured cadence and writes state understate/watch/plus a human-readable report underdata-reports/.- The
process-watch-reportskill compares watcher state withstate/ingest/, runs only the affected update skills, verifies their integrity gates, and stamps a source as ingested only after a clean run.
Individual refresh procedures live in .agents/skills/. Several sources need
browser access, a local cache, or human review, so a full refresh is not assumed
to be a single unattended network command.
Every source, what is built from it, and which features it powers are published and kept current at naiasno.bg/data — the interactive data map, with the full source list, original publishers, downloads, refresh cadence and the recent-update log behind it.
That page is the maintained source of truth, and this file deliberately does not restate it: a source list duplicated into a README goes stale silently, on the one claim this project cannot afford to get wrong. Republished public data retains the terms of its original publisher.
The main commands target separate layers:
pnpm run deploy # Firebase Hosting only
pnpm run deploy:db # Firebase `db` Function only
pnpm run bucket:sync:dry # preview the selected static-data upload
pnpm run bucket:sync # publish the selected static-data trees to GCSPostgreSQL migrations and cloud loaders are separate again. Deployment order is
significant for routes that span Cloud SQL, Functions, and Hosting, and some
function-served pages require an additional Hosting cache purge after a bundle
change. Follow the relevant deployment section in CLAUDE.md; do not
infer that pnpm run deploy publishes the API or database.
LICENSE is authoritative. In summary:
- First-party code, specifications, build configuration, and generated first-party output are MIT licensed.
- Republished public data under
data/,raw_data/, and the storage bucket retains each source's terms. - Third-party fonts and vendored packages retain their upstream licences.
- The project brand, logos, videos, and photographs of identifiable people are reserved as described in the licence.
The repository is marked "private": true in package.json only to prevent an
accidental npm publish; it does not change the reuse rights in LICENSE.
Issues and pull requests are welcome. See CONTRIBUTING.md for the required checks, inbound licence terms, and the additional rules for changes to the published risk methodology.