45 KiB
Product Roadmap And Status
Date: 2026-03-13 Purpose: Canonical source for active roadmap, open gaps, and document ownership.
Canonical Documents
- Active product and refactor backlog: this file
- Estimating system design and workbook mapping: estimating-extension-design.md
- Dispo clean-slate import design and field mapping: dispo-import-implementation.md
- Dispo worker ticket pack and dependency breakdown: dispo-import-implementation-tickets.md
- Demand/assignment migration cutover and readiness policy: demand-assignment-migration-cutover.md
- Strategic longer-horizon architecture direction: v2-architecture-proposal-2026-03-11.md
- Implementation history and decisions: LEARNINGS.md
Older plans and reviews were left in place only as archive notes so active guidance is no longer split across stale task lists.
Current Baseline
The following items were proposed in older markdown files and are already implemented enough that they should not stay on the active backlog:
- Argon2-based user creation and login compatibility
- Manager-restricted notification creation
- Resource detail surface
- Timeline virtualization
- Excel import and export support
- Skill-matrix import flow, AI summary generation, and related resource metadata
- View preference groundwork such as shared column/view preference types and hooks
- Shared dynamic-field filter and blueprint-validation parity across project and resource APIs
- Blueprints list parity with shared sort, selection, and view-preference behavior
- Shared dashboard widget contracts, layout normalization/migration, and registry-driven rendering
Active Workstreams
| Workstream | Status | Why It Is Still Open | Recommended Next Step |
|---|---|---|---|
| Estimating system | Complete |
Full CRUD, versioning, export, planning handoff, clone/template, rate cards per client, richer version comparison, scope-to-effort rule engine, experience multipliers & shoring ratios, weekly phasing (4Dispo grid), and structured commercial terms. | — |
| Demand vs assignment split | Complete |
Legacy Allocation table dropped. legacyAllocationId columns removed. Migration tooling deleted. Compatibility facades renamed to clean domain names (updateAllocationEntry, deleteAllocationEntry, fillOpenDemand, loadAllocationEntry). isPlaceholder is a derived read-model property. No legacy compatibility naming remains. |
— |
| Widget platform refactor | Implemented |
Widget config typing, layout normalization, registry-driven rendering, and dashboard query extraction now live behind shared/application contracts instead of ad hoc router logic. | Keep future widgets on the same registry + application-use-case pattern. |
| Package-level regression tests | Expanded |
Shared schema validation (rate-card, allocation, estimate — 32 tests), engine vacation/recurrence (29 tests), staffing capacity-analyzer (12 tests), plus existing application and dashboard tests. | Continue adding API-level integration tests for remaining router procedures. |
| Chargeability and resource planning (Dispo v2) | Complete |
Plans 1-5 (schema, types, SAH engine, 5 API routers, 5 admin UIs, resource/project modal extensions), Phase A (live forecast report), Phase B (target comparison + drill-down grouping), Phase D (Excel/CSV export). Phase C (SAP actuals) removed from scope. | — |
Prioritized Backlog
1. Estimating foundation
Build the estimating bounded context first. It is the largest product gap and the most structurally separate from the existing planning workflow.
Scope:
- Prisma models for estimate aggregates
- typed calculation engine in
packages/engine - estimate wizard and estimate workspace UI
- rate-card and resource snapshot handling
- export serializers
Current state:
- completed enough to use as foundation:
- estimating Prisma schema
- shared estimate types and schemas
- engine summary helper
- application-layer estimate use-cases
- API estimate router registration
- estimates list route and first create wizard
- completed enough to use as foundation:
- estimate workspace detail route with tabbed read surfaces
- working-draft editor for overview, assumptions, scope, and staffing
- live resource-linked staffing rows with snapshot persistence on save
- completed in the current iteration:
- server-side demand-line normalization before metrics
- persisted live-vs-manual rate override metadata on demand lines
- read/write workspace visibility for manual overrides vs live resource snapshots
- completed in the current iteration:
- approved-version planning handoff into downstream allocations
- estimate project snapshot persistence for approval and handoff context
- completed in the current iteration:
- format-specific export serializers with downloadable stored artifacts and workspace previews
- completed in the current iteration:
- estimate clone/template feature across all layers (shared schema, application use-case, API router, UI)
- rate cards per client with admin management UI
- richer version comparison engine: per-item scope diffs with field-level change detection, resource snapshot rate/location diffs, chapter-grouped subtotals sorted by cost impact, margin % delta
- version comparison UI: scope item detail table, resource rate diff table, chapter subtotals section, margin summary card
- scope-to-effort rule engine: admin CRUD for rule sets (per_frame/per_item/flat unit modes), estimate workspace preview + apply with replace/append mode, engine with 16 tests
- experience multipliers & shoring ratios: hierarchical specificity matching (chapter/location/level), rate multipliers, shoring ratio + additional effort factor, admin CRUD, estimate workspace preview + apply, engine with 23 tests
- weekly phasing (4Dispo grid): ISO week-based phasing with even/front-loaded/back-loaded patterns, chapter aggregation view, heat-map coloring, estimate workspace phasing tab, engine with 30 tests
- structured commercial terms: pricing model (fixed/T&M/hybrid), contingency %, discount %, payment term days, warranty months, payment milestones with validation and amount computation, integrated into financials tab, engine with 19 tests
2. Refactor the planning core before v2 migration
The best low-risk internal work is the refactor slice that reduces duplication without forcing a full relational migration yet.
Scope:
- shared dynamic-field validation/filter path
- typed widget config and layout migration
- package-level regression tests
3. Prepare the demand/assignment model split
This should follow the refactor slice, not precede it. The current Allocation model is workable short-term but is the main domain-model constraint for v2 planning and estimating integration.
Current incremental slice:
- completed in the current iteration:
- shared
DemandRequirementandAssignmentcontracts - application adapter that splits mixed allocation rows into explicit read models
- additive timeline/allocation API read endpoints and project-context exposure for the new split
- migrate primary timeline and allocations UI surfaces onto explicit
demands/assignmentsread models - add demand-aware allocations page visibility alongside assignment-first tables
- shared
- completed in the current iteration:
- manual create flows now write through
createDemandRequirement/createAssignmentinstead of the mixedallocation.createpayload where the UI already has an explicit intent - open-demand fill flows now route through a single compatibility-aware backend command so the UI no longer resolves linked demand records or branches between demand and placeholder fill mutations
- manual create flows now write through
- completed in the current iteration:
- centralize create-side legacy-allocation compatibility wiring in application use-cases so allocation create, explicit demand/assignment create, and timeline quick-assign share the same dual-write path
- centralize split-record creation from compatibility allocations behind shared application helpers so demand-only, assignment-only, and estimate-handoff create facades no longer each hand-wire
legacyAllocationIdpropagation - centralize the remaining compatibility-create orchestration behind a shared legacy-allocation bundle helper so demand-only, assignment-only, and combined compatibility creates no longer duplicate the "create legacy allocation, then materialize split rows" sequence
- completed in the current iteration:
- centralize estimate planning-handoff compatibility creation in shared application helpers so resource-backed and fallback-placeholder handoff lines reuse the same demand/assignment dual-write paths as the rest of planning
- completed in the current iteration:
- migrate the resource-detail surface off legacy mixed allocation reads onto assignment-only read models
- completed in the current iteration:
- align allocation report/export generation with assignment read-model semantics instead of direct placeholder filtering
- completed in the current iteration:
- migrate dashboard demand queries onto demand-requirement and assignment semantics, with legacy allocation and
staffingReqsfallback during the compatibility period - centralize dashboard planning demand loading behind a shared split read-model helper so project demand rollups no longer hand-normalize mixed allocation rows and can derive project metadata from explicit demand/assignment relations during the compatibility window
- migrate dashboard demand queries onto demand-requirement and assignment semantics, with legacy allocation and
- completed in the current iteration:
- migrate resource and dashboard utilization metrics onto assignment-first booking reads with legacy allocation fallback
- completed in the current iteration:
- migrate staffing suggestion, utilization-analysis, and capacity-window APIs off direct
resource.allocationsreads onto the shared assignment-first booking helper
- migrate staffing suggestion, utilization-analysis, and capacity-window APIs off direct
- completed in the current iteration:
- migrate project detail compatibility reads and project cost rollups off direct
project.allocationsqueries onto split read models and assignment-first booking reads with legacy fallback
- migrate project detail compatibility reads and project cost rollups off direct
- completed in the current iteration:
- remove unused legacy allocation hydration from
resource.getByIdand move timeline budget-status calculations onto assignment-first booking reads with legacy fallback
- remove unused legacy allocation hydration from
- completed in the current iteration:
- keep timeline quick-assign, inline edit, and project-shift mutations in sync with linked demand/assignment rows during the compatibility period, while falling back to legacy allocation sync for intentionally dual-linked estimate handoff rows
- completed in the current iteration:
- keep legacy allocation delete flows in sync with linked demand/assignment rows and move allocation edit flows onto explicit demand/assignment update commands where linked rows already exist
- collapse the main allocation edit modal onto the compatibility update facade so the frontend no longer resolves linked demand or assignment ids before submitting edits
- completed in the current iteration:
- centralize demand-requirement and assignment update compatibility sync in application use-cases so dedicated API procedures, legacy allocation edit facades, and bulk status updates share the same write path
- completed in the current iteration:
- centralize legacy-allocation link resolution in shared application code so allocation and timeline compatibility facades use the same exact-one-link vs dual-link decision rule
- completed in the current iteration:
- centralize compatibility-aware legacy allocation update routing in an application use-case so allocation edit, bulk status, timeline inline edit, and project-shift mutations share the same exact-one-link vs dual-link fallback behavior
- completed in the current iteration:
- move timeline
getEntriesViewandgetProjectContextread paths to demand/assignment-first responses, while preserving legacy allocation fallback and compatibility ids for drag/edit flows
- move timeline
- completed in the current iteration:
- move timeline drag-context cross-project resource bookings onto the shared assignment-first booking helper, and allow that helper to serve unbounded resource-context reads while preserving legacy allocation fallback
- completed in the current iteration:
- update the project detail page to present explicit assignments and open demands instead of relying on the compatibility
project.allocationsview
- update the project detail page to present explicit assignments and open demands instead of relying on the compatibility
- completed in the current iteration:
- route the legacy
allocation.listandtimeline.getEntriesfacades through the same demand/assignment-first read-model loaders aslistViewandgetEntriesView, so compatibility endpoints no longer hand-shape raw allocation query results
- route the legacy
- completed in the current iteration:
- centralize project-scoped planning reads behind a shared project/timeline loader so
project.getById,timeline.getProjectContext, and project-shift preparation all build from the same demand/assignment-first source - add focused API regression coverage for the shared project-planning loader, including active-only filtering and explicit-plus-legacy fallback shaping
- centralize project-scoped planning reads behind a shared project/timeline loader so
- completed in the current iteration:
- move dashboard overview budget utilization onto the shared assignment-first booking helper so linked legacy allocations no longer risk double-counting staffing cost in the top-level summary cards
- completed in the current iteration:
- route the legacy
allocation.fillPlaceholderAPI facade through the same compatibility-aware open-demand fill command asfillOpenDemandByAllocation, while preserving the old response contract for callers still on the legacy endpoint - move project-shift resource booking windows onto the shared assignment-first booking helper so shift validation no longer hand-loads mixed allocation and assignment rows for cross-project overlap checks
- route the legacy
- completed in the current iteration:
- rename primary allocation/timeline UX copy from placeholder language to open-demand and assignment language where the explicit demand/assignment flows already exist
- completed in the current iteration:
- route timeline project-shift preview/apply through a shared demand/assignment-first planning helper, updating explicit demand and assignment rows directly while preserving compatibility fallback for legacy-only rows
- move the allocation modal form state to explicit demand-vs-assignment intent semantics while preserving the legacy allocation edit facade during the migration window
- completed in the current iteration:
- harden legacy allocation update, delete, batch delete, batch status, and timeline inline-edit facades so explicit
DemandRequirementandAssignmentids without a backing legacy allocation row still work through the old endpoints - centralize allocation-facade id resolution in shared application helpers so legacy API surfaces can accept allocation, demand, or assignment ids without duplicating router-side branching
- add regression coverage for explicit-id compatibility across application helpers, allocation router facades, and timeline inline editing
- harden legacy allocation update, delete, batch delete, batch status, and timeline inline-edit facades so explicit
- completed in the current iteration:
- move explicit assignment availability validation onto the shared assignment-booking compatibility loader so conflict checks no longer hand-read mixed legacy allocation rows
- add exclusion support for in-flight compatibility ids in shared booking reads, preventing linked legacy rows from being double-counted during assignment creation
- completed in the current iteration:
- move legacy resource-allocation availability validation onto the shared assignment-booking compatibility loader so old create flows also respect explicit assignment bookings during the migration window
- add regression coverage proving legacy non-placeholder allocation creation now blocks against explicit assignment availability conflicts
- completed in the current iteration:
- move dashboard overview allocation counters off raw legacy
allocation.count()so explicit demand/assignment rows without a backing legacy allocation still appear in top-level planning totals - keep dashboard budget utilization on assignment-first booking reads while aligning the visible overview counts with the split planning read model
- move dashboard overview allocation counters off raw legacy
- completed in the current iteration:
- move project-list and role usage counts onto a shared compatibility-aware planning-entry counter so linked legacy rows are deduplicated against explicit demand/assignment rows in user-facing badges and delete guards
- keep existing
_count.allocationsresponse shapes intact while sourcing those values from the split planning model during the migration window
- completed in the current iteration:
- move estimate planning-handoff duplicate detection onto a shared compatibility-aware planning-entry counter so explicit demand/assignment-only handoff rows block reruns without hand-rolled legacy allocation bookkeeping
- completed in the current iteration:
- add an idempotent
packages/dbbackfill + dry-run rehearsal utility for migrating legacy allocation rows into additiveDemandRequirement/Assignmentrecords without overwriting already-linked explicit rows
- add an idempotent
- completed in the current iteration:
- add a reconciliation audit utility for the demand/assignment persistence split so migration readiness can be measured by pending legacy backfills, explicit non-legacy rows, and legacy-data anomalies before any broader cutover
- completed in the current iteration:
- allow legacy open-demand fill compatibility commands to accept explicit
DemandRequirementids without a backing legacy allocation row - keep the legacy
allocation.fillPlaceholderfacade reload path compatible with split assignment/demand ids by reloading through the shared allocation-facade resolver
- allow legacy open-demand fill compatibility commands to accept explicit
- completed in the current iteration:
- centralize linked demand-fill compatibility behind a shared explicit demand-fill helper so
fillDemandRequirementand the legacyfillPlaceholderfacade now reuse the same assignment-first transaction path - keep explicit demand fulfillment working even when a legacy placeholder link is stale or already missing, so additive split records remain operable during migration cleanup
- centralize linked demand-fill compatibility behind a shared explicit demand-fill helper so
- completed in the current iteration:
- stop explicit
DemandRequirementandAssignmentupdates from broadly mirroring back into legacyAllocationrows, so split persistence is authoritative by default after backfill completion - keep the remaining legacy mirror writes scoped to true legacy-id compatibility updates, preserving old
allocation.updatebehavior without forcing every explicit split edit through the legacy updater - add regression coverage proving live-linked explicit allocation-facade updates stay on explicit split semantics while legacy-only update branches still sync the compatibility row intentionally
- stop explicit
- completed in the current iteration:
- move timeline inline-edit compatibility loading onto the shared allocation-facade resolver so it no longer hand-resolves allocation vs demand vs assignment ids
- keep timeline inline cost recalculation resource loading separate from id-resolution logic, reducing another compatibility-only branch in the router
- completed in the current iteration:
- turn the legacy
allocation.createfacade into an intent-inferred compatibility path so open-demand creation no longer requiresisPlaceholderwhen no resource is supplied, while resource-backed creates still route through assignment dual-write behavior - stop the allocation modal edit flow from sending the legacy
isPlaceholdertoggle back to the compatibility update facade, letting persisted demand-vs-assignment identity stay authoritative during the migration window - add router regression coverage for generic create compatibility on both demand and assignment paths so the remaining facade stays stable while newer explicit commands continue to replace it
- turn the legacy
- completed in the current iteration:
- centralize the generic allocation-create compatibility contract in shared schemas so omitted
isPlaceholdernow means "infer intent from resource presence" across callers instead of only inside the API router - move the low-level legacy
createAllocationuse-case onto the same inferred-demand rule so direct application callers and compatibility wrappers cannot drift from the router behavior - add application regression coverage proving omitted-flag open-demand creation still writes a placeholder-compatible legacy allocation row
- centralize the generic allocation-create compatibility contract in shared schemas so omitted
- completed in the current iteration:
- harden the demand/assignment backfill rehearsal tooling with blocker detection for orphaned explicit legacy links, pending backfills, and invalid staffed legacy rows
- add strict
--fail-on-blockersand machine-readable--jsonmodes to the DB backfill/audit scripts so migration rehearsal can gate automation safely - extend DB regression coverage for metadata-preserving backfill planning, staffed
headcount > 1warnings, and orphaned legacy-link audit states
- completed in the current iteration:
- make explicit demand and assignment updates tolerate stale
legacyAllocationIdvalues when the backingAllocationrow has already been cleaned up, so split-row edits no longer fail during the migration window - centralize backing-legacy-allocation existence checks across update and delete compatibility helpers instead of assuming every split row with a legacy link still has a live legacy record
- add regression coverage for stale-link explicit update flows through the allocation compatibility facade
- make explicit demand and assignment updates tolerate stale
- completed in the current iteration:
- make shared allocation-facade id resolution fall back from missing legacy
Allocationrows to splitDemandRequirementorAssignmentrows bylegacyAllocationId, so stale compatibility ids continue to work after legacy-row cleanup - keep that fallback intentionally strict by refusing to guess when both a demand and an assignment still claim the same stale legacy id
- add regression coverage for stale legacy-id update and delete flows through both the application compatibility helpers and the allocation router facade
- make shared allocation-facade id resolution fall back from missing legacy
- completed in the current iteration:
- self-heal missing
DemandRequirementrows for legacy placeholders before compatibility fills run, so legacy placeholder ids no longer bypass split persistence when backfill has not happened yet - route
fillOpenDemandWithCompatibilityand theallocation.fillPlaceholderfacade through explicit demand-fill semantics after that self-heal step, deleting the backing legacy placeholder row instead of mutating or cloning staffed legacy allocations - keep the low-level
fillPlaceholderhelper on the shared demand-fill compatibility transaction so allocation-shaped responses still remain available for intentional legacy-placeholder compatibility branches
- self-heal missing
- completed in the current iteration:
- add a canonical demand/assignment cutover document with explicit go/no-go criteria, signoff requirements, staged execution, and ambiguity policy
- add a single readiness command that combines audit and dry-run signals, writes deterministic review artifacts, and fails fast when cutover blockers remain
- add focused DB regression coverage for readiness report generation and deterministic artifact naming
- completed in the current iteration:
- rerun the demand/assignment readiness gate against the live workspace and confirm a clean
gobaseline with0pending demand backfills,0pending assignment backfills, and0orphaned legacy links - execute the first real
db:backfill:demand-assignment --applypass on shared data; it completed as a no-op because all132scanned legacy allocations were already represented by additive split rows - refresh the canonical cutover baseline and saved readiness artifacts so follow-up work can focus on compatibility-branch retirement and signoff rather than backlog speculation
- rerun the demand/assignment readiness gate against the live workspace and confirm a clean
- completed in the current iteration:
- make legacy allocation update compatibility self-heal unbackfilled rows by creating a
DemandRequirementorAssignmenton first legacy-only edit instead of leaving the row split-blind indefinitely - keep the old
allocation.updateand batch-status facade behavior intact while ensuring staffed legacy rows still run through assignment availability validation during that backfill-on-update path - add regression coverage for legacy-only placeholder and staffed update flows so split persistence is now exercised even before the bulk backfill runs
- make legacy allocation update compatibility self-heal unbackfilled rows by creating a
- completed in the current iteration:
- keep explicit
DemandRequirementandAssignmentdeletes on explicit persistence semantics even when a backing legacyAllocationrow still exists, instead of routing those deletes through the legacy-removal branch - remove the backing legacy allocation row as a cleanup side effect for those explicit deletes, while leaving surviving split rows addressable through stale compatibility ids during the migration window
- add regression coverage for explicit delete paths with live legacy links so demand deletes detach assignments and assignment deletes no longer risk deleting sibling demand records
- keep explicit
- completed in the current iteration:
- make explicit demand-fill flows sever a linked legacy placeholder instead of continuing to mutate or clone legacy
Allocationrows after the split records already exist - keep legacy-placeholder-id fill compatibility intact, but route direct
DemandRequirementfills onto assignment-first persistence with legacy cleanup as a side effect - add regression coverage for explicit legacy-linked fill flows through both the application use-cases and the allocation router facade
- make explicit demand-fill flows sever a linked legacy placeholder instead of continuing to mutate or clone legacy
- completed in the current iteration:
- centralize compatibility id derivation in a shared helper so allocation read models, booking fallbacks, demand-fill compatibility, dashboard demand loading, and timeline consumers all preserve the same
legacyAllocationId ?? explicitIdrule - centralize stale-legacy fallback partitioning in the shared split-allocation read-model helper so timeline and allocation compatibility reads no longer duplicate the same fallback-legacy branching logic
- add regression coverage for booking fallback compatibility ids and shared split-read fallback partitioning, keeping timeline/router cleanup pinned to application-level helpers
- centralize compatibility id derivation in a shared helper so allocation read models, booking fallbacks, demand-fill compatibility, dashboard demand loading, and timeline consumers all preserve the same
- completed in the current iteration:
- stop the explicit
createDemandRequirementandcreateAssignmentAPI routes from dual-writing legacyAllocationrows by default, so split persistence remains authoritative unless a caller intentionally uses a compatibility facade - move the legacy
allocation.createfacade itself onto split-authoritative demand/assignment creation, preserving intent inference and the allocation-shaped response contract without persisting new legacyAllocationrows - emit create/update allocation SSE events through the shared compatibility-id helper for explicit and legacy-linked split rows alike, and add router regression coverage for explicit create behavior without legacy dual writes
- stop the explicit
- completed in the current iteration:
- isolate the last legacy-placeholder fill mutation path behind a dedicated application helper so explicit
DemandRequirementfills no longer carry inlineallocation.create/allocation.updatebranching - centralize demand-fill progress updates behind a shared application helper so explicit demand fills and the legacy-placeholder branch now decrement or complete demand rows through the same path
- remove the extra assignment reload from the single-headcount legacy-placeholder fill branch because the created assignment already carries the correct compatibility linkage and relations
- retire the dedicated legacy-placeholder fill mutation branch entirely, so both explicit demand fills and
allocation.fillPlaceholdernow route through the same split-authoritative demand-fill command and legacy placeholder rows are always cleaned up as a side effect instead of being mutated in place - move explicit demand-fill legacy-row cleanup onto the shared delete-side compatibility helper so fill transactions no longer carry their own inline
allocation.deletebranch - isolate the create-side allocation compatibility paths behind dedicated legacy-create helpers so demand, assignment, and estimate-handoff compatibility writes no longer piggyback on the generic low-level create entry point
- centralize direct legacy allocation table create/update/delete calls behind a shared record-store helper so compatibility-only write paths now share one low-level mutation surface
- centralize direct legacy allocation compatibility reads behind the same shared record-store helper so placeholder self-heal, facade resolution, and legacy reload flows no longer hand-call
db.allocation.findUnique - centralize true legacy-id mirror payload shaping in shared helpers so intentional
allocation.updatecompatibility writes are clearly separated from split-authoritative demand/assignment updates - isolate the compatibility delete-side legacy removal path behind a dedicated helper so the allocation facade no longer carries inline raw
allocation.deletebranching - move delete-side backing-legacy existence tolerance into the shared existence helper so the compatibility delete path no longer carries its own
allocation.findUniqueshape shim - verify the isolated compatibility branches with focused application/API regression coverage and keep the remaining raw legacy allocation access constrained to the shared record-store helper plus the shared backing-existence helper
- isolate the last legacy-placeholder fill mutation path behind a dedicated application helper so explicit
- completed in the current iteration:
- expose explicit split-row ids alongside compatibility ids in shared planning read models, with allocation-shaped entries carrying
entityIdand demand/assignment entries carrying the real explicit row id insourceAllocationId - move timeline drag, inline edit, open-demand fill, project-panel edit/delete, allocation modal edit, and single-row allocation delete flows onto those explicit ids while preserving compatibility
idvalues for rendering, SSE, and stale-legacy callers
- expose explicit split-row ids alongside compatibility ids in shared planning read models, with allocation-shaped entries carrying
- completed in the current iteration:
- keep the allocations table selection UX keyed to compatibility-facing display ids while routing batch status and batch delete payloads through explicit split-row ids where available
- completed in the current iteration:
- retired the true legacy-id delete surface by routing legacy allocation deletes through split-authoritative
deleteAssignment/deleteDemandRequirementpaths with lazy backfill viaensureLegacyAllocationSplitPersistence - retired the true legacy-id update mirror by routing legacy allocation updates through split-authoritative
updateAssignment/updateDemandRequirementpaths with lazy backfill - removed
isPlaceholderfrom Prisma schema columns, router inputs, and create facade — demand vs. assignment intent is now derived fromresourceIdpresence at read-model build time; the sharedAllocationtype still exposesisPlaceholderas a computed property for frontend consumption - removed legacy allocation reads from
loadAllocationReadModel— now queries only demand/assignment tables, passingallocations: [] - reversed
findAllocationFacadeEntryresolution priority to be split-authoritative (demand/assignment first, legacy allocation last resort) - removed the
fillPlaceholderlegacy facade from the allocation router - cleaned up dead legacy compatibility helpers:
updateLegacyAllocationWithCompatibility,deleteLegacyAllocationWithCompatibility,syncLegacyAllocationUpdate,syncLegacyAllocationRemoval,toLegacyAllocationUpdateData,fillPlaceholderapplication use-case, and their tests - moved
deleteBackingLegacyAllocationIfPresentinto the sharedlegacy-allocation-record-storehelper
- retired the true legacy-id delete surface by routing legacy allocation deletes through split-authoritative
- completed in the current iteration:
- Stage 5: dropped the legacy
Allocationtable from the Prisma schema (132 rows removed) - removed
legacyAllocationIdcolumns and indexes fromDemandRequirementandAssignment - deleted
getAllocationCompatibilityIdhelper and replaced all ~20 call sites with direct entity.idaccess - deleted
getLegacyAllocationLinksand removed legacy-link fallback fromfindAllocationFacadeEntry - simplified SSE event emissions to use entity IDs directly
- deleted migration tooling: backfill, audit, readiness scripts and their tests
- cleaned seed data: removed all
allocation.createandallocation.deleteManycalls - renamed
FillPlaceholderModaltoFillOpenDemandModal - deleted
legacy-allocation-links.test.tsand updated all remaining tests
- Stage 5: dropped the legacy
- completed in the current iteration:
- retired compatibility facades by renaming to clean domain names:
updateAllocationEntry,deleteAllocationEntry,fillOpenDemand,loadAllocationEntry - replaced all
AllocationFacade*type prefixes withAllocationEntry* - removed
compatibilityIdandsourcefields from booking interfaces - renamed
demandRequirementByCompatibilityId→demandRequirementById,existingFacade→resolved - updated test descriptions to remove "compatibility" references
- migration is fully complete — no legacy compatibility naming or debt remains
- retired compatibility facades by renaming to clean domain names:
Delivery Sequence
- Completed: shared dynamic-field parity, blueprint validation parity, Blueprints list parity, and typed dashboard layout/widget migration
- Completed: estimate router procedures, estimates list route, and first wizard shell
- Completed: demand/assignment split persistence — all reads and writes are split-authoritative, legacy compatibility branches retired
- Complete: Dispo v2 — all phases delivered (Phase C/SAP actuals removed from scope).
- Next: broader cross-package regression coverage
- Then: broader cross-package regression coverage
Dispo v2 Parallel Stream
Runs independently of the demand/assignment workstream. Source analysis and detailed plans in samples/Dispov2/plan-*.md.
| Plan | Scope | Depends On | Primary Files |
|---|---|---|---|
| Country/SAH/FTE | Country + MetroCity models, SAH calculator, Spain schedule, FTE scaling | - | schema.prisma, packages/engine/src/sah/, packages/shared/src/types/country.ts |
| OrgUnit Hierarchy | 3-level self-ref OrgUnit tree (L5→L6→L7) | - | schema.prisma, packages/api/src/router/org-unit.ts, apps/web/src/components/org-units/ |
| Utilization Categories | UtilizationCategory model on Projects (Chg, BD, MD&I, M&O, PD&R, Absence) | - | schema.prisma, packages/api/src/router/utilization-category.ts |
| Client/WBS | Self-ref Client tree (Master→Entity), project linking | - | schema.prisma, packages/api/src/router/client.ts, apps/web/src/components/clients/ |
| Resource Extensions | EID attributes, ManagementLevel, ResourceType, derivation rules | Plans 1-4 | schema.prisma, Resource model, ResourceModal.tsx |
| Chargeability Report | Live reporting page, forecast from assignments + SAH, export | All above | packages/engine/src/chargeability/, apps/web/src/app/(app)/reports/ |
Serialization constraint: all plans add to schema.prisma — edits serialized, not parallel.
Demand And Assignment Persistence Split
This is now the highest-value planning-core change. The read side is already separated enough that the next step should be an additive persistence migration instead of more UI churn.
Problem (resolved)
The legacy Allocation table mixed two business concepts: demand (isPlaceholder = true) and assignment (isPlaceholder = false). That overload leaked into creation, fill, estimate handoff, validation, reporting, and planning logic.
This has been fully resolved: the Allocation table has been dropped, and all reads/writes now go through first-class DemandRequirement and Assignment tables. isPlaceholder remains as a derived read-model property for frontend consumption.
Target persistence model
Additive target for the next schema slice:
| New model | Core fields | Purpose |
|---|---|---|
DemandRequirement |
id, projectId, roleId, role, startDate, endDate, hoursPerDay, percentage, headcount, status, metadata |
first-class planning demand |
Assignment |
id, projectId, resourceId, demandRequirementId?, startDate, endDate, hoursPerDay, percentage, dailyCostCents, status, metadata |
actual staffing assignment |
AssignmentRevision later |
assignmentId, changedAt, before, after, reason |
audit-friendly history after the base split lands |
Field mapping from current Allocation
Current Allocation field |
DemandRequirement target | Assignment target | Notes |
|---|---|---|---|
id |
new id on backfill | new id on backfill | keep old allocation id in compatibility metadata during migration |
projectId |
projectId |
projectId |
direct |
resourceId |
not stored except optional suggestedResourceId metadata |
resourceId |
nullable meaning is removed from the main model |
startDate / endDate |
direct | direct | direct |
hoursPerDay |
direct | direct | direct |
percentage |
direct | direct | direct |
role / roleId |
direct | copied from linked demand when useful | preserve text fallback until role normalization is done |
isPlaceholder |
removed | removed | business state moves to model choice, not a flag |
headcount |
direct | removed from assignment | one assignment row always means one staffing record |
dailyCostCents |
derived or zero | direct | demand does not carry execution cost as a required source field |
status |
direct | direct | keep enum initially for compatibility |
metadata |
direct | direct | preserve estimateHandoff, recurrence, and migration provenance |
Delivery phases
- Phase A: additive schema
Add Prisma models for
DemandRequirementandAssignmentwithout removingAllocation. Add nullabledemandRequirementIdonAssignmentonly if the implementation keeps theAssignmentname; if the table name must stayallocationstemporarily, keep a compatibility mapping documented in Prisma comments. - Phase B: dual-write application services
Introduce
defineDemand,assignResource, andfillDemanduse cases inpackages/application. New estimate handoff should createDemandRequirementrows first, then createAssignmentrows only for lines that have a resolvable resource. - Phase C: backfill and compatibility reads — Complete. Backfill ran as a no-op (all rows already split). Backfill/audit/readiness tooling has been deleted.
- Phase D: command/API cutover — Complete.
isPlaceholderremoved from create/update payloads.fillPlaceholderreplaced byfillOpenDemandByAllocation. Compatibility facades renamed to clean domain names. - Phase E: legacy removal — Complete. Legacy
Allocationtable dropped.legacyAllocationIdcolumns removed. All compatibility naming retired.
Use cases to migrate first
| Priority | Scope | Why |
|---|---|---|
P0 |
estimate planning handoff | this is where planning demand now enters the operational domain |
P0 |
manual demand creation and fill flow | direct replacement for placeholder allocation creation/fill |
P1 |
timeline command paths | move/resize/edit semantics should stop branching on placeholder state |
P1 |
timeline command paths and remaining persistence-facing reads | remove the remaining placeholder-specific assumptions after the dashboard/report cleanup |
P2 |
legacy allocation CRUD compatibility | keep only as a migration facade |
Acceptance criteria for the split
- A demand can exist without inventing a fake allocation row.
- Filling one seat of a multi-headcount demand does not mutate assignment semantics.
- Approved estimate handoff writes demand first and assignment second.
- Timeline and allocations pages render from explicit demand and assignment reads with no
isPlaceholderbranching in UI data loading. - Legacy
allocation.listcan remain temporarily, but new features must not requireisPlaceholderinput. - Migration can be run without losing existing allocation history or estimate handoff metadata.
Parallel tickets
| Ticket | Owner | Scope |
|---|---|---|
DAS-01 |
A1-architect |
finalize Prisma target schema, compatibility period, and cutover rules |
DAS-02 |
C1-estimate-backend |
switch estimate planning handoff from placeholder allocations to demand-first writes |
DAS-03 |
C1-estimate-backend |
add demand/assignment application services and additive API procedures |
DAS-04 |
C2-estimate-frontend |
adapt wizard/workspace follow-up UIs and any demand-fill flows to new commands |
DAS-05 |
T1-regression |
backfill tests, compatibility tests, and Docker smoke checks |
Known residual semantic mismatches
All previously listed mismatches have been resolved:
- The legacy
Allocationtable has been dropped; persistence usesDemandRequirementandAssignmentexclusively. - Compatibility facades have been renamed to clean domain names (
updateAllocationEntry,deleteAllocationEntry,fillOpenDemand). isPlaceholderis retained as a derived read-model property (not a stored column) for frontend consumption.
Parallel Delivery Plan
Use a single orchestrator and split roadmap execution into package-owned workstreams. Shared files should be merged only at integration checkpoints.
| Agent | Scope | Primary Files / Packages | Deliverables | Notes |
|---|---|---|---|---|
A1-architect |
keep roadmap, contracts, and merge boundaries coherent | docs/product-roadmap.md, docs/estimating-extension-design.md, shared contract entry points | acceptance criteria, sequencing, shared-file coordination | should not implement feature code unless integration is blocked |
C1-estimate-backend |
estimate domain, router, persistence, exports | packages/api, packages/application, packages/engine, packages/db, packages/shared |
workspace read/write procedures, export serializers, version actions, metrics persistence | owns server-side behavior and cross-package type safety |
C2-estimate-frontend |
estimates pages, wizard follow-up, workspace tabs | apps/web/src/app/(app)/estimates, apps/web/src/components/estimates, shared UI components |
detail workspace, overview/assumptions/scope/rates tabs, iteration UX | should avoid editing backend contracts without handoff |
T1-regression |
tests and runtime verification | packages/*/test*, apps/web verification paths, Docker runtime checks |
regression tests, package typechecks, app smoke validation | runs after each integration checkpoint |
R1-reviewer |
final integration and regression review | diff review across touched packages | findings, missing permissions, backward-compatibility risks | review after C1 and C2 merge |
Active Parallel Slice
Current target: execute the demand/assignment persistence split without blocking estimate usage that already works.
| Ticket | Owner | Depends On | Scope |
|---|---|---|---|
DAS-01 Additive Prisma schema and compatibility rules |
A1-architect + C1-estimate-backend |
current read-model split | define DemandRequirement / Assignment tables, migration comments, and cutover constraints |
DAS-02 Demand-first planning handoff |
C1-estimate-backend |
DAS-01 |
change approved estimate handoff to create demand first, assignment second |
DAS-03 Demand/assignment command surface |
C1-estimate-backend |
DAS-01 |
add demand create/fill procedures and compatibility wrappers for legacy allocation flows |
DAS-04 Frontend follow-up flows |
C2-estimate-frontend |
DAS-02, DAS-03 |
completed for the main allocation, timeline, and estimate handoff surfaces; continue only for residual legacy copy |
DAS-05 Regression and migration net |
T1-regression |
DAS-01 to DAS-04 checkpoints |
backfill tests, contract tests, Docker smoke validation, and migration rehearsal |
Merge Boundaries
- serialize edits to:
packages/db/prisma/schema.prismapackages/shared/src/types/allocation.tspackages/shared/src/schemas/allocation.schema.tspackages/api/src/router/index.tspackages/api/src/router/allocation.tspackages/application/src/use-cases/estimate/create-planning-handoff.ts
- allow parallel edits to:
- demand/assignment backend packages vs estimate frontend app code
- docs vs tests
- dashboard/report query cleanup vs estimate workspace UI copy
Document Ownership
| Topic | Canonical File | Notes |
|---|---|---|
| Active backlog | product-roadmap.md | Update this instead of reopening old plan files. |
| Estimating design and field mapping | estimating-extension-design.md | Holds workbook analysis, mapping, and implementation plan. |
| Strategic architecture direction | v2-architecture-proposal-2026-03-11.md | Keep as strategy, not sprint backlog. |
| Historical decisions | LEARNINGS.md | Append-only log. |