Files
FPandA-Engine/README.md
2026-03-20 14:05:09 +01:00

6.5 KiB
Raw Blame History

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 13)
  • 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 (112)
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 112, 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.