new readme
This commit is contained in:
250
README.md
250
README.md
@@ -1,37 +1,35 @@
|
||||
# FP&A Budgeting Engine
|
||||
|
||||
A production-grade REST API for corporate budgeting, variance analysis, and department-level financial reporting. Built to automate the workflows FP&A teams typically run manually in Excel.
|
||||
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 Business Problem
|
||||
## The Problem
|
||||
|
||||
Every month, FP&A analysts pull actuals from ERP systems, paste them into spreadsheets, and manually calculate budget vs. actual variances by department and cost center. This is slow, error-prone, and doesn't scale.
|
||||
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 replaces that workflow with a reliable API: budget data is structured in a normalized schema, actuals are ingested on a schedule, and variance reports are available on demand — for any department, any period, any GL account.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## What It Does
|
||||
## Features
|
||||
|
||||
- **Budget management** — Create and version annual budgets by department and GL account
|
||||
- **Actuals ingestion** — Load actuals from CSV or JSON (ERP export format)
|
||||
- **Variance analysis** — Budget vs. actual, favourable/unfavourable, percentage and absolute
|
||||
- **Rollups** — P&L rollup by department, cost center, and fiscal period
|
||||
- **Alerts** — Flag accounts exceeding budget by a configurable threshold
|
||||
- **Audit trail** — Every budget change is timestamped and attributed
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
## Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| API server | Go (net/http + chi router) |
|
||||
| Database | PostgreSQL |
|
||||
| Schema migrations | golang-migrate |
|
||||
| Config | Environment variables (.env) |
|
||||
| Docs | OpenAPI 3.0 (docs/openapi.yaml) |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -39,145 +37,173 @@ This engine replaces that workflow with a reliable API: budget data is structure
|
||||
|
||||
```
|
||||
fpa-budgeting-engine/
|
||||
├── cmd/
|
||||
│ └── server/
|
||||
│ └── main.go # Entry point
|
||||
├── cmd/server/main.go # Entry point, wiring, graceful shutdown
|
||||
├── internal/
|
||||
│ ├── handler/ # HTTP handlers
|
||||
│ ├── handler/ # HTTP layer — decode request, call service, encode response
|
||||
│ │ ├── budget.go
|
||||
│ │ ├── actuals.go
|
||||
│ │ └── variance.go
|
||||
│ ├── model/ # Domain types
|
||||
│ ├── model/ # Domain types
|
||||
│ │ ├── budget.go
|
||||
│ │ ├── actuals.go
|
||||
│ │ └── variance.go
|
||||
│ ├── repository/ # DB layer
|
||||
│ ├── repository/ # Database queries
|
||||
│ │ ├── budget_repo.go
|
||||
│ │ └── actuals_repo.go
|
||||
│ └── service/ # Business logic
|
||||
│ └── service/ # Business logic
|
||||
│ ├── budget_service.go
|
||||
│ └── variance_service.go
|
||||
├── migrations/
|
||||
│ ├── 001_create_departments.up.sql
|
||||
│ ├── 002_create_gl_accounts.up.sql
|
||||
│ ├── 003_create_budgets.up.sql
|
||||
│ ├── 004_create_actuals.up.sql
|
||||
│ └── 005_create_audit_log.up.sql
|
||||
├── docs/
|
||||
│ └── openapi.yaml
|
||||
├── scripts/
|
||||
│ └── seed_demo.sql # Sample data for demo/testing
|
||||
├── .env.example
|
||||
├── docker-compose.yml
|
||||
├── Makefile
|
||||
└── README.md
|
||||
├── 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key API Endpoints
|
||||
|
||||
```
|
||||
POST /api/v1/budgets Create a new budget entry
|
||||
GET /api/v1/budgets?dept=&period= List budgets with filters
|
||||
PUT /api/v1/budgets/{id} Update a budget line
|
||||
POST /api/v1/actuals/ingest Ingest actuals (JSON or CSV)
|
||||
GET /api/v1/variance?dept=&period= Variance report
|
||||
GET /api/v1/variance/summary Full P&L summary
|
||||
GET /api/v1/variance/alerts Accounts over threshold
|
||||
GET /api/v1/rollup?by=department Rollup by dimension
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Finance Concepts Implemented
|
||||
|
||||
**Chart of Accounts** — GL accounts are typed (revenue, COGS, opex, capex) and roll up into P&L line items correctly.
|
||||
|
||||
**Variance conventions** — Revenue variances are favourable when actuals exceed budget. Cost variances are favourable when actuals are below budget. The engine handles both correctly.
|
||||
|
||||
**Fiscal periods** — Supports fiscal year offsets (e.g. FY starting April). Periods are stored as fiscal quarters and months, not calendar months.
|
||||
|
||||
**Budget versioning** — Original budget, Forecast 1, Forecast 2 are tracked separately, enabling Budget vs. Forecast vs. Actual three-way comparison.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
# Clone and configure
|
||||
git clone https://gitea.yoursite.com/yourname/fpa-budgeting-engine
|
||||
cd fpa-budgeting-engine
|
||||
|
||||
cp .env.example .env
|
||||
|
||||
# Start Postgres
|
||||
docker-compose up -d db
|
||||
go mod tidy
|
||||
go run ./cmd/server
|
||||
# → API on http://localhost:8080
|
||||
# → fpa.db created automatically on first run
|
||||
```
|
||||
|
||||
# Run migrations
|
||||
make migrate-up
|
||||
To load demo data (Engineering, Sales, Marketing for FY2024 P09):
|
||||
|
||||
# Seed demo data
|
||||
psql $DATABASE_URL < scripts/seed_demo.sql
|
||||
|
||||
# Start the server
|
||||
make run
|
||||
# → API available at http://localhost:8080
|
||||
```bash
|
||||
# Demo data loads via the ingest endpoint — see scripts/seed_demo.sh
|
||||
# or POST directly to /api/v1/actuals/ingest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Variance Report Response
|
||||
## 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
|
||||
```
|
||||
|
||||
```json
|
||||
GET /api/v1/variance?dept=engineering&period=2024-Q3
|
||||
|
||||
{
|
||||
"department": "Engineering",
|
||||
"period": "2024-Q3",
|
||||
"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_account": "6100",
|
||||
"description": "Salaries & Wages",
|
||||
"gl_code": "6100",
|
||||
"gl_description": "Salaries & Wages",
|
||||
"gl_type": "headcount",
|
||||
"budget": 4200000,
|
||||
"actual": 4380000,
|
||||
"variance": -180000,
|
||||
"variance_pct": -4.3,
|
||||
"status": "unfavourable"
|
||||
"variance_abs": -180000,
|
||||
"variance_pct": -4.29,
|
||||
"status": "unfavourable",
|
||||
"currency": "DKK"
|
||||
},
|
||||
{
|
||||
"gl_account": "6300",
|
||||
"description": "Software Subscriptions",
|
||||
"budget": 320000,
|
||||
"actual": 289000,
|
||||
"variance": 31000,
|
||||
"variance_pct": 9.7,
|
||||
"status": "favourable"
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"total_budget": 5180000,
|
||||
"total_actual": 5290000,
|
||||
"total_variance": -110000,
|
||||
"total_variance_pct": -2.1
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why Go for a Finance API?
|
||||
## Finance Concepts
|
||||
|
||||
Go's compile-time type safety, zero-cost abstractions, and simple concurrency model make it well-suited for financial data services:
|
||||
**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.
|
||||
|
||||
- **No null pointer surprises** — strict typing prevents the class of bugs that corrupt financial calculations
|
||||
- **Fast and predictable** — consistent sub-10ms response times under load
|
||||
- **Easy to deploy** — single binary, no runtime dependencies, runs anywhere
|
||||
- **Explicit error handling** — every failure path is handled, not swallowed
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
## Configuration
|
||||
|
||||
- [ ] Multi-currency support with FX rate table
|
||||
- [ ] Excel export of variance reports (finance teams need this)
|
||||
- [ ] Webhook notifications when accounts breach threshold
|
||||
- [ ] Integration adapter for SAP/NetSuite actuals export format
|
||||
- [ ] Role-based access (department managers see only their cost centers)
|
||||
```bash
|
||||
# .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.
|
||||
Reference in New Issue
Block a user