FP&A Budgeting Engine
A REST API for corporate budget management, actuals ingestion, and variance reporting. Built to replace the manual Excel workflows FP&A teams run every month.
The Problem
Every month, finance teams export actuals from their ERP, paste them into spreadsheets, and manually calculate budget vs. actual variances by department and GL account. It is slow, error-prone, and breaks the moment someone edits the wrong cell.
This engine moves that logic into a versioned, auditable API. Budget data lives in a structured schema. Actuals are ingested on demand. Variance reports — with favourability, percentage, and absolute figures — are available instantly for any department, period, or budget version.
Features
- Budget CRUD — create and update budgets by department, GL account, fiscal period, and version (original, forecast 1–3)
- Actuals ingestion — upsert actuals from any source via JSON; designed to accept ERP export feeds
- Variance analysis — budget vs. actual with correct favourability logic (revenue accounts favour positive variance; cost accounts favour negative)
- Alert endpoint — returns all GL lines where absolute variance exceeds a configurable threshold
- Zero infrastructure — SQLite database, single binary, runs anywhere with no external dependencies
Stack
| Layer | Choice | Why |
|---|---|---|
| Language | Go | Fast, statically typed, single binary deploy |
| Routing | net/http (stdlib) |
No dependencies; method+path routing since Go 1.22 |
| Database | SQLite via modernc.org/sqlite |
Zero infrastructure, file-based, ships with the binary |
| Schema | Auto-migrated on startup | No migration tool needed; CREATE TABLE IF NOT EXISTS is idempotent |
Project Structure
fpa-budgeting-engine/
├── cmd/server/main.go # Entry point, wiring, graceful shutdown
├── internal/
│ ├── handler/ # HTTP layer — decode request, call service, encode response
│ │ ├── budget.go
│ │ ├── actuals.go
│ │ └── variance.go
│ ├── model/ # Domain types
│ │ ├── budget.go
│ │ ├── actuals.go
│ │ └── variance.go
│ ├── repository/ # Database queries
│ │ ├── budget_repo.go
│ │ └── actuals_repo.go
│ └── service/ # Business logic
│ ├── budget_service.go
│ └── variance_service.go
├── migrations/ # Reference SQL (schema is also auto-applied on start)
├── scripts/seed_demo.sql # Realistic demo data for Engineering, Sales, Marketing
├── docs/openapi.yaml # API specification
└── .env.example
Getting Started
git clone https://gitea.yoursite.com/yourname/fpa-budgeting-engine
cd fpa-budgeting-engine
cp .env.example .env
go mod tidy
go run ./cmd/server
# → API on http://localhost:8080
# → fpa.db created automatically on first run
To load demo data (Engineering, Sales, Marketing for FY2024 P09):
# Demo data loads via the ingest endpoint — see scripts/seed_demo.sh
# or POST directly to /api/v1/actuals/ingest
API
Budgets
POST /api/v1/budgets Create a budget line
PUT /api/v1/budgets/{id} Update amount or notes
DELETE /api/v1/budgets/{id} Remove a budget line
Actuals
POST /api/v1/actuals/ingest Upsert an actual (idempotent by period + dept + GL)
Variance
GET /api/v1/variance Full variance report
GET /api/v1/variance/alerts Lines exceeding threshold (default 10%)
Query parameters for variance endpoints:
| Param | Example | Description |
|---|---|---|
year |
2024 |
Fiscal year |
period |
9 |
Fiscal period (1–12) |
dept |
ENG |
Department code (omit for all) |
version |
original |
Budget version |
threshold |
15 |
Alert threshold % (alerts endpoint only) |
Health
GET /api/v1/health Returns 200 if DB is reachable
Example: Variance Report
GET /api/v1/variance?year=2024&period=9&dept=ENG
{
"department": "ENG",
"fiscal_year": 2024,
"fiscal_period": 9,
"version": "original",
"currency": "DKK",
"total_budget": 6740000,
"total_actual": 7074600,
"total_variance": -334600,
"total_variance_pct": -4.97,
"lines": [
{
"gl_code": "6100",
"gl_description": "Salaries & Wages",
"gl_type": "headcount",
"budget": 4200000,
"actual": 4380000,
"variance_abs": -180000,
"variance_pct": -4.29,
"status": "unfavourable",
"currency": "DKK"
},
{
"gl_code": "6500",
"gl_description": "Consulting & Contractors",
"gl_type": "opex",
"budget": 450000,
"actual": 680000,
"variance_abs": -230000,
"variance_pct": -51.11,
"status": "unfavourable",
"currency": "DKK"
},
{
"gl_code": "5000",
"gl_description": "Cloud Infrastructure",
"gl_type": "cogs",
"budget": 850000,
"actual": 791000,
"variance_abs": 59000,
"variance_pct": 6.94,
"status": "favourable",
"currency": "DKK"
}
]
}
Finance Concepts
Favourability convention — whether a variance is good or bad depends on the account type. Revenue accounts are favourable when actuals exceed budget. Cost accounts are favourable when actuals come in under budget. The engine handles both correctly using a favour_high flag on each GL account.
Budget versioning — original budget and up to three forecast revisions are stored separately, enabling budget vs. forecast vs. actual three-way comparison without overwriting history.
GL account types — accounts are typed as revenue, cogs, opex, capex, or headcount. This drives both the P&L rollup structure and the favourability logic.
Fiscal periods — stored as integers 1–12, decoupled from calendar months so the schema supports fiscal years that start in any month.
Configuration
# .env.example
DB_PATH=fpa.db # Path to SQLite file. Use :memory: for tests.
PORT=8080
Why Go for a Finance API
Most finance tooling is Python notebooks or Excel macros — useful for analysis, fragile in production. Go compiles to a single binary with no runtime dependencies, handles concurrency safely, and makes every error path explicit. The result is an API that behaves predictably under load and is straightforward to deploy on any server or container without a setup checklist.