HOCO B2B

Planning method

How effort was estimated, how the scheduler turns it into dates, and how to change the plan.

Effort

Every task is estimated in person-days of focused work, twice:

ColumnMeaning
ClassicFrom scratch — no reusable internal code, no AI assistance. Kept for comparison only.
OptimizedThis team: reusing what the team already built on previous projects (UI kit patterns, RBAC, queues, storage, PDF, e-mail and notification modules, CI and Docker recipes), with AI-assisted development. Only this number is scheduled.

Sizes are a quick read of the optimized effort: XS ≤ 0.5 d · S ≤ 1.5 d · M ≤ 4 d · L ≤ 10 d.

What has no precedent barely moves between the two columns: the offline-first delegate app, the GPS and tour logic, the client workshops, the pilot and — above all — the design work, which is priced at Akram's own estimate.

Akram's 21 days are read as 21 working days (Sunday–Thursday). If he meant three calendar weeks, set the effort of DES-01…DES-21 to 0.7 in tasks.json and every date recomputes.

Scheduling

The scheduler (apps/docs/src/lib/plan/schedule-core.mjs) is the only thing that produces dates:

  1. Working days. Each person gets a stream of the days they can work: Sunday–Thursday from the plan start, minus public holidays (settings.holidays), minus their absences.
  2. Lanes. A task belongs to the first person of its team. A person's lane is walked by phase (release: P0 → P4) then by order.
  3. Start. A task starts after its explicit deps and after the previous task of its lane.
  4. Duration. optimized ÷ FTE working days, kept fractional — two half-day tasks share one day instead of taking two.
  5. Pinned starts. A locked task with pinnedStart cannot start earlier than that date.
  6. Done tasks sit on their doneOn date and consume no capacity.
  7. Phases and milestones are derived: a phase spans the earliest start to the latest end of its open tasks; a milestone is reached when the last task in its after list ends.

Co-responsible people (the rest of team) appear on the task brief but do not consume their own lane — when a contribution is large it is a separate task in their lane.

Changing the plan

To…Change in apps/docs/content/plan/tasks.json
Re-estimateoptimized (and effort)
Reassignteam (first entry owns the lane)
Re-sequenceorder within the lane, or deps
Move to another phaserelease
Mark progressstatus: "done" + doneOn
Add an absence or a holidaypeople[].unavailable or settings.holidays
Change capacitypeople[].fte

Then:

pnpm --filter @hoco/docs schedule          # print the new calendar, warns on unknown dependencies
pnpm --filter @hoco/docs generate          # regenerate the team-task briefs
pnpm --filter @hoco/docs check-mdx         # make sure every page still compiles

Commit the plan change in its own commit (docs(plan): …) so the history of the plan is readable.

What the method deliberately does not do

It does not invent capacity. If a date is wrong, the levers are real: move scope to another phase, change who owns a task, change an FTE, add a person, or change the estimate itself — and say why in the commit. There is no setting that makes the same work take less time.

It does not model part-time contributions inside one lane (a two-week pilot is planned as ten days of each developer's lane because they are on call throughout).

It does not hide buffers. Gaps in a lane are visible on the timeline; they are the first capacity to use when something slips.

On this page