0 · Who this is for changes the design
This is a tool for FP&A, not for HR. That is not a cosmetic difference — it moves the spine of the model.
| An HR headcount tool | An FP&A planning tool | |
|---|---|---|
| Primary object | the person | the plan version |
| Spine | the org chart | the cost centre — because it ties to the GL |
| Headline output | a headcount | a variance bridge — plan vs forecast, explained |
| Unit | people | FTE and dollars, phased by month |
| The question | “who works here?” | “why are we off plan, and what do we do about it?” |
ELT and project remain — but as rollup and allocation dimensions on top of the cost centre, not instead of it. A number FP&A cannot reconcile to the general ledger is a number they cannot use.
1 · The variance bridge is the product
“We are $2.1M under budget” is not an answer. It is the beginning of a two-week investigation that FP&A runs by hand, in a spreadsheet, every single month.
The answer FP&A actually needs is the walk — the decomposition of the gap into its drivers:
Plan (AOP) $24.0M 142.0 FTE
− hiring slippage (6 reqs, ~1 qtr) −1.4M − 6.0 favourable, and temporary
− attrition above plan (2.1 pts) −0.9M − 4.2 favourable now, a hole later
+ hired above planned level/geo +0.2M 0.0 rate & mix — permanent
+ 2 incremental reqs not in plan +0.3M + 2.0 unbudgeted
− backfill lag (avg 47 days) −0.3M 0.0 timing only
─────────────────────────────────────────────────────
Current forecast $21.9M 133.8 FTE
Variance to plan −$2.1M − 8.2 FTE
The drivers must sum to the variance. Exactly. If they don't, the decomposition is wrong — and the system says so rather than plugging the difference.
That is a hard check in this codebase's existing sense: a check that fails if the answer is wrong. A bridge whose drivers don't reconcile to the total is not a bridge, it is a plug — and “plug” is the word FP&A uses right before they stop trusting a tool.
Every driver is separately meaningful, and they carry different implications:
Timing (slippage, backfill lag)
Favourable this year, and reverses next year. Money you have not saved — only deferred.
Rate & mix
Hired at a higher level or a more expensive geo than planned. Permanent. It compounds into next year's run-rate.
Attrition
Favourable in-year, but it is a hole in capacity. The favourable variance and the missed roadmap are the same event.
Volume (incremental reqs)
Genuinely unbudgeted headcount. The only driver that is a real decision rather than a consequence.
2 · Plan versions — FP&A lives in re-forecast cycles
There is never one plan. There is the AOP (the annual operating plan, locked), then F1, F2, F3… — each re-forecast — and then actuals. FP&A is constantly asked “what did we think in F2, and what changed?”
The ledger is bitemporal: it separates when something was true (valid time) from when we learned it (transaction time). Plan versions fall straight out of it. “Show me the F2 forecast as we understood it on 15 May” is ledger.asOf(validAt, knownAt) — a query, not a feature.
Every bridge is therefore version-to-version: AOP → F1, F1 → F2, F3 → actual. Not just plan → today.
3 · The cost centre is the spine; ELT and project hang off it
CostCentre is the anchor — it is what reconciles to the general ledger, and it is what a control total is published against. ELT is a rollup of cost centres (ownership). Project is an allocation dimension that crosses cost centres (a person in one cost centre can work on a project sponsored by another — that is cross-charging, and FP&A cares about it a great deal).
Nesting project under ELT asserts every project belongs to exactly one ELT. Usually false, and it makes shared platform work unrepresentable. The tree must be pivotable: Cost centre → Project → Role, ELT → Cost centre → Project, Project → Cost centre. The leaf is an assignment, not a person.
4 · Headcount and FTE are not the same number
A person split 50/50 across two projects is one head and 0.5 FTE on each. Roll that up and headcount double-counts while FTE stays correct.
FTE is the number that sums. Headcount is not.
Every rollup, total and dollar figure is computed in FTE. Headcount is a distinct-person count via set-union at each node, and is labelled non-additive across siblings. And because FP&A must tie to Finance, three lines are always distinct: Headcount (FTE employees), Total workforce (incl. contractors and interns), and Spend. Contractors are excluded from headcount and absolutely consume budget — collapsing these is how a workforce number stops reconciling.
5 · Canonical model additions
All bitemporal; all flow through the existing mapper, ledger and three-state reconciliation unchanged — so an allocation two systems disagree about becomes a contested claim for free.
| Entity | Fields | Why FP&A needs it |
|---|---|---|
PlanVersion | versionId (AOP/F1/F2…), fiscalYear, lockedAt | every number is as of a plan version |
Elt | eltId, name, leader | ownership rollup |
Project | projectId, name, sponsorCostCentreId, capitalizable | who funds it — cross-charging |
Assignment | personId, projectId, allocation, validFrom/To | the join. Without it, project spend is fiction |
Departure | personId, lastDay, reason, noticeGivenAt | a known leaver — a fact, not a rate |
RateCard | role, level, geo, employmentType, fullyLoadedAnnual, currency | fully loaded — never base salary |
Assumption | scope, kind (attrition / time-to-fill), value, source | declared, versioned, on the page |
Person gains role. Req gains projectId, role, level, geo, expectedStart, backfillFor — a req is a to-be-hired head.
5.1 · backfillFor is not optional detail
A req replacing someone who quit is not net-new headcount — it restores a seat attrition removed. Count backfills as growth and you overstate hiring by exactly your attrition rate, every quarter, invisibly. It also makes the variance bridge un-closeable, because backfill lag and incremental adds move in opposite directions.
6 · Spend
- Fully loaded, never base salary — salary + benefits + tax + equity + bonus, from a rate card keyed by
role × level × geo × employmentType. Report base comp as “spend” and Finance rejects the tool on sight. - Synthetic but realistic values, grounded in plausible market ranges and labelled synthetic in the UI. No individual compensation exists anywhere in the system — which is also why this is publishable.
- Contractors cost more per FTE, not less (agency margin).
- Spend is FTE-months, phased. A hire starting 15 November costs half of November, not a year.
spend = Σ fte × allocation × monthlyRate × months_active_in_period. FP&A needs the monthly phasing, not an annual figure. - Exit run-rate is reported alongside in-year spend — the annualised cost of the org as it stands at period end. It is what next year's budget starts from, and it is the number a CFO asks for second.
- Capex/opex:
capitalizableis carried and surfaced as a flag. We mark it; we do not attempt the calculation, and we say so. - Currency is carried on the rate card. Multi-geo spend without FX is a number that will not tie.
7 · Forecast — monthly, six quarters
forecast_fte(m) = active_fte(m) // fact: people on the books
− known_departures(m) // fact: last day is a date
+ tbh_arrivals(m) // assumption: expected start
− modelled_attrition(m) // assumption: a rate, not a person
Facts and assumptions are never blended into one line.
Actual
History, from the ledger.
Committed — facts only
Actuals, minus known departures, plus TBH with a confirmed start (offer accepted). A solid line.
Modelled — an estimate
Plus TBH at its expected start, minus modelled attrition. Drawn as a band, because that is what it is.
Plan
The selected plan version (AOP, F1, F2…), phased by fiscal month.
An open req is a probability of a person, arriving after a realistic time-to-fill — a Staff engineer in the Bay Area is not a support hire in Bangalore. expectedStart = openedAt + timeToFill(role, geo). Where an offer is accepted, that date is a fact and the head moves to the committed line. Vacancy savings — budgeted heads not yet hired — is a first-class favourable variance, not an accident of the maths.
8 · The honesty rules carry forward
If the PPM tool and a team’s tracker disagree about who is on Project Atlas, and no rule has authority to pick, then Atlas’s FTE is null — and therefore its spend is null. The page says: “we cannot tell you what Atlas costs, because HRIS and the PPM tool disagree about who works on it.” To an FP&A analyst who has been burned by a confident wrong number in a board deck, that is the most valuable sentence in the product.
- A contested input poisons its dependents: contested FTE ⇒ contested spend ⇒ contested variance. Em-dash, with the disagreement shown.
- The bridge must reconcile. Drivers sum to the variance, or the system reports that it cannot decompose the gap. It never plugs.
- Every assumption is declared, versioned and displayed — attrition, time-to-fill, rate card. A forecast whose assumptions are invisible is a guess wearing a suit.
- Over-allocation is a finding, not an error. If three PMs each claim 50% of the same engineer, allocations sum to 150%. We do not normalise it away and we do not throw. That conflict is real, and a human resolves it.
9 · Where the data comes from — and disagrees
| Source | Contributes | Conflict |
|---|---|---|
ppm | projects, assignments | These two disagree about allocations — deliberately. It drives a contested project spend: the sharpest demo in the product. |
sheet_d | a team’s own view of who is on what | |
hris | role; departures | — |
ats | projectId, backfillFor, expectedStart | — |
finance | rate card, plan versions, assumptions | — |
Both new sources are mapped cold by the existing agent — no hand-authored mappings, traces committed, graded against a blind golden key. Same rules as every source before them.
10 · Surface
/bridge— the variance walk, version to version. The page FP&A opens first, and the one that answers the question they are actually asked./workforce— the nested, expandable tree with a pivot selector (cost centre / ELT / project / role). Native<details>, server-rendered, no client framework. Per node: FTE, headcount (marked non-additive), TBH (net of backfill), known leavers, spend, plan, variance./forecast— the monthly curve (plan / actual / committed / modelled band), the assumptions panel, and what drives each month's change.
All zero-runtime-LLM, replayed from committed artifacts.
11 · What a first draft would have missed
| Miss | Consequence for FP&A |
|---|---|
| A headcount number with no variance bridge | Answers a question nobody asked. The analyst still rebuilds it by hand. |
| Drivers that don't sum to the variance | A plug. The moment FP&A finds one, the tool is dead. |
| One plan, not versions (AOP / F1 / F2) | Cannot answer “what changed since last forecast?” — the core monthly ritual. |
| Timing and rate/mix blended together | Deferred spend looks like saved spend. Next year's budget is set wrong. |
| Counting backfills as growth | Overstates hiring by the attrition rate, every quarter. |
| TBH with no start date | Assumes every open req is filled tomorrow. Phasing badly wrong. |
| Annual spend instead of monthly phasing | A November hire billed as a full year. |
| Base salary instead of fully-loaded cost | Understates by 25–40%. Finance rejects the tool. |
| Contractors folded into “headcount” | Either the headcount or the spend is wrong. Usually both. |
| Adding headcount across a shared-people tree | Double-counts. The totals will not tie to the GL. |
| No exit run-rate | Cannot seed next year's budget — the CFO's second question. |
| Project nested under ELT | Shared platform work and cross-charging become unrepresentable. |
Per-source data freshness matters — a three-week-old Anaplan pull beside today's HRIS is a silent trap, and FP&A closes on a calendar. Multi-geo needs FX. And someone must own the definition of headcount when two systems disagree, which is exactly what the contested state forces into the open instead of leaving it to be settled in a meeting.
12 · Explicitly out of scope
The optimizer (allocate a fixed budget and req ceiling across projects, with shadow prices) and interactive what-if levers. Both are attractive; neither is worth building on numbers FP&A has not yet agreed on. The bridge comes first — it is what makes the numbers arguable in the first place.