Files
Hartmut 4a49ec4f05 fix(sanity): resolve 15 gaps from sanity check audit (G-01 through G-15)
- G-01: ProjectWizard renders blueprint fieldDefs with DynamicFieldInput component
- G-02: Blueprint rolePresets validated via RolePresetsSchema in wizard; API keeps loose schema
- G-03: ProjectWizard step 2/3 validation (role, hoursPerDay, headcount required)
- G-04: EstimateWizard validates baseCurrency and demand line cost rates
- G-05: Project lifecycle transition guards with ALLOWED_TRANSITIONS map
- G-06: Blueprint validator extended for minLength/maxLength/pattern and DATE range checks
- G-07: assertBlueprintDynamicFields merges global blueprint fieldDefs into validation
- G-08: (tracked — chapter managed dropdown; deferred to backend ticket)
- G-09: JSDoc added to lcrCents/ucrCents clarifying LCR/UCR terminology
- G-10: Dispo route redirect already in place — closed as done
- G-11: packages/ui empty by design — closed as documented
- G-12: @deprecated JSDoc added to CreateAllocationSchema and UpdateAllocationSchema
- G-13: ProjectWizard review step enhanced with blueprint name, field values, skills, assignments
- G-14: ProjectWizard handleSubmit collects per-item warnings instead of silent swallowing
- G-15: Vacation cancel reverses usedDays entitlement for APPROVED ANNUAL/OTHER vacations

Tests: all 1575 passing (1 pre-existing failure in insights-summary unrelated to these changes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 00:11:12 +02:00

19 KiB

CapaKraken Sanity Check

Date: 2026-04-05 Purpose: Living reference covering terminology, process flows, wizard/blueprint validation, and feature gaps.


Table of Contents

  1. Terminology Glossary
  2. Process Flows
  3. Wizard & Blueprint Audit
  4. Feature Gap Register

1. Terminology Glossary

1.1 Allocation / DemandRequirement / Assignment

Term Definition Where
DemandRequirement First-class DB entity — an unfilled staffing need on a project (role, headcount, hours, budget). No resourceId. packages/db model, packages/shared/types/allocation.ts
Assignment First-class DB entity — a resource assigned to a project for a date range with hours/cost. Has resourceId (required) and optional demandRequirementId. packages/db model, packages/shared/types/allocation.ts
Allocation Legacy read-model interface — unified view merging demands and assignments via isPlaceholder flag. No DB table. packages/shared/types/allocation.ts lines 29-47
AllocationLike Union-compatible shape keeping isPlaceholder for backwards compat with UI components. packages/shared/types/allocation.ts lines 135-156
"allocation entry" Facade term used in use-case files (update-allocation-entry.ts, load-allocation-entry.ts, delete-allocation-entry.ts) to mean "either a DemandRequirement or an Assignment". packages/application/src/use-cases/allocation/

Known inconsistencies:

  • The tRPC router is still named allocation (not planning or demand-assignment)
  • CreateAllocationSchema coexists with CreateDemandRequirementSchema and CreateAssignmentSchema
  • createDemand is an undocumented alias for createDemandRequirement in the router
  • FillOpenDemandByAllocationSchema uses field name allocationId referencing a non-existent table

1.2 Resource / User

Term Definition
User Auth/login identity with email, password hash, SystemRole, MFA, dashboard preferences. Optional 1:1 link to Resource.
Resource Staffable person with EID, rates (LCR/UCR), skills, chapter, availability, dynamic fields. Optional 1:1 link to User.

Status: Clean. No inconsistencies found. UI labels correctly use "User" in admin/auth context and "Resource" in planning/staffing context.

1.3 Blueprint / DynamicFields / Widget

Term Definition
Blueprint Configuration template defining custom fields for Resources or Projects. Contains fieldDefs, defaults, validationRules, rolePresets.
BlueprintFieldDefinition Schema for a single custom field (type, label, key, validation, options). Stored in Blueprint.fieldDefs JSONB.
DynamicFields Record<string, unknown> — actual custom field values stored on Resource/Project.
Widget Dashboard component (stat-cards, charts, tables). Completely separate from blueprints.

Status: Clean. No confusion between blueprint fields and dashboard widgets.

1.4 Chapter

Definition: Organizational group/department. Always a plain String? — there is no Chapter entity anywhere in the schema.

Appears on 9+ models: Resource, StagedResource, StagedAssignment, EstimateDemandLine, RateCardLine, ResourceCostSnapshot, EffortRule, ExperienceMultiplierRule.

Known inconsistencies:

  • chapter (display name), chapterCode (StagedResource), chapterToken (StagedAssignment) — three naming variants for the same concept in import pipeline
  • No referential integrity, no centralized rename, no metadata (color, description)
  • Grouping in reports/staffing relies on exact string matching

1.5 Rate Fields

Field Found On Meaning Unit
lcrCents Resource, StagedResource, ResourceCostSnapshot Labor Cost Rate (hourly) Integer cents
ucrCents Resource, StagedResource, ResourceCostSnapshot Utilization Cost Rate (hourly) Integer cents
dailyCostCents Assignment Daily assignment cost Integer cents
costRateCents EstimateDemandLine, RateCardLine Cost rate per hour Integer cents
billRateCents EstimateDemandLine, RateCardLine Bill rate per hour Integer cents
budgetCents DemandRequirement, Project Budget allocation Integer cents

Known inconsistencies:

  • Resources use lcrCents/ucrCents (domain jargon); Estimates use costRateCents/billRateCents (generic). Same concept, different names.
  • Assignments store dailyCostCents (daily); all other rates are hourly. Time dimension is implicit.
  • No billRateCents on Assignment — billing lives only in the estimating domain.

1.6 Estimate Domain

Term Definition
Estimate Top-level container with name, project link (optional), status, base currency.
EstimateVersion Immutable snapshot within an estimate. Contains demand lines, scope items, assumptions, metrics.
EstimateDemandLine Effort line with role, resource, hours, rates, totals. The financial building block.
ScopeItem Deliverable/work item (shot, asset) with frame/item counts.
ResourceCostSnapshot Point-in-time rate capture for audit trail.

Status: Clean. "Demand line" (estimating) and "demand requirement" (planning) are distinct concepts, well-separated in code.

1.7 Dispo / Import

Term Definition
Dispo Short for "Disposition" (German resource planning). Legacy system name (DISPO_V2).
ImportBatch Generic import container. sourceSystem defaults to "DISPO_V2".
Staged* Prefix for all import staging models (StagedResource, StagedProject, etc.).

Known inconsistencies:

  • Dual routes: /admin/imports (new, with tabs) and /admin/dispo-imports (old, standalone). Both still active.
  • Router named dispoRouter rather than generic importRouter.

2. Process Flows

2.1 Project Lifecycle

DRAFT ──→ ACTIVE ──→ ON_HOLD ──→ COMPLETED
  │          │          │            │
  └──────────┴──────────┴────────────┴──→ CANCELLED

Guard conditions:

  • Permission: MANAGE_PROJECTS + managerProcedure
  • No state machine enforcement — any status can transition to any other status. CANCELLED → ACTIVE is allowed.

Side effects: Dashboard cache invalidation, webhook project.status_changed, audit log on batch operations.

Files: packages/api/src/router/project-lifecycle.ts lines 72-88

Gap: No transition guards. Consider adding allowed-transition validation.

2.2 Estimate Lifecycle

Estimate status: DRAFT → IN_REVIEW → APPROVED → ARCHIVED Version status:

WORKING ──submit──→ SUBMITTED ──approve──→ APPROVED
                                              │
                                         ──revision──→ (new WORKING)

Other SUBMITTED versions → SUPERSEDED (on submit or approve)
Other APPROVED versions → SUPERSEDED (on approve of different version)

Guard conditions (enforced):

  • Submit: version must be WORKING (throws otherwise)
  • Approve: version must be SUBMITTED (throws otherwise)
  • Revision: no existing WORKING version; source must be locked

Side effects: lockedAt timestamp set, parent estimate status synced, superseded versions marked.

Files: packages/application/src/use-cases/estimate/version-actions.ts

Status: Well-implemented with proper guards and cascading state management.

2.3 Staffing Flow

DemandRequirement ──rank resources──→ StaffingSuggestion[] ──fill──→ Assignment

Scoring (4 factors):

Factor Weight Logic
Skill 40% Required skills (70%) + preferred (30%), proficiency 1-5 normalized
Availability 30% 100 minus 10 per conflict day
Cost 20% 100 at/under budget LCR, linear to 0 at 2x
Utilization 10% 100 if 20%+ below target, degrades linearly

Fill guards:

  • Demand must not be CANCELLED or COMPLETED
  • Duplicate check (same resource + project + overlapping dates)
  • Availability check (fails if >5 conflict days)
  • DailyCostCents calculated via engine

Files: packages/staffing/src/skill-matcher.ts, packages/application/src/use-cases/allocation/fill-demand-requirement.ts

2.4 Estimate-to-Planning Handoff

EstimateDemandLine ──handoff──→ DemandRequirement + optional Assignment

Guards:

  • Version must be APPROVED
  • Estimate must be linked to a project
  • No duplicate handoff (checks existing entries for same version)

Logic per demand line (hours > 0):

  • If resourceId present: creates DemandRequirement + Assignment (both PROPOSED)
  • If no resourceId: creates DemandRequirement only (placeholder)
  • If assignment creation fails (resource not found, conflicts): falls back to placeholder

Rate snapshotting: costRateCents, billRateCents, currency, totals, monthly spread, and staffing attributes all captured in estimateHandoff metadata.

Files: packages/application/src/use-cases/estimate/create-planning-handoff.ts

2.5 Vacation Flow

Request ──→ PENDING ──approve──→ APPROVED
                     ──reject──→ REJECTED
                     ──cancel──→ CANCELLED

REJECTED ──re-approve──→ APPROVED
CANCELLED ──re-approve──→ APPROVED

Guards:

  • Approve: source must be PENDING, CANCELLED, or REJECTED
  • Reject: source must be PENDING only
  • Cancel: source must not be CANCELLED; actor must be ADMIN/MANAGER or original requester

Overlap detection: Advisory only (does not block). Warns when >50% of a chapter is absent on any working day. Creates HIGH priority notification.

Entitlement: Only ANNUAL leave triggers deduction. Sick/public holiday/other do not.

Files: packages/api/src/router/vacation-management-procedures.ts, vacation-management-support.ts

2.6 Dispo Import Flow

DRAFT → STAGING → STAGED → REVIEW_READY → APPROVED → COMMITTING → COMMITTED
                                                                       │
                                          FAILED ←─── (any error) ─────┘
                                          CANCELLED ←── (manual cancel)

Commit preconditions: batch in STAGED/REVIEW_READY/APPROVED; no blocking unresolved records; no staged PUBLIC_HOLIDAY records.

Commit transaction (600s timeout):

  1. Upsert utilization categories and role seeds
  2. Upsert resources by EID, roles, vacation entitlements, availability rules
  3. Upsert projects by shortCode (TBD → DRAFT, others → ACTIVE)
  4. Upsert assignments (aggregated, PROPOSED status)
  5. Create vacations (APPROVED with admin user)
  6. Mark all staged records as COMMITTED
  7. Post-transaction: recompute resource value scores

Rollback: Entire commit in $transaction — any error rolls back everything.

Files: packages/application/src/use-cases/dispo-import/commit-dispo-import-batch.ts

2.7 Budget Monitoring

Pure function: computeBudgetStatus(budgetCents, winProbability, allocations, dateRange)

Threshold Level Code
>= 70% info BUDGET_INFO
>= 85% warning BUDGET_WARNING
>= 95% critical BUDGET_CRITICAL
> 100% critical BUDGET_EXCEEDED

Allocation splits: CONFIRMED + ACTIVE → confirmedCents; PROPOSED → proposedCents; COMPLETED/CANCELLED → ignored.

Files: packages/engine/src/budget/monitor.ts

2.8 Chargeability Forecast

SAH Calculator → net working hours (minus holidays, absences, variable schedules)
     ↓
Chargeability Calculator → FTE-weighted forecast per utilization category
     ↓
Rules Engine → cost attribution and chargeability effect per absence type

SAH supports: Variable schedule rules (Spain Friday/summer hours), public holidays, absences, FTE factor.

Category breakdown: Chg, BD, MD&I, M&O, PD&R, absence, unassigned — all as ratios against SAH.

Rules engine: Matches by triggerType (SICK/VACATION/PUBLIC_HOLIDAY/CUSTOM), project/orderType scope, specificity ranking. Effects: CHARGE/ZERO/REDUCE for cost, COUNT/SKIP for chargeability.

Default rules: Vacation/sick count toward chargeability but not charged to project. Public holidays: neither.

Files: packages/engine/src/chargeability/calculator.ts, sah/calculator.ts, rules/engine.ts


3. Wizard & Blueprint Audit

3.1 ProjectWizard

File: apps/web/src/components/projects/ProjectWizard.tsx (1294 lines, 5 steps)

Step Name Validation Issues
0 Blueprint & Identity shortCode (regex), name (non-empty) OK
1 Timeline & Budget dates valid, budget >= 0 OK
2 Staffing Demand None Can advance with empty role names, zero headcount
3 Suggestions None Only shown if requiredSkills non-empty
4 Review & Create None No re-validation before submit

Critical findings:

  1. Blueprint fieldDefs are NEVER rendered. The wizard always sends dynamicFields: {}. If a blueprint has required custom fields, project creation fails server-side with UNPROCESSABLE_CONTENT. Zero matches for fieldDefs in the entire wizard file.

  2. rolePresets loaded but unvalidated. Blueprint rolePresets are cast via as unknown as StaffingRequirement[] with no runtime check. Malformed presets create broken staffing rows.

  3. Silent failure on post-creation. Assignment and demand creation errors after project creation are swallowed in empty catch blocks — project can be created without its staffing.

  4. Review step is incomplete. Does not show: blueprint name, required/preferred skills per role, individual assignment details, per-role budget, total staffing cost.

3.2 EstimateWizard

File: apps/web/src/components/estimates/EstimateWizard.tsx (833 lines, 5 steps)

Step Name Validation Issues
0 Setup name.trim() only No check on currency, status, version label
1 Assumptions None Empty assumptions allowed
2 Scope None Empty scope allowed
3 Staffing None Zero rates, missing resources pass through
4 Review name.trim() only No financial validation

Critical findings:

  1. Minimal validation across all steps. Only name.trim() is checked. Demand lines with zero hours, zero rates, or invalid currencies pass to the server.

  2. Resource rate auto-fill works correctly. Selecting a resource fills name, chapter, currency, costRate (from lcrCents), billRate (from ucrCents), roleId.

  3. Scope XLSX import works. Uses exceljs with fuzzy column matching, 10MB/5000 row limits.

  4. Financial summary is correctly computed (hours * rateCents with rounding). Margin percent rounded to nearest integer.

3.3 Blueprint Validation Engine

File: packages/engine/src/blueprint/validator.ts (92 lines)

FieldType Runtime Validation Gap
NUMBER Type check, min/max Full
BOOLEAN true/false check Full
SELECT Value in options Full
MULTI_SELECT All values in options Full
URL new URL() test Full
EMAIL Regex check Basic
TEXT Required only No minLength/maxLength/pattern
TEXTAREA Required only No minLength/maxLength
DATE Required only No format/range validation

Disconnect: The Zod generateDynamicZodSchema (in blueprint.schema.ts) handles minLength/maxLength/pattern, but the runtime validateCustomFields does not. Both exist but serve different purposes — the Zod schema is not called during entity mutations.

Global blueprints: Fields from global blueprints are NOT validated server-side during create/update. Only the entity-specific blueprint is checked via assertBlueprintDynamicFields.

rolePresets: Accepted as z.array(z.unknown()) — no structural validation at any layer.


4. Feature Gap Register

# Gap Severity Area Recommendation
G-01 ProjectWizard never renders blueprint fieldDefs — dynamicFields: {} always sent High Wizard Add dynamic field rendering in Step 1 or a new step. Load fieldDefs from selected blueprint, render form inputs by type, validate before submit.
G-02 rolePresets unvalidated at API, storage, and consumption layers High Blueprint Add a proper Zod schema for rolePresets matching StaffingRequirement[]. Validate on write, validate on read in wizard.
G-03 ProjectWizard Steps 2-3 have no validation Medium Wizard Add required checks: role name non-empty, hoursPerDay > 0, headcount >= 1.
G-04 EstimateWizard has near-zero validation (only name.trim()) Medium Wizard Add per-step validation: currency required (Step 0), demand lines need hours > 0 and rates > 0 (Step 3).
G-05 Project lifecycle has no transition guards (CANCELLED → ACTIVE is allowed) Medium Process Add an allowed-transitions map in the updateStatus mutation.
G-06 Blueprint TEXT/TEXTAREA/DATE fields lack runtime validation for minLength/maxLength/pattern/date-range Medium Validation Extend validateCustomFields to check all constraints that generateDynamicZodSchema already supports.
G-07 Global blueprint fields not validated server-side during entity create/update Medium Validation Include global blueprint fieldDefs in assertBlueprintDynamicFields resolution.
G-08 Chapter is a freeform string (9+ models) with no lookup table Medium Data model Consider promoting to a first-class entity or at minimum a managed dropdown populated from SELECT DISTINCT.
G-09 LCR/UCR vs costRate/billRate naming split across domains Low Terminology Document the mapping explicitly. Consider aliasing in shared types.
G-10 Dual dispo import routes (/admin/imports and /admin/dispo-imports) Low UI Deprecate and redirect the old route.
G-11 packages/ui nearly empty — all components in apps/web Low Architecture Not urgent. Migrate shared components when a second app is needed.
G-12 Legacy CreateAllocationSchema / UpdateAllocationSchema still active alongside first-class schemas Low Terminology Mark as deprecated in code comments. Plan removal when all consumers migrated.
G-13 ProjectWizard review step does not show skills, assignment details, or total cost Low UX Enhance review panel to surface all collected data.
G-14 PostProject-creation assignment/demand creation errors silently swallowed Medium Reliability Replace empty catch with error reporting and partial-creation recovery UI.
G-15 Vacation cancel does not reverse entitlement deduction Low Process Verify if this is intentional. If approved vacation is cancelled, usedDays should be decremented.

Priority Matrix

Fix immediately (High severity):

  • G-01: Blueprint fieldDefs not rendered in ProjectWizard
  • G-02: rolePresets unvalidated

Fix soon (Medium severity):

  • G-03, G-04: Wizard validation gaps
  • G-05: Project lifecycle transition guards
  • G-06, G-07: Blueprint validation completeness
  • G-08: Chapter entity promotion
  • G-14: Silent failure on post-creation

Track / document (Low severity):

  • G-09 through G-13, G-15: Terminology docs, route cleanup, architecture notes