A read-only explorer for the Philippine General Appropriations Act (GAA), covering fiscal years 2020–2026 across 40 national government groups. The portal presents national trends, department hierarchies, programs, expense classes, line-item data, generated reports, and budget-cycle comparisons.
A companion microsite at /2027 covers the FY2027 National Expenditure Program — the Executive's ₱7.20 T proposal — measured line by line against the FY2026 GAA.
The application is built with React, TypeScript, and Vite. It runs on Cloudflare Workers, uses D1 for queryable department and budget-cycle data, and reads larger JSON and Parquet assets from Cloudflare R2. DuckDB-Wasm powers in-browser analysis of columnar data.
- Node.js
^20.19.0or>=22.12.0 - npm
- A local copy of the BetterGov.PH GAA dataset on Hugging Face only when rebuilding datasets
- Wrangler authentication only when working with D1 or deploying
npm install
cp .env.example .env.local
npm run devOpen the URL printed by Vite. The example environment points the browser at the public R2 data host, so the multi-gigabyte local dataset is not required for normal UI development.
To serve data from public/data instead, leave VITE_DATA_BASE_URL unset. In this repository public/data may be a symlink to the ignored local data/ directory.
| Command | Purpose |
|---|---|
npm run dev |
Start the Vite development server |
npm run build |
Type-check and create a production build |
npm run preview |
Build and preview the application through Wrangler |
npm run lint |
Run ESLint |
npm run deploy |
Build, exclude local data assets, and deploy the Worker |
npm run build:index |
Rebuild the national summary JSON |
npm run import:nep2027 |
Build the FY2027 NEP tree in data/2027 from the DBM extract |
npm run verify:nep2027 |
Reconcile data/2027 against the raw FY2027 CSV |
npm run upload:nep2027 |
Publish the FY2027 assets to R2 under 2027/ |
npm run build:parquet -- --all |
Convert heavy department JSON tables to partitioned Parquet |
npm run build:sqlite -- --all --reset |
Rebuild the SQLite database used to prepare D1 data |
npm run dump:sql |
Export the SQLite database as D1-compatible SQL |
npm run upload:r2 -- --all |
Upload generated JSON and Parquet assets to R2 |
hearings sync |
Hearing transcripts are published by the transcriptions pipeline: sync_to_site.py uploads hearings/<videoId>/ files to R2 and upserts hearings rows into D1. Served at /hearings (grid) and /hearings/:videoId (interactive transcript + summary). |
Most data scripts also accept a single department, for example:
npm run build:parquet -- --dept=07 --table=objects
npm run build:sqlite -- --dept=07
npm run upload:r2 -- --dept=07 --dry-runCopy .env.example and configure only the values needed for your workflow:
# Browser-side base URL for JSON and Parquet assets.
VITE_DATA_BASE_URL=https://budget-assets.bettergov.ph
# Required only by the R2 upload and cleanup scripts.
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=budgetNever commit R2 credentials. .env and .env.local are ignored by Git.
src/pages/ Route-level React views
src/components/ Shared portal and report UI
src/lib/ Data loading, DuckDB, CSV, and formatting utilities
src/worker/ Cloudflare Worker and D1 API routes
scripts/ Dataset conversion, verification, upload, and deploy helpers
docs/ Detailed data-pipeline documentation
data/ Generated/source data (ignored by Git)
The main routes are:
/— national overview/explore— dataset exploration/methodology— coverage and data-quality notes/d/:deptId/*— department overview, trends, programs, objects, data, reports, and budget-cycle views/2027— FY2027 National Expenditure Program microsite (overview,/2027/d/:deptId,/2027/explore,/2027/methodology)/api/dept/:deptId/*— internal Worker endpoints backed by D1 (used by the SPA)/api/v1/*— public REST API (see below)/docs— public API documentation/mcp— MCP server for AI agents
A versioned, CORS-open, read-only public API serves all three datasets (GAA FY2020–2026, NEP FY2027, budget cycle) with every amount rescaled to exact pesos:
GET /api/v1— endpoint indexGET /api/v1/gaa/...— national totals, departments, agencies, expense classes, programs, object-level line items, cross-department program searchGET /api/v1/nep/2027/...— national overview, departments, and rollups by agency/program/expense class/fund/region/object/operating unit/divisionGET /api/v1/budget-cycle/...— NEP → GAA → execution facts for covered departmentsGET /api/v1/openapi.json— OpenAPI 3.1 specification
Human-readable documentation is served at /docs. The same data is exposed to AI agents
through a stateless Streamable-HTTP MCP server at /mcp
(claude mcp add --transport http ph-budget https://<host>/mcp). The REST handlers and MCP
tools share one data layer (src/worker/public-api.ts), so the two surfaces cannot drift.
Small core datasets are loaded through the Worker or as JSON. Larger hierarchy and object tables are stored as year-partitioned Parquet and queried with DuckDB-Wasm. Production asset URLs are resolved through VITE_DATA_BASE_URL; the Worker serves the SPA and handles /api/* requests first.
The source dataset is intentionally excluded from Git. It is available from the BetterGov.PH GAA dataset on Hugging Face. With a populated data/<department-id>/ tree, the typical GAA asset workflow is:
npm run build:index
npm run build:parquet -- --all
npm run build:sqlite -- --all --reset
npm run dump:sql
npm run upload:r2 -- --all --dry-run
npm run upload:r2 -- --allReview generated SQL before applying it to D1. R2 upload commands require the credentials described above.
The FY2027 NEP microsite has its own self-contained pipeline — one CSV in, data/2027 out, no D1 involved:
npm run import:nep2027
npm run verify:nep2027
npx wrangler d1 execute budget --local --file=data/2027/d1-import.sql
npm run upload:nep2027 -- --dry-run
npm run upload:nep2027FY2027 is hybrid: the aggregation tables (nep_meta, nep_departments,
nep_rollups) are served live from D1 through /api/nep2027/*, and only the
line-item parquet is fetched from the data host.
See FY2027 NEP import and the /2027 microsite for the field mapping, the two structural corrections it makes over the legacy tree, and the analyst caveats. Note that .env points the browser at the production R2 host, which does not yet carry the 2027/ prefix, so the microsite will 404 on a plain npm run dev. Until the assets are published, create a .env.local (gitignored, and it overrides .env) containing:
VITE_DATA_BASE_URL=That serves every dataset from the public/data symlink instead. Delete it once npm run upload:nep2027 has run.
The optional NEP → GAA → execution dataset has a separate import, verification, and D1 loading workflow. See Budget-cycle import and normalization for the source workbook requirements, crosswalk rules, quality checks, and release procedure.
- GAA source amounts are published in thousands of pesos and converted to full pesos for display.
- GAA appropriations are legal authority to spend; they are not obligations or disbursements.
- UACS object codes changed between 2025 and 2026, so object-level year-over-year comparisons require care.
- The FY2027 NEP is a proposal, not an enacted appropriation; Congress amends it before it becomes the GAA. FY2027 and FY2026 share the post-2025 object-code scheme, so that particular comparison is safe.
- Program renames and organizational changes can break otherwise continuous series.
- AI-assisted aggregation and editorial material should be verified against official source documents before citation.
See the in-app methodology page for the complete set of coverage and data-quality caveats.