Semantius Logo

Zero-Based Budgeting — Semantic Model

1. Overview

A budgeting platform implementing Peter Pyhrr’s Zero-Based Budgeting (ZBB) methodology: every cost-centre owner rebuilds their budget from zero each cycle by submitting decision packages (discrete activities or expenditures), each with multiple funding levels (minimum, current, enhanced) and a granular cost breakdown. Packages are ranked within their cost centre, reviewed through an explicit approval workflow, and the chosen funding level becomes the funded amount. The model captures the planning artefacts (cycles, packages, levels, costs) and the governance artefacts (rankings, approval actions, role assignments) but does not model actuals, variance analysis, or downstream GL postings — those live in upstream/downstream finance systems.

2. Entity summary

#Table nameSingular labelPurpose
1budget_cyclesBudget CycleThe planning period (e.g. FY26) over which budgets are rebuilt from zero
2cost_centersCost CenterOrg unit responsible for justifying its own budget — the ZBB “decision unit”
3decision_packagesDecision PackageA discrete activity, service, or expenditure being justified — the atomic unit of ZBB
4funding_levelsFunding LevelA service-level option for a package (minimum / current / enhanced) with its own cost and benefit
5cost_line_itemsCost Line ItemGranular cost row inside a funding level (e.g. salaries, software, travel)
6cost_categoriesCost CategoryTaxonomy of cost types (Salary, Contractor, Software, Travel, Capex, …)
7gl_accountsGL AccountChart-of-accounts entry linking cost lines to the general ledger
8cost_driversCost DriverQuantitative driver reusable across line items (FTE count, transaction volume, square footage)
9package_rankingsPackage RankingPriority ordering of packages within a cost centre, scoped to a cycle
10approval_actionsApproval ActionAudit-trail entry for a review decision on a package
11usersUserA person who owns, reviews, or approves packages
12cost_center_assignmentsCost Center AssignmentJunction: which user holds which role on which cost centre

Entity-relationship diagram

flowchart LR
    budget_cycles -->|scopes| decision_packages
    budget_cycles -->|scopes| package_rankings
    cost_centers -->|hierarchy| cost_centers
    cost_centers -->|owns| decision_packages
    cost_centers -->|has| cost_center_assignments
    cost_centers -->|has| package_rankings
    users -->|owns| cost_centers
    users -->|owns| decision_packages
    users -->|performs| approval_actions
    users -->|assigned via| cost_center_assignments
    decision_packages -->|has| funding_levels
    decision_packages ---|selects| funding_levels
    decision_packages -->|has| approval_actions
    decision_packages -->|ranked in| package_rankings
    funding_levels -->|breaks down| cost_line_items
    funding_levels -->|actioned in| approval_actions
    cost_categories -->|categorises| cost_line_items
    cost_categories -->|hierarchy| cost_categories
    gl_accounts -->|maps| cost_line_items
    gl_accounts -->|hierarchy| gl_accounts
    cost_drivers -->|drives| cost_line_items

3. Entities

3.1 budget_cycles — Budget Cycle

Plural label: Budget Cycles Label column: cycle_name Audit log: yes Description: A planning period (typically annual) during which cost centres rebuild their budgets from zero. The cycle bounds all packages, rankings, and approvals.

Fields

Field nameFormatRequiredLabelReference / Notes
cycle_namestringyesCycle Name(label) e.g. “FY26 ZBB Cycle”
fiscal_yearintegeryesFiscal Yeare.g. 2026
start_datedateyesStart Date
end_datedateyesEnd Date
cycle_statusenumyesStatusvalues: draft, planning, in_review, locked, archived
descriptiontextnoDescription

Relationships

  • A budget_cycle may scope many decision_packages (1:N, via decision_packages.budget_cycle_id).
  • A budget_cycle may scope many package_rankings (1:N, via package_rankings.budget_cycle_id).

3.2 cost_centers — Cost Center

Plural label: Cost Centers Label column: cost_center_name Audit log: yes Description: An organisational unit (department, function, team) accountable for justifying its own budget. In ZBB language this is the “decision unit”. Hierarchical via parent_cost_center_id.

Fields

Field nameFormatRequiredLabelReference / Notes
cost_center_codestringyesCodeunique, e.g. “CC-1001”
cost_center_namestringyesName(label)
parent_cost_center_idreferencenoParent Cost Centercost_centers (N:1, self-ref hierarchy, clear on delete)
owner_user_idreferencenoPrimary Ownerusers (N:1, clear on delete)
gl_segmentstringnoGL Segmentoptional ERP linkage
is_activebooleanyesActivedefault true

Relationships

  • A cost_center may have a parent cost_center (N:1, self-referential).
  • A cost_center may have a primary owner user (N:1).
  • A cost_center owns many decision_packages (1:N, parent, restrict on delete — historical packages are preserved).
  • A cost_center has many cost_center_assignments (1:N, parent, cascade on delete).
  • A cost_center has many package_rankings (1:N, parent, cascade on delete).

3.3 decision_packages — Decision Package

Plural label: Decision Packages Label column: package_title Audit log: yes Description: The atomic unit of ZBB — a discrete activity, service, or expenditure being justified from zero. Every package is owned by a cost centre, scoped to a cycle, broken into 2+ funding levels, and moves through an approval workflow.

Fields

Field nameFormatRequiredLabelReference / Notes
package_codestringyesCodeunique, e.g. “PKG-FY26-001”
package_titlestringyesTitle(label)
cost_center_idparentyesCost Centercost_centers (N:1, restrict on delete)
budget_cycle_idreferenceyesBudget Cyclebudget_cycles (N:1, restrict on delete)
package_typeenumyesPackage Typevalues: continuing, new, discretionary, mandatory
priority_tierenumnoPriority Tiervalues: must_have, should_have, nice_to_have
package_statusenumyesStatusvalues: draft, submitted, in_review, approved, rejected, cut, deferred
business_justificationhtmlyesBusiness Justificationthe “why” narrative — core ZBB artefact
consequences_of_not_fundinghtmlnoConsequences if Not Fundedwhat breaks if killed
alternatives_consideredhtmlnoAlternatives Considered
selected_funding_level_idreferencenoSelected Funding Levelfunding_levels (N:1, clear on delete) — set after approval
owner_user_idreferenceyesPackage Ownerusers (N:1, restrict on delete)
submitted_atdate-timenoSubmitted At
approved_atdate-timenoApproved At

Relationships

  • A decision_package belongs to one cost_center (N:1, parent, restrict on delete).
  • A decision_package is scoped to one budget_cycle (N:1, required).
  • A decision_package is owned by one user (N:1, required).
  • A decision_package has many funding_levels (1:N, parent, cascade on delete).
  • A decision_package may select one of its funding_levels as the funded option (N:1, via selected_funding_level_id, clear on delete). Circular reference with the parent edge above — selected_funding_level_id.decision_package_id must equal this.id.
  • A decision_package has many approval_actions (1:N, parent, restrict on delete to preserve audit trail).
  • A decision_package may appear in many package_rankings (1:N).

3.4 funding_levels — Funding Level

Plural label: Funding Levels Label column: funding_level_label Audit log: yes Description: A service-level option for a decision package (typically minimum / current / enhanced). Each level has its own cost stack and benefit narrative; the package owner recommends one and an approver selects one.

Fields

Field nameFormatRequiredLabelReference / Notes
funding_level_labelstringyesLevel Label(label) e.g. “Minimum”, “Current”, “Enhanced +2 FTE”
decision_package_idparentyesDecision Packagedecision_packages (N:1, cascade on delete)
level_tierenumyesTiervalues: minimum, current, enhanced, custom
level_orderintegeryesOrder1 = lowest, ascending
is_recommended_levelbooleanyesRecommended by Ownerdefault false; the owner picks one before submission
headcount_ftefloatnoHeadcount (FTE)total FTE at this level
currency_codestringyesCurrencyISO 4217, e.g. “USD”
service_descriptionhtmlyesService Descriptionwhat’s delivered at this level
benefit_narrativehtmlnoIncremental Benefitbenefit vs the next-lower level
risk_narrativehtmlnoRisk if Chosen

Relationships

  • A funding_level belongs to one decision_package (N:1, parent, cascade on delete).
  • A funding_level has many cost_line_items (1:N, parent, cascade on delete).
  • A funding_level may be referenced by many approval_actions (1:N, via approval_actions.funding_level_id, clear on delete).
  • A funding_level may be the selected level on its parent decision_package (1:0..1, via decision_packages.selected_funding_level_id).

3.5 cost_line_items — Cost Line Item

Plural label: Cost Line Items Label column: line_item_label Audit log: yes Description: A granular cost row inside a funding level — e.g. “Senior Engineer salaries (×2)”, “Datadog enterprise licence”. Supports either driver-based input (quantity × unit_cost) or lump-sum entry; total_cost_amount is always the canonical roll-up figure.

Fields

Field nameFormatRequiredLabelReference / Notes
line_item_labelstringyesDescription(label)
funding_level_idparentyesFunding Levelfunding_levels (N:1, cascade on delete)
cost_category_idreferenceyesCost Categorycost_categories (N:1, restrict on delete)
gl_account_idreferencenoGL Accountgl_accounts (N:1, clear on delete)
cost_driver_idreferencenoCost Drivercost_drivers (N:1, clear on delete)
quantityfloatnoQuantityoptional driver-based input, e.g. 2 (FTE)
unit_cost_amountfloatnoUnit Costoptional, paired with quantity
total_cost_amountfloatyesTotal Costcanonical figure used in roll-ups
currency_codestringyesCurrencyISO 4217
cost_periodenumyesPeriodvalues: one_time, recurring_annual
notestextnoNotes

Relationships

  • A cost_line_item belongs to one funding_level (N:1, parent, cascade on delete).
  • A cost_line_item belongs to one cost_category (N:1, required, restrict on delete).
  • A cost_line_item may map to one gl_account (N:1, optional).
  • A cost_line_item may be driven by one cost_driver (N:1, optional).

3.6 cost_categories — Cost Category

Plural label: Cost Categories Label column: category_name Audit log: no Description: Taxonomy of cost types used to classify line items (Salary, Contractor, Software, Travel, Capex, etc.). Hierarchical via parent_category_id so categories can roll up.

Fields

Field nameFormatRequiredLabelReference / Notes
category_codestringyesCodeunique, e.g. “SALARY”
category_namestringyesName(label)
category_typeenumyesTypevalues: opex, capex, mixed
parent_category_idreferencenoParent Categorycost_categories (N:1, self-ref, clear on delete)

Relationships

  • A cost_category may have a parent cost_category (N:1, self-referential).
  • A cost_category may classify many cost_line_items (1:N).

3.7 gl_accounts — GL Account

Plural label: GL Accounts Label column: account_name Audit log: no Description: Chart-of-accounts entry that links ZBB cost line items back to the general ledger. Hierarchical (parent/child accounts).

Fields

Field nameFormatRequiredLabelReference / Notes
account_codestringyesCodeunique, e.g. “5100”
account_namestringyesName(label) e.g. “Salaries Expense”
account_typeenumyesTypevalues: asset, liability, equity, revenue, expense, contra
parent_account_idreferencenoParent Accountgl_accounts (N:1, self-ref, clear on delete)

Relationships

  • A gl_account may have a parent gl_account (N:1, self-referential).
  • A gl_account may be mapped to by many cost_line_items (1:N).

3.8 cost_drivers — Cost Driver

Plural label: Cost Drivers Label column: driver_name Audit log: no Description: A reusable quantitative driver of cost — e.g. headcount, transaction volume, square footage. Cost line items can reference a driver to make the cost-build transparent and easy to flex.

Fields

Field nameFormatRequiredLabelReference / Notes
driver_codestringyesCodeunique, e.g. “FTE_COUNT”
driver_namestringyesName(label) e.g. “Full-Time Equivalents”
unit_of_measurestringyesUnite.g. “headcount”, “transactions/month”
current_valuefloatnoCurrent Valuemost recent quantity
descriptiontextnoDescription

Relationships

  • A cost_driver may drive many cost_line_items (1:N).

3.9 package_rankings — Package Ranking

Plural label: Package Rankings Label column: ranking_label Audit log: yes Description: A prioritisation entry — within a cost centre and cycle, this row says “package X is ranked at position Y”. Used by the cost-centre owner during the ZBB ranking ceremony and by finance during roll-up reviews.

Fields

Field nameFormatRequiredLabelReference / Notes
ranking_labelstringyesRanking(label) caller composes on insert, e.g. “FY26 / CC-1001 / #3 K8s Migration”
cost_center_idparentyesCost Centercost_centers (N:1, cascade on delete) — the scope of this ranking
budget_cycle_idreferenceyesBudget Cyclebudget_cycles (N:1, restrict on delete)
decision_package_idreferenceyesDecision Packagedecision_packages (N:1, cascade on delete)
rank_positionintegeryesRank1 = highest priority
rationaletextnoRationale

Composite uniqueness expected on (cost_center_id, budget_cycle_id, rank_position) and (cost_center_id, budget_cycle_id, decision_package_id). Implementation enforces.

Relationships

  • A package_ranking belongs to one cost_center (N:1, parent, cascade on delete).
  • A package_ranking is scoped to one budget_cycle (N:1, required).
  • A package_ranking ranks one decision_package (N:1, required).

3.10 approval_actions — Approval Action

Plural label: Approval Actions Label column: action_label Audit log: yes Description: A single review event on a decision package — submission, approval, rejection, cut to a lower funding level, deferral. The full sequence of approval_actions for a package is the audit trail of how the package moved through governance.

Fields

Field nameFormatRequiredLabelReference / Notes
action_labelstringyesAction(label) caller composes on insert, e.g. “Approve · Jane Doe · 2026-04-15”
decision_package_idparentyesDecision Packagedecision_packages (N:1, restrict on delete to preserve audit trail)
funding_level_idreferencenoFunding Levelfunding_levels (N:1, clear on delete) — level approved or cut to
actor_user_idreferenceyesActorusers (N:1, restrict on delete)
action_typeenumyesAction Typevalues: submit, approve, reject, cut, defer, request_changes, withdraw
commenttextnoComment
acted_atdate-timeyesActed At

Relationships

  • An approval_action belongs to one decision_package (N:1, parent, restrict on delete).
  • An approval_action may reference one funding_level (N:1).
  • An approval_action is performed by one user (N:1, required).

3.11 users — User

Plural label: Users Label column: display_name Audit log: no Description: A person who owns, reviews, or approves decision packages. The table_name: users matches the Semantius built-in exactly so the deployer can deduplicate against the platform user table.

Fields

Field nameFormatRequiredLabelReference / Notes
user_emailemailyesEmailunique
display_namestringyesDisplay Name(label) e.g. “Jane Doe”
is_activebooleanyesActivedefault true
departmentstringnoDepartment
job_titlestringnoJob Title

Relationships

  • A user may own many cost_centers (1:N, via cost_centers.owner_user_id).
  • A user may own many decision_packages (1:N, via decision_packages.owner_user_id).
  • A user may perform many approval_actions (1:N, via approval_actions.actor_user_id).
  • A user may have many cost_center_assignments (1:N).

3.12 cost_center_assignments — Cost Center Assignment

Plural label: Cost Center Assignments Label column: assignment_label Audit log: no Description: Junction entity that captures which user holds which ZBB role on which cost centre — owner, reviewer, approver, or controller. Drives package routing and review permissions during the cycle.

Fields

Field nameFormatRequiredLabelReference / Notes
assignment_labelstringyesAssignment(label) caller composes on insert, e.g. “Jane Doe · Owner · CC-1001”
cost_center_idparentyesCost Centercost_centers (N:1, cascade on delete)
user_idreferenceyesUserusers (N:1, cascade on delete)
assignment_roleenumyesRolevalues: owner, reviewer, approver, controller
is_primarybooleannoPrimaryone primary per (cost_center, role) by convention
valid_fromdatenoValid From
valid_todatenoValid To

Relationships

  • A cost_center_assignment belongs to one cost_center (N:1, parent, cascade on delete).
  • A cost_center_assignment references one user (N:1, cascade on delete).
  • cost_centersusers is many-to-many through this junction (with role).

4. Relationship summary

FromFieldToCardinalityKindDelete behavior
cost_centersparent_cost_center_idcost_centersN:1referenceclear
cost_centersowner_user_idusersN:1referenceclear
decision_packagescost_center_idcost_centersN:1parentrestrict
decision_packagesbudget_cycle_idbudget_cyclesN:1referencerestrict
decision_packagesselected_funding_level_idfunding_levelsN:1referenceclear
decision_packagesowner_user_idusersN:1referencerestrict
funding_levelsdecision_package_iddecision_packagesN:1parentcascade
cost_line_itemsfunding_level_idfunding_levelsN:1parentcascade
cost_line_itemscost_category_idcost_categoriesN:1referencerestrict
cost_line_itemsgl_account_idgl_accountsN:1referenceclear
cost_line_itemscost_driver_idcost_driversN:1referenceclear
cost_categoriesparent_category_idcost_categoriesN:1referenceclear
gl_accountsparent_account_idgl_accountsN:1referenceclear
package_rankingscost_center_idcost_centersN:1parentcascade
package_rankingsbudget_cycle_idbudget_cyclesN:1referencerestrict
package_rankingsdecision_package_iddecision_packagesN:1referencecascade
approval_actionsdecision_package_iddecision_packagesN:1parentrestrict
approval_actionsfunding_level_idfunding_levelsN:1referenceclear
approval_actionsactor_user_idusersN:1referencerestrict
cost_center_assignmentscost_center_idcost_centersN:1parentcascade
cost_center_assignmentsuser_idusersN:1referencecascade

cost_centersusers is many-to-many through cost_center_assignments (with assignment_role).

5. Enumerations

5.1 budget_cycles.cycle_status

  • draft
  • planning
  • in_review
  • locked
  • archived

5.2 decision_packages.package_type

  • continuing
  • new
  • discretionary
  • mandatory

5.3 decision_packages.priority_tier

  • must_have
  • should_have
  • nice_to_have

5.4 decision_packages.package_status

  • draft
  • submitted
  • in_review
  • approved
  • rejected
  • cut
  • deferred

5.5 funding_levels.level_tier

  • minimum
  • current
  • enhanced
  • custom

5.6 cost_line_items.cost_period

  • one_time
  • recurring_annual

5.7 cost_categories.category_type

  • opex
  • capex
  • mixed

5.8 gl_accounts.account_type

  • asset
  • liability
  • equity
  • revenue
  • expense
  • contra

5.9 approval_actions.action_type

  • submit
  • approve
  • reject
  • cut
  • defer
  • request_changes
  • withdraw

5.10 cost_center_assignments.assignment_role

  • owner
  • reviewer
  • approver
  • controller

6. Open questions

6.1 🔴 Decisions needed (blockers)

None.

6.2 🟡 Future considerations (deferred scope)

  • Should ZBB scopes that span multiple cost centres (cross-functional initiatives, shared services) be supported via a cost_center_groups entity, or is the current single-cost_center_id link on decision_packages sufficient?
  • Should currency_code be promoted to its own currencies entity with FX rates, to support multi-currency budget consolidation? Currently a free-text ISO 4217 string on funding_levels and cost_line_items.
  • Should justification supporting evidence (spreadsheets, vendor quotes, slide decks) be modelled via an attachments entity, or kept in an external document store?
  • Should prior-period actuals be loaded into the model for variance reporting, or always pulled from upstream finance systems at query time? (ZBB de-emphasises prior periods, but reviewers often want the comparison.)
  • Should funding-level cost roll-ups be stored as denormalised snapshots on funding_levels (for performance) or always derived from cost_line_items at query time?
  • Should rankings be expressible at multiple scopes (cost centre → function → corporate), e.g. via a ranking_scope enum and an optional roll-up parent ID, or stay scoped to cost centres only with corporate roll-up handled in the reporting layer?
  • Should cost_center_assignments enforce a single concurrent assignment per (user, cost_center, role) via valid_from/valid_to, or permit overlapping assignments? Currently the date fields are optional.

7. Implementation notes for the downstream agent

A short checklist for the agent who will materialise this model in Semantius (or equivalent):

  1. Create one module named zero_based_budgeting (the module name must equal the system_slug from the front-matter — do not invent a different slug here) and two baseline permissions (zero_based_budgeting:read, zero_based_budgeting:manage) before any entity.
  2. Create entities in the order given in §2 — entities referenced by others first. The circular reference between decision_packages.selected_funding_level_id and funding_levels.decision_package_id requires a two-pass approach: create both entities, then add decision_packages.selected_funding_level_id after funding_levels exists.
  3. For each entity: set label_column to the snake_case field marked as label in §3, pass module_id, view_permission, edit_permission. Do not manually create id, created_at, updated_at, or the auto-label field.
  4. For each field in §3: pass table_name, field_name, format, title (the Label column), and for reference/parent fields also reference_table and a reference_delete_mode consistent with §4. (The §3 Required column is analyst intent; the platform manages nullability internally and does not need a per-field flag.)
  5. Fix up each entity’s auto-created label-column field title. create_entity auto-creates a field whose field_name equals the entity’s label_column, and its title defaults to singular_label. Every entity in this model has a label_column whose §3 Label differs from singular_label (e.g. entity vendors would yield title “Vendor” but we want “Vendor Name”). After each create_entity call, follow up with update_field to set the correct title. The update_field id is the composite string "{table_name}.{field_name}" (e.g. "cost_centers.cost_center_name", "decision_packages.package_title", "funding_levels.funding_level_label") — pass it as a string, not an integer, or the update will fail. The full list of fixups:
    • budget_cycles.cycle_name → “Cycle Name”
    • cost_centers.cost_center_name → “Name”
    • decision_packages.package_title → “Title”
    • funding_levels.funding_level_label → “Level Label”
    • cost_line_items.line_item_label → “Description”
    • cost_categories.category_name → “Name”
    • gl_accounts.account_name → “Name”
    • cost_drivers.driver_name → “Name”
    • package_rankings.ranking_label → “Ranking”
    • approval_actions.action_label → “Action”
    • users.display_name → “Display Name”
    • cost_center_assignments.assignment_label → “Assignment”
  6. Deduplicate against Semantius built-in tables. This model is self-contained and declares users, which exists in Semantius as a built-in. For each declared entity, read Semantius first: if a built-in already covers it, skip the create and reuse the built-in as the reference_table target — do not attempt to recreate. Optionally add the model’s required fields (display_name, is_active, department, job_title) to the built-in only if they are missing (additive, low-risk changes only).
  7. Junction-table label population. Three entities have label fields the caller must populate on insert because they have no natural single-field label: package_rankings.ranking_label, approval_actions.action_label, cost_center_assignments.assignment_label. The implementing application or workflow should compose these from the related records (e.g. "{cycle_name} / {cost_center_code} / #{rank_position} {package_title}" for a ranking).
  8. After creation, spot-check that label_column on each entity resolves to a real field, that all reference_table targets exist, and that the decision_packagesfunding_levels circular reference resolves cleanly in both directions.