Skip to main content

FS Garage Utilisation - Metrics and Calculations

Complete metrics catalog for the Utilisation scorecard: project-wise stream segregation, KPI card formulas, employee utilisation, and project analysis.

Audience: engineers, ops, leadership
Repos: base-fs-garage-fe + fs-garage-base
UI route: /utilization

Code source of truth:

  • BE join + stream segregation: pb_hooks/utilisation.pb.js (streamOf, project maps)
  • BE / FE derived engine: pb_hooks/utilisation_derived.js, lib/utilization/derivedMetrics.js
  • KPI click explanations: lib/utilization/derivedMetricExplain.js

Snapshot field: snapshot.derived with version: 3 after Run Now.


1. How numbers are produced​

1. Publish Garage allocations (person x project x week planned hours)
2. Run Now pulls Timesheet entries for the week
3. Canon project names (TS_PROJECT_MAP) then segregate each row into a stream
4. Build person segments (person x project planned/actual)
5. applyDerivedMetrics() -> badges, streams, projects, KPIs, key actions
6. Store utilisation_snapshots.snapshot JSON
7. FE renders cards (and can enrich older snapshots for display)

Grain of segregation: every planned or logged hour is attributed to a person x project segment, then rolled up to streams, people, and projects.


2. Constants and terminology​

TermMeaning
STD_HOURS (40)Standard week capacity per person
Standard capacityheadcount x 40
Allocated / TEAM CAPACITYsum(employee.planned) from published Garage allocations
LoggedAll Timesheet hours for the person/week (work + leave entries)
Leave hoursLeave detected from Timesheet leave projects / reports
Holiday hoursHours on Holiday projects (own bar bucket; not overhead)
Productive / actualmax(0, logged - leaveHours - holidayHours)
Unloggedmax(0, planned - logged)
CohortPeople with published allocations (TEAM SIZE). Interns are listed separately and excluded from scorecard KPI totals
On-plan band+/- 10% around 100% for leave-adjusted util interpretation
Week windowRun Now blocked for future weeks

What is materiality?​

Materiality is the minimum hour threshold used to decide whether a variance or risk is large enough to flag.

ItemValue
ConstantMATERIALITY_H = 8
Plain meaningOne person-day (about 8 hours in a standard week)
Why it existsSmall noise (1-2h miss) should not flood Key Actions / "material" project flags. Only gaps at least about one full day are treated as actionable.

Where it is used:

  • Project Material flag (P3): abs(actual - planned) >= 8
  • Key Actions named rules: only fire when severity / hours at risk are material (typically >= 8h; some rules use higher bars like 16h)
  • Billable gap company signal: gross bench >= 8h

So if a project is 3h over plan, it is not material. If it is 8h+ over/under, it is material.


3. Project-wise stream segregation​

3.1 Name canonicalization first​

Timesheet project names are mapped onto Garage names via TS_PROJECT_MAP / canonProject() before segregation. Examples:

Timesheet name (examples)Canonical Garage name
ABA, ABA-IndieCommerceABA / IndieCommerce
Thirstie on-demandThirstie on demand
FS Labs, FS-Labs, workshopsFS Labs / R&D
FS-Garage BaseFS-Garage
TATA / HR
Fleet Studio Administration / Operations long namesOperations
HR / Talent long namesTA / HR

If no map hit, the raw name is kept (then rule-based classification may still apply).

3.2 Stream keys​

Every segment lands in exactly one of:

Stream keyUI labelMeaning
billableBillableClient delivery books
fslabsFS Labs / R&DLabs, academy, internal product R&D
hropsHR / OperationsOps, TA/HR, interview, admin
overheadOverheadLeave, learning, GPTW/town hall, onboarding, other

3.3 Explicit project maps (primary segregation)​

These maps win when the canonical project name matches:

Billable (BILLABLE)

  • ABA / IndieCommerce
  • MWE
  • Cocktail Courier
  • Thirstie on demand
  • TIBA
  • Wally SEBI
  • CFO
  • OCEO

FS Labs (FSLABS)

  • FS Labs / R&D
  • FS-Academy
  • FS-Labs-SiftHire
  • Wally
  • Wally Marketing
  • FS-Garage

HR / Ops (HROPS)

  • Operations
  • TA / HR
  • TA

3.4 Rule-based fallback (streamOf)​

If not in the maps above, classification uses name rules (order matters):

  1. Leave project (LEAVE in name) -> overhead
  2. Onboarding -> overhead
  3. Structured / unstructured Learning -> overhead
  4. HR combined bucket (Interview, Fleet admin/ops, names starting with HR, ADMIN, etc.) -> hrops
    (this wins over bare GPTW/TOWN so combined HR projects stay hrops)
  5. Pure GPTW / Town Hall (02-GPTW, GPTW, TOWN) -> overhead
  6. Name contains FS or WALLY -> fslabs
  7. Else -> overhead

Segment flag: segment.billable = true when the project is in the billable map (used for billable planned/actual rollups).

3.5 How planned vs actual are segregated​

SourceWhat happens
Garage allocation rowHours added to person planned and to that project's planned; stream planned totals use streamOf(project)
Timesheet entryHours added to person logged and to segment actual for canonical project; also increment stream actual + billable/labs/hrops/overhead person fields

Person x project segment fields (core grain):

  • project - canonical name
  • planned - Garage allocation hours
  • actual - Timesheet hours on that project
  • billable - boolean from billable map

Important exclusions

  • Intern hours appear in Intern sections but are excluded from main KPI totals
  • Intern Training can sit under overhead stream label historically but is excluded from overheadActual to avoid double-count with its own bar
  • Holiday is its own employee bar bucket (not folded into overhead composition bars)

3.6 Category splits inside streams​

Overhead composition categories (classifyOverheadCat):

  • Leave
  • Holiday
  • Onboarding
  • Learning - Structured / Learning - Unstructured (or raw Timesheet name when present)
  • GPTW / Town Hall
  • Other

HR / Ops categories (classifyHropsCat):

  • Interview (Panelists and HM)
  • IT Administration
  • Fleet Studio Ops
  • HR / Other

4. Primary KPI cards (top matrix)​

These are the main scorecard KPI tiles (labels match UI / derivedMetricExplain.js).

Metric (UI label)Definition
TEAM SIZE (B1)People in the allocated cohort. Formula: count(employees with published allocations). Same cohort for capacity, streams, and employee util. Interns excluded from main KPI totals.
TEAM CAPACITY (B3)Total planned hours (main capacity figure). Formula: sum(employee.planned). Meta: standard capacity headcount x 40 and coverage% = allocated / standardCapacity x 100.
TOTAL LOGGED (B4)All Timesheet hours for the cohort. Formula: sum(employee.logged). Includes work + leave. Often shows vs allocated: logged / allocated x 100.
BILLABLE HOURS (B7)Hours on billable projects. Formula: sum(employee.billableActual). Share of logged: billableActual / logged x 100.
FS LABS / R&D (S-LABS)Labs / R&D stream hours. Formula: sum(fslabs stream actual). Often compared to billable: labs / billableActual x 100.
HR / OPERATIONS (S-HROPS)HR/Ops stream hours. Formula: sum(hrops stream actual). Interview, IT admin, Fleet ops, related.
OVERHEAD (S-OH)Overhead stream hours. Formula: sum(overhead stream actual). Should match Overhead Composition total (learning / onboarding / GPTW / leave-project style load as classified).
INTERN TEAM SIZE (INT1)Interns in Timesheet weekly report. Formula: count(interns in reports/filter for week). Intern hours shown separately; not in other KPI totals.

5. Derived insight KPI cards​

Clickable cards; drawer shows meaning, formula, and this-week computation.

Metric (UI label)Definition
BILLABLE UTIL % (K2)Share of allocated hours that went to billable work. Formula: billableActual / allocated x 100. Denominator is TEAM CAPACITY (Garage plan), not logged.
EFFECTIVE BILLABLE % (K3)Billable vs capacity after leave. Formula: billableActual / (standardCapacity - leave) x 100. Leave-adjusted capacity denominator.
HOURLY BILLABLE CONVERSION (K4)Of productive hours by people allocated to included books, how much was billable. Formula: sum(billableActual) / sum(productive) over eligible staff x 100. Eligible = people with planned hours on included project list (default TIBA). Productive = leave/holiday-net actual.
BILLABLE / FTE (K5)Average billable hours per person. Formula: billableActual / headcount. Result is hours, not %.
DELIVERY ATTAINMENT (K6)Billable delivery vs billable plan. Formula: billableActual / billablePlan x 100. 100% means billable actual matched billable allocation.
OVERHEAD RATIO (K7)Non-delivery load share of logged. Formula: (hropsHours + overheadHours) / logged x 100.
UNBASELINED (K8)Share of logged hours with no allocation on that segment. Formula: unbaselinedHours / logged x 100, where unbaselined hours = sum(segment.actual where planned = 0).
PLAN DEVIATION (K9)Average % miss vs plan (MAPE). Lower is better. Formula: average( abs(logged - planned) / planned ) x 100. Only people with planned > 0.
ON-PLAN RATE (K10)Share of people with ON PLAN badge. Formula: count(onPlan badges) / headcount x 100. Badge uses productive vs planned (see employee section).
R&D INTENSITY (R1)Labs relative to billable. Formula: labsActual / billableActual x 100. Not share of total logged.
DISPLACEMENT (R4)Non-billable load on people who had a billable plan. Formula: sum(learning + overhead_net + max(0, labsActual - labsPlanned)) for people with billablePlanned > 0. Hours that may displace billable work.
ALLOCATION DRIFT (K14)Hours logged with no allocation for that person/project. Formula: sum(project.driftHours) from contributors with actual > 0 and planned = 0.

6. Employee utilisation​

6.1 Per-employee fields​

FieldMeaning / formula
plannedGarage planned hours = sum of allocation segments
loggedTimesheet hours = sum of entries for person/week
leaveHoursLeave from leave projects / report
holidayHoursHoliday project hours
actual / productivemax(0, logged - leaveHours - holidayHours)
unloggedHoursmax(0, planned - logged)
billablePlanned / billableActualBillable stream hours (segments with billable=true, or stored fields)
labsPlanned / labsActualLabs stream hours
hropsPlanned / hropsActualHR/Ops stream hours
learningHoursLearning classification hours
overheadActualOverhead load after folding learning/GPTW/onboarding style projects
benchHoursmax(0, billablePlanned - billableActual)
utilizationPctproductive / planned x 100 (if planned > 0)
adjUtilPctLeave-adjusted util % (see below)
segments[]Person x project rows: { project, planned, actual, billable }

6.2 Leave-adjusted utilisation % (stored)​

adj_denom = planned - leaveHours
adjUtil% = productive / adj_denom x 100
(null if adj_denom <= 0)

Band for leave-adjusted interpretation: ON PLAN if within 100 +/- 10, else below/above.

6.3 Employee badge (what the UI shows)​

Current FE/BE enrich uses plan vs productive for the badge (not the +/-10% leave-adjusted band):

ConditionBadge
productive > plannedABOVE PLAN
productive < plannedBELOW PLAN
productive = plannedON PLAN
both zeroN/A / neutral

Status meta typically: {productive}h productive.

Missing timesheet icon when unloggedHours > 0.

6.4 Employee bar / stream stacks​

Person bars segregate hours by project/stream after folding:

  • Overhead-fold projects (structured/unstructured learning, onboarding, GPTW/town hall, bare Overhead) roll into overhead totals and are not shown as separate delivery project bars
  • HR combined projects stay visible as HR/Ops segments
  • Holiday and Intern Training have special handling so they are not double-counted in overhead

7. Project analysis​

Projects are enriched from all person segments with matching project name, then sorted by absolute variance hours (not %).

MetricMeaning / formula
Planned / ActualProject planned hours / project actual hours
P1 Variance hoursactual - planned
Variance %variance / planned x 100 (if planned > 0; else 100 if actual only)
P2 Attainment %actual / planned x 100 (null if no plan)
P3 Materialabs(variance) >= 8 (materiality = one person-day; see section 2)
P10 ContributorsDistinct people with actual > 0 (or planned/actual touch)
P11 Concentration %max(person actual) / project actual x 100
Concentration lead(s)People tied for max hours on the project
P12 Bus factorSmallest k such that top-k people cover >= 50% of project actual
Top contributorsTop 3 by hours (ties extended)
P13 Delivery stateSee table below
P14 Zero-loggersPeople with planned > 0 and actual = 0 on that project
P15 Drift contributorsPeople with actual > 0 and planned = 0 on that project
Drift hoursSum of drift contributor actuals

Delivery state (P13)​

ConditionState
planned > 0 and actual = 0NOT STARTED
no plan and actual > 0UNBASELINED
no plan and no actualN/A
actual / planned < 0.25STALLED
0.25 <= ratio < 0.85UNDER-DELIVERED
ratio > 1.1OVER-DELIVERED
otherwise (near plan)ON PLAN

Status color key: stalled / under / not started -> belowPlan; over -> abovePlan; on plan -> onPlan.


8. Stream mix analysis (S1-S7)​

For each stream (billable, fslabs, hrops, overhead):

MetricFormula
S1 Attainment %actual / planned x 100 (null if no plan)
S2 Actual share %actual / logged x 100
S3 Planned share %planned / allocated x 100
S4 Mix drift (pts)S2 - S3
S5 Target bandCompare S2 to band below
S6 Variance hoursactual - planned
S7 BaselineIf planned = 0 -> no baseline / unbaselined style label with actual hours

Target bands (actual share of logged)​

StreamBand
Billable60-100%
FS Labs15-20%
HR / Ops0-15%
Overhead0-8%

Band status: BELOW BAND / IN BAND / ABOVE BAND.

Badge style copy: {attainment}% OF PLAN · {+/- mixDrift}pts mix

  • % of plan = delivery of allocated hours for that stream
  • mix pts = whether the stream's share of the week matched the plan shape

9. Billable gap attribution​

Company totals​

grossBench   = sum max(0, billablePlanned_i - billableActual_i)
overdelivery = sum max(0, billableActual_i - billablePlanned_i)
netGap = sum (billableActual_i - billablePlanned_i)

Per-person attribution (priority order)​

For each person with billable bench > 0, remaining balance is consumed in order:

  1. leave (leaveHours)
  2. unlogged (unloggedHours)
  3. learning (learningHours)
  4. labs overrun max(0, labsActual - labsPlanned)
  5. residual

Company attribution = sum of those buckets. Shown in Billable Gap panel + Key Action BILLABLE_GAP.


10. Leave summary​

IDMetricFormula
L1Absence rateleaveTotal / standardCapacity x 100
L2Planning complianceplannedLeave / leaveTotal x 100
L3Unplanned hoursleave rows typed UNPLANNED
L5Concentrationmax(person leave) / leaveTotal x 100

11. Reconciliation​

loggedDelta   = org_wide_logged - cohort_logged
billableDelta = org_wide_billable - cohort_billable

Non-zero deltas surface as banners / DATA_RECONCILIATION key action.


12. Key Actions (hours at risk)​

Severity = hours (no money / bill rate).

Company signals (pinned)​

RuleWhenSeverity
DATA_RECONCILIATIONabs logged/billable delta > 0sum of abs deltas
MIX_DRIFTbillable abs(mixDriftPts) > 5abs(mix)/100 x logged
UNLOGGED_CAPACITYtotal unlogged >= 40hunlogged hours
BILLABLE_GAPgross bench >= 8hgross bench

Named actions (top by hours, materiality >= 8h)​

RuleWhenSeverity
ZERO_LOGGERplanned > 0, logged = 0planned hours
STALLED_PROJECTdelivery state STALLEDplanned - actual
BILLABLE_SHORTFALLattainment < 85%, abs(var) >= 16, not stalledabs(variance)
BENCH_CONCENTRATIONperson bench >= 16hbench hours
ALLOCATION_DRIFTproject drift >= 8hdrift hours
BUS_FACTORbus factor = 1 and actual >= 8hproject actual

13. Quick formula cheat sheet​

coverage%            = allocated / (headcount x 40) x 100
productive = max(0, logged - leave - holiday)
unlogged = max(0, planned - logged)
utilisation_headline = logged / allocated x 100 (TOTAL LOGGED vs TEAM CAPACITY)
billable_util% = billableActual / allocated x 100 (K2)
effective_billable% = billableActual / (capacity - leave) x 100 (K3)
conversion% = billableActual_elig / productive_elig x 100 (K4, TIBA cohort by default)
delivery_attainment% = billableActual / billablePlan x 100 (K6)
project_variance = actual - planned
project_attainment% = actual / planned x 100
stream_mix_drift_pts = (actual/logged x 100) - (planned/allocated x 100)

14. How to verify​

  1. Publish allocations for the week.
  2. Run Now (ceo / c-suite) -> derived.version = 3.
  3. Open /utilization and click KPI cards - drawer text comes from derivedMetricExplain.js.
  4. Spot-check one person: segments should sum to planned/logged; streams should match streamOf(project).
  5. Spot-check one project: contributors / zero-loggers / drift should match person segments.

15. Code map​

ConcernLocation
Project maps + streamOf segregationfs-garage-base pb_hooks/utilisation.pb.js
Derived metrics applyfs-garage-base pb_hooks/utilisation_derived.js
FE enrich / project / employeebase-fs-garage-fe lib/utilization/derivedMetrics.js
KPI card meanings + formulasbase-fs-garage-fe lib/utilization/derivedMetricExplain.js
Hourly conversion project listlib/utilization/hourlyBillableConversion.js (default TIBA)
UI sectionscomponents/utilization/* (KpiCard, DerivedInsights, StreamMix, ProjectVariance, EmployeeUtilisation, KeyActions, Leave)