Skip to content

RFC: Forward Capacity & Effort Forecasting

Answer "who has room next month?" from data we already have — task assignments, estimates, and capacity — without building a booking engine.

Owner: Ben TurnerCreated: 2026-08-14

💬 Feedback wanted — this is a draft. A GitHub Discussion will be opened for it; until then, comment on the PR that introduced this document. Open questions are flagged in Risks & open questions.

TL;DR

🔭 The gap

Utilization only looks backward.

Time tracking can say who was over-loaded last month. It cannot say who has room for the onboarding that signs next week — the question every delivery lead actually asks.

📐 The move

Project the data we already have.

Assigned tasks carry estimates and due dates; people carry weekly capacity and time off. Spreading each task's remaining estimated minutes across its remaining working days yields a per-person, per-week forecast — no new data entry, no scheduler.

🪜 The path

Read-only first, allocation later.

Phase 1 is a forecast report beside the existing Utilization report. Phase 2 adds lightweight planned-hours overrides. A booking engine is explicitly out of scope.

Motivation

🧱 The unanswerable question

Staffing a new onboarding today means opening the Utilization report (history), the project list, and a spreadsheet. Nothing in the product projects load forward, so assignment decisions run on memory and hallway knowledge.

🐌 Late overload detection

An over-committed person surfaces only after the weeks are logged — as a red row in a retrospective heatmap. By then the burnout weeks already happened and the affected projects already slipped.

🚧 The competitive floor

Every PSA and resource-management product adjacent to us (Rocketlane, Kantata, Float, Runn, Scoro) leads with forward capacity — allocations, placeholders, forecasted utilization. Buyers coming from those tools treat "who has room?" as table stakes; we answer only "who had room?".

Proposal

What already exists

The forecast needs four inputs, and all four are live today:

InputWhere it lives
Who is meant to do the workTask assignee on every project task
How big the work istime_allotted_minutes per task, inherited from the playbook template
How much of it is already doneTime entries per task (remaining = allotted − worked)
How much room a person hasWeekly capacity per person (or the org default), minus time off, from the availability model

Phase 1 composes these into a projection. It introduces no new tables and no new data entry.

The projection

For each person, for each of the next N weeks (default 8, capped at 12):

  1. Take their open assigned tasks that carry an estimate.
  2. For each task, compute remaining minutes = allotted − worked so far, floored at zero (an already-blown estimate contributes nothing forward — the overrun is the variance report's story).
  3. Spread those remaining minutes evenly across the task's remaining working days, from max(today, task start) to its due date. A task past due but unfinished lands entirely in the current week — late work is this week's load, not a fiction spread into the past.
  4. Sum per week, and divide by that week's capacity (weekly capacity pro-rated by availability, exactly as the retrospective utilization report already computes it).
mermaid
flowchart LR
    A[Assigned tasks<br/>with estimates] --> C[Remaining minutes<br/>allotted - worked]
    B[Task date range<br/>start to due] --> D[Spread across<br/>remaining working days]
    C --> D
    D --> E[Projected minutes<br/>per person per week]
    F[Capacity minus<br/>time off] --> G[Forecast ratio<br/>per person per week]
    E --> G

The even spread is deliberately simple. It is the same model GuideCX and Teamwork use for planned load, it needs no per-day scheduling input, and it is honest about what it is: a projection from estimates, not a commitment calendar.

Honesty rules — carried over, not invented

The retrospective reports already enforce rules that keep their numbers defensible, and the forecast inherits every one:

  • An unset estimate never fabricates load. Unestimated assigned tasks are excluded from the projection and surfaced as a per-person count ("6 assigned tasks carry no estimate"), the same way the variance report keeps a not_estimated bucket. The forecast's own completeness is part of the report.
  • Missing capacity never fabricates a ratio. A person with no capacity shows projected minutes but no percentage, and is excluded from team aggregates — identical to the utilization report.
  • Time off reduces the denominator. A week with approved days off owes proportionally less, through the same availability service every other report uses.
  • A task without a due date cannot be spread. It is excluded and counted, next to the unestimated count, rather than silently dropped or arbitrarily placed.

Current vs proposed

A seventh report, Forward capacity, sits beside Utilization: the same person × period heatmap, shaded by projected load for the next 8 weeks, with the same drill-through — click a hot cell and see exactly which tasks put that person over. "Who has room in September?" is a glance; "what would moving this project buy us?" is a follow-up filter. The existing utilization heatmap component, filter contract, and drill pattern are reused wholesale.

Surfaces

  • Report: Insights → Time → Forward capacity — person × week heatmap, over-capacity flagged with the same alert strip semantics as utilization (over 100% projected, no capacity set, unestimated/undated work counts). Per-cell hovercard decomposes the projection into contributing tasks.
  • API: GET /time-tracking/reports/forecast behind the existing _REPORT_ROLES gate and shared filter contract (people, accounts, projects, playbooks all narrow it).
  • Project staffing check: the project Time tab gains a one-line signal per assignee — "adding this person's remaining estimates puts them at 118% in the week of Sep 7" — computed from the same endpoint, no new math.

Phasing

PhaseScopeNew data model
1 — Forecast (this RFC)The report, API, and project staffing signal described aboveNone
2 — Planned-hours overridesLet a lead override the even spread for one assignment ("front-load this to 20h next week"); overrides become the spread where presentOne table: per-assignment weekly planned minutes
3 — Aero staffing suggestions"Find room": Aero proposes assignees for an incoming project from forecasted headroom, the way it already proposes template estimate updates from variance historyNone beyond phase 2

Phase 1 ships value alone; phases 2–3 are sequenced options, not commitments.

Alternatives considered

  • A full resource-management module (allocations, bookings, placeholders, drag-and-drop scheduling — the Kantata/Float/Runn shape). Rejected for now: it is a second product's worth of surface area, it demands a planning discipline our customers' onboarding teams largely do not run, and every input it needs beyond phase 2 is speculative until the forecast proves demand.
  • Integrate an external RM tool instead of building. Rejected: the inputs (assignments, estimates, worked time, capacity) are our own domain data; exporting them to price in a second vendor for one report inverts the build/buy logic.
  • Do nothing and keep utilization retrospective. Rejected: it is the single largest capability gap against every adjacent competitor, and the cheapest phase closes most of the distance.

Risks & open questions

HighForecast quality tracks estimate coverage

An org that estimates 20% of its tasks gets a forecast describing 20% of its load, and may read it as the whole picture.

Mitigation: the unestimated/undated counts render inside the report (not a footnote), the alert strip calls out low coverage, and the playbook variance report already gives orgs the loop that improves estimates over time.

MedEven spread misreads lumpy work

A 40-hour task due in eight weeks projects as 5h/week; the real work may be one brutal week. Cells will sometimes under- or over-warn.

Mitigation: acceptable for a projection and stated in the report's own description; phase 2's overrides exist precisely for the assignments where the spread is wrong enough to matter.

MedDate hygiene becomes load-bearing

Tasks with stale due dates pile projected load into the current week.

Mitigation: past-due load is visually separated ("overdue backlog" vs projected flow) so a hygiene problem reads as one, and the data-quality report grows an undated/past-due tasks section.

LowQuery cost

The projection is a per-request aggregation over assigned open tasks and their entries.

Mitigation: bounded horizon (12 weeks), existing indexes on assignee and task dates, and the same batched availability lookups the utilization report uses; no materialization until measured need.

Open questions

Do subtask-less "general project time" patterns need a forecast analog? Meetings and coordination are real future load but carry no task estimates. An org-level overhead percentage per person is one option; ignoring it (and saying so) is another. If we ever read real calendar data for this, OAuth (Google / Microsoft) is the only acceptable integration path.

Where does the staffing signal live in the project flow? The Time tab is the phase-1 answer, but assignment happens in the task grid and builder — surfacing headroom at assignment time may be the higher-leverage placement.

Should tentative work enter the forecast? Runn's tentative toggle and Float's tentative allocations exist because pipeline load changes staffing answers. Our nearest analog is projects created from unsigned deals via Workflows — including them behind a toggle is cheap once the report exists, but the definition of "tentative" needs a product decision.

Internal documentation — gated behind Cloudflare Access.