# Nexus 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](#1-terminology-glossary)
2. [Process Flows](#2-process-flows)
3. [Wizard & Blueprint Audit](#3-wizard--blueprint-audit)
4. [Feature Gap Register](#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
