# Headroom - Architecture & Design **Version:** 1.0 **Date:** February 17, 2026 **Status:** Approved for Implementation --- ## Table of Contents 1. [System Architecture](#system-architecture) 2. [Data Model](#data-model) 3. [API Architecture](#api-architecture) 4. [Authentication Flow](#authentication-flow) 5. [Deployment Architecture](#deployment-architecture) 6. [Technology Stack Details](#technology-stack-details) 7. [Quality Standards](#quality-standards) 8. [Development Workflow](#development-workflow) --- ## System Architecture ### High-Level Component Diagram ```mermaid graph TB subgraph "Client Layer" Browser[Web Browser] end subgraph "Frontend Container :5173" SvelteKit[SvelteKit App] Store[Svelte Stores] Forms[Superforms + Zod] Charts[Recharts] Tables[TanStack Table] end subgraph "Backend Container :3000" API[Laravel API] Resources[API Resources] Auth[JWT Auth] Queue[Queue System] Scribe[API Docs - Scribe] end subgraph "Data Layer" Postgres[(PostgreSQL)] Redis[(Redis Cache)] end subgraph "Reverse Proxy" NPM[Nginx Proxy Manager] end Browser -->|HTTPS| NPM NPM -->|/api/*| API NPM -->|/*| SvelteKit SvelteKit -->|REST API| API SvelteKit --> Store SvelteKit --> Forms SvelteKit --> Charts SvelteKit --> Tables API --> Resources API --> Auth API --> Queue API --> Scribe API -->|Read/Write| Postgres API -->|Cache| Redis Queue -->|Job Storage| Redis style SvelteKit fill:#ff3e00 style API fill:#ff2d20 style Postgres fill:#336791 style Redis fill:#dc382d ``` ### Request Flow ```mermaid sequenceDiagram participant User participant Browser participant NPM as Nginx Proxy Manager participant SvelteKit participant Laravel as Laravel API participant Redis participant DB as PostgreSQL User->>Browser: Access dashboard Browser->>NPM: GET / NPM->>SvelteKit: Forward request SvelteKit-->>Browser: Return HTML + JS Browser->>NPM: GET /api/allocations?month=2026-02 NPM->>Laravel: Forward API request Laravel->>Redis: Check cache alt Cache Hit Redis-->>Laravel: Return cached data else Cache Miss Laravel->>DB: Query allocations DB-->>Laravel: Return data Laravel->>Redis: Store in cache end Laravel->>Laravel: Transform via API Resources Laravel-->>Browser: JSON response Browser->>Browser: Render with TanStack Table ``` --- ## Data Model ### Entity Relationship Diagram ```mermaid erDiagram TEAM_MEMBER ||--o{ ALLOCATION : "receives" TEAM_MEMBER ||--o{ ACTUAL : "logs" TEAM_MEMBER ||--o{ PTO : "requests" TEAM_MEMBER }o--|| ROLE : "has" PROJECT ||--o{ ALLOCATION : "requires" PROJECT ||--o{ ACTUAL : "tracks" PROJECT }o--|| PROJECT_STATUS : "in" PROJECT }o--|| PROJECT_TYPE : "categorized_as" ALLOCATION }o--|| MONTH : "for" ACTUAL }o--|| MONTH : "in" HOLIDAY }o--|| MONTH : "occurs_in" PTO }o--|| MONTH : "occurs_in" TEAM_MEMBER { uuid id PK string name uuid role_id FK decimal hourly_rate boolean active timestamp created_at timestamp updated_at } ROLE { uuid id PK string name string description timestamp created_at } PROJECT { uuid id PK string project_code UK string title uuid status_id FK uuid type_id FK decimal approved_estimate json forecasted_effort date start_date date end_date timestamp created_at timestamp updated_at } PROJECT_STATUS { uuid id PK string name int order boolean is_active boolean is_billable } PROJECT_TYPE { uuid id PK string name string description } ALLOCATION { uuid id PK uuid project_id FK uuid team_member_id FK string month decimal allocated_hours text notes timestamp created_at timestamp updated_at } ACTUAL { uuid id PK uuid project_id FK uuid team_member_id FK string month decimal hours_logged text notes timestamp created_at timestamp updated_at } MONTH { string id PK int year int month int working_days } HOLIDAY { uuid id PK date date string name string description } PTO { uuid id PK uuid team_member_id FK date start_date date end_date string reason string status timestamp created_at } ``` ### Database Schema Details **Key Design Decisions:** 1. **UUIDs for Primary Keys** - Prevents ID enumeration attacks - Easier distributed system support (future) - No auto-increment exposure 2. **Normalized Master Data** - Roles, Statuses, Types in separate tables - Allows dynamic configuration without code changes 3. **Month as String (YYYY-MM)** - Simplifies queries and grouping - Index-friendly - Human-readable 4. **JSON for Forecasted Effort** - Flexible structure: `{"2026-02": 40, "2026-03": 60}` - Easy to extend without schema changes - PostgreSQL JSON operators for querying 5. **Soft Deletes** - Team members: use `active` boolean (preserve historical allocations) - Projects: `deleted_at` timestamp (audit trail) --- ## API Architecture ### REST Endpoint Structure ```mermaid graph LR subgraph "API Routes" Auth[/api/auth] TeamMembers[/api/team-members] Projects[/api/projects] Allocations[/api/allocations] Actuals[/api/actuals] Reports[/api/reports] MasterData[/api/master-data] end Auth --> Login[POST /login] Auth --> Logout[POST /logout] Auth --> Refresh[POST /refresh] TeamMembers --> TMList[GET /] TeamMembers --> TMCreate[POST /] TeamMembers --> TMShow[GET /:id] TeamMembers --> TMUpdate[PUT /:id] TeamMembers --> TMDelete[DELETE /:id] Projects --> PList[GET /] Projects --> PCreate[POST /] Projects --> PShow[GET /:id] Projects --> PUpdate[PUT /:id] Projects --> PDelete[DELETE /:id] Allocations --> AList[GET /?month=YYYY-MM] Allocations --> ACreate[POST /] Allocations --> ABulk[POST /bulk] Allocations --> AUpdate[PUT /:id] Allocations --> ADelete[DELETE /:id] Actuals --> ACList[GET /?month=YYYY-MM] Actuals --> ACCreate[POST /] Actuals --> ACBulk[POST /bulk] Actuals --> ACUpdate[PUT /:id] Reports --> RForecast[GET /forecast] Reports --> RUtilization[GET /utilization] Reports --> RCost[GET /cost] Reports --> RAllocation[GET /allocation] Reports --> RVariance[GET /variance] MasterData --> MDRoles[GET /roles] MasterData --> MDStatuses[GET /statuses] MasterData --> MDTypes[GET /types] ``` ### API Resource Transformations **Laravel API Resources ensure consistent JSON structure:** ```json // Team Member Resource { "data": { "id": "uuid", "name": "John Doe", "role": { "id": "uuid", "name": "Backend Developer" }, "hourly_rate": 150.00, "active": true, "created_at": "2026-02-01T10:00:00Z" } } // Allocation Resource { "data": { "id": "uuid", "project": { "id": "uuid", "code": "PROJ-001", "title": "Client Dashboard Redesign" }, "team_member": { "id": "uuid", "name": "Jane Smith" }, "month": "2026-02", "allocated_hours": 40.0, "notes": "Focus on frontend work", "created_at": "2026-02-01T10:00:00Z" } } // Report Resource (Forecast) { "data": { "period": { "from": "2026-02", "to": "2026-05" }, "projects": [ { "id": "uuid", "code": "PROJ-001", "title": "Client Dashboard Redesign", "approved_estimate": 120.0, "forecast_by_month": { "2026-02": 40.0, "2026-03": 60.0, "2026-04": 20.0 }, "allocated_by_month": { "2026-02": 38.0, "2026-03": 62.0, "2026-04": 20.0 }, "variance": { "2026-02": -2.0, "2026-03": 2.0, "2026-04": 0 }, "status": "under_allocated" } ], "summary": { "total_approved": 120.0, "total_allocated": 120.0, "variance": 0 } } } ``` ### Validation & Error Responses **Laravel Form Requests handle validation:** ```php // Example: AllocationRequest public function rules(): array { return [ 'project_id' => 'required|exists:projects,id', 'team_member_id' => 'required|exists:team_members,id', 'month' => 'required|date_format:Y-m', 'allocated_hours' => 'required|numeric|min:0|max:9999.99', 'notes' => 'nullable|string|max:1000', ]; } ``` **Error Response Format:** ```json { "message": "The given data was invalid.", "errors": { "allocated_hours": [ "The allocated hours must be at least 0." ], "month": [ "The month field must be in YYYY-MM format." ] } } ``` ### Caching Strategy ```mermaid graph TD Request[API Request] --> CheckCache{Cache Exists?} CheckCache -->|Yes| ReturnCache[Return Cached Response] CheckCache -->|No| QueryDB[Query Database] QueryDB --> Transform[Transform via API Resource] Transform --> StoreCache[Store in Redis] StoreCache --> ReturnFresh[Return Fresh Response] Mutation[Create/Update/Delete] --> InvalidateCache[Invalidate Related Caches] InvalidateCache --> ExecuteMutation[Execute Mutation] style CheckCache fill:#ffd700 style StoreCache fill:#dc382d style InvalidateCache fill:#dc382d ``` **Cache Keys Pattern:** ``` allocations:month:{YYYY-MM} allocations:project:{project_id} allocations:member:{member_id} reports:forecast:{from}:{to}:{filters_hash} reports:utilization:{month}:{team_id} capacity:month:{YYYY-MM} ``` **Cache Invalidation Rules:** | Mutation | Invalidate Keys | |----------|-----------------| | Allocation created/updated | `allocations:month:{month}`, `allocations:project:{project_id}`, `allocations:member:{member_id}`, `reports:*` | | Actual logged | `actuals:month:{month}`, `reports:utilization:*`, `reports:variance:*` | | Team member updated | `allocations:member:{id}`, `capacity:*`, `reports:*` | | Project updated | `allocations:project:{id}`, `reports:*` | **TTL (Time to Live):** - Allocations: 1 hour - Reports: 15 minutes - Master data: 24 hours --- ## Authentication Flow ### JWT Authentication Sequence ```mermaid sequenceDiagram participant User participant SvelteKit participant Laravel participant Redis participant DB User->>SvelteKit: Enter credentials SvelteKit->>Laravel: POST /api/auth/login Laravel->>DB: Validate credentials DB-->>Laravel: User found Laravel->>Laravel: Generate JWT token Laravel->>Redis: Store refresh token Laravel-->>SvelteKit: {access_token, refresh_token, user} SvelteKit->>SvelteKit: Store tokens in localStorage SvelteKit-->>User: Redirect to dashboard Note over SvelteKit,Laravel: Subsequent requests User->>SvelteKit: Access protected resource SvelteKit->>Laravel: GET /api/allocations
Header: Authorization: Bearer {token} Laravel->>Laravel: Validate JWT alt Token Valid Laravel->>DB: Fetch data DB-->>Laravel: Return data Laravel-->>SvelteKit: JSON response else Token Expired Laravel-->>SvelteKit: 401 Unauthorized SvelteKit->>Laravel: POST /api/auth/refresh
{refresh_token} Laravel->>Redis: Validate refresh token Redis-->>Laravel: Token valid Laravel->>Laravel: Generate new access token Laravel-->>SvelteKit: {access_token} SvelteKit->>Laravel: Retry original request end ``` ### Token Structure **Access Token (JWT):** ```json { "sub": "user-uuid", "role": "manager", "permissions": ["allocate_resources", "view_reports"], "iat": 1708171200, "exp": 1708174800, "jti": "token-uuid" } ``` **Refresh Token:** - Stored in Redis with 7-day TTL - One-time use (rotated on refresh) - Revocable (logout invalidates) ### Authorization Middleware ```mermaid graph TD Request[Incoming Request] --> ExtractToken[Extract JWT from Header] ExtractToken --> ValidateToken{Token Valid?} ValidateToken -->|No| Return401[Return 401 Unauthorized] ValidateToken -->|Yes| LoadUser[Load User from Token] LoadUser --> CheckPermission{Has Permission?} CheckPermission -->|No| Return403[Return 403 Forbidden] CheckPermission -->|Yes| ExecuteController[Execute Controller] ExecuteController --> CheckPolicy{Model Policy?} CheckPolicy -->|Fail| Return403 CheckPolicy -->|Pass| ReturnResponse[Return Response] style ValidateToken fill:#ffd700 style CheckPermission fill:#ffd700 style CheckPolicy fill:#ffd700 ``` **Permission Matrix:** | Role | Permissions | |------|-------------| | **Superuser** | `*` (all permissions) | | **Manager** | `view_all_projects`, `create_project`, `edit_own_project`, `allocate_own_team`, `view_reports` | | **Developer** | `view_own_allocations`, `log_hours`, `view_assigned_projects` | | **Top Brass** | `view_all_reports` (read-only) | --- ## Deployment Architecture ### Docker Compose Structure ```mermaid graph TB subgraph "Docker Compose Environment" subgraph "Frontend Container" SK[SvelteKit
Node:latest
Port 5173] SKVol[/app mounted
from ./frontend] end subgraph "Backend Container" LAR[Laravel
PHP 8.4-FPM
Port 3000] LARVol[/var/www mounted
from ./backend] end subgraph "Database Container" PG[PostgreSQL:latest
Port 5432] PGVol[/var/lib/postgresql/data
mounted from ./data/postgres] end subgraph "Cache Container" RD[Redis:latest
Port 6379] RDVol[/data mounted
from ./data/redis] end end subgraph "Host Machine" NPM[Nginx Proxy Manager
Port 80/443] FrontendCode[./frontend/
SvelteKit source] BackendCode[./backend/
Laravel source] end NPM -->|Proxy /api/*| LAR NPM -->|Proxy /*| SK SK -->|API calls| LAR LAR -->|DB queries| PG LAR -->|Cache| RD SK -.->|Code mount| FrontendCode LAR -.->|Code mount| BackendCode style SK fill:#ff3e00 style LAR fill:#ff2d20 style PG fill:#336791 style RD fill:#dc382d ``` ### docker-compose.yml ```yaml version: '3.8' services: frontend: build: context: ./frontend dockerfile: Dockerfile.dev container_name: headroom_frontend ports: - "5173:5173" volumes: - ./frontend:/app - /app/node_modules environment: - VITE_API_URL=http://backend:3000/api depends_on: - backend networks: - headroom_network restart: unless-stopped backend: build: context: ./backend dockerfile: Dockerfile.dev container_name: headroom_backend ports: - "3000:3000" volumes: - ./backend:/var/www - /var/www/vendor environment: - APP_ENV=local - APP_DEBUG=true - DB_CONNECTION=pgsql - DB_HOST=postgres - DB_PORT=5432 - DB_DATABASE=headroom - DB_USERNAME=headroom_user - DB_PASSWORD=${DB_PASSWORD} - REDIS_HOST=redis - REDIS_PORT=6379 depends_on: - postgres - redis networks: - headroom_network restart: unless-stopped postgres: image: postgres:latest container_name: headroom_postgres ports: - "5432:5432" volumes: - ./data/postgres:/var/lib/postgresql/data environment: - POSTGRES_DB=headroom - POSTGRES_USER=headroom_user - POSTGRES_PASSWORD=${DB_PASSWORD} networks: - headroom_network restart: unless-stopped redis: image: redis:latest container_name: headroom_redis ports: - "6379:6379" volumes: - ./data/redis:/data networks: - headroom_network restart: unless-stopped networks: headroom_network: driver: bridge volumes: postgres_data: redis_data: ``` ### Environment Variables (.env) **Backend (.env):** ```ini APP_NAME=Headroom APP_ENV=local APP_KEY=base64:... APP_DEBUG=true APP_URL=http://localhost:3000 DB_CONNECTION=pgsql DB_HOST=postgres DB_PORT=5432 DB_DATABASE=headroom DB_USERNAME=headroom_user DB_PASSWORD=secure_password_here REDIS_HOST=redis REDIS_PORT=6379 JWT_SECRET=your_jwt_secret_here JWT_TTL=60 JWT_REFRESH_TTL=10080 CACHE_DRIVER=redis QUEUE_CONNECTION=redis ``` **Frontend (.env):** ```ini VITE_API_URL=http://localhost:3000/api VITE_APP_NAME=Headroom ``` ### Reverse Proxy Configuration (Nginx Proxy Manager) **Proxy Host 1: Frontend** - Domain: `headroom.local` (or your domain) - Forward to: `http://headroom_frontend:5173` - WebSocket support: Enabled **Proxy Host 2: API** - Domain: `headroom.local/api/*` (or your domain) - Forward to: `http://headroom_backend:3000/api` - Custom headers: ``` X-Forwarded-For: $remote_addr X-Forwarded-Proto: $scheme ``` --- ## Technology Stack Details ### Backend: Laravel **Project Structure:** ``` backend/ ├── app/ │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── AuthController.php │ │ │ ├── TeamMemberController.php │ │ │ ├── ProjectController.php │ │ │ ├── AllocationController.php │ │ │ ├── ActualController.php │ │ │ └── ReportController.php │ │ ├── Requests/ │ │ │ ├── AllocationRequest.php │ │ │ ├── ProjectRequest.php │ │ │ └── ... │ │ ├── Resources/ │ │ │ ├── TeamMemberResource.php │ │ │ ├── ProjectResource.php │ │ │ ├── AllocationResource.php │ │ │ └── ... │ │ └── Middleware/ │ │ └── JwtMiddleware.php │ ├── Models/ │ │ ├── TeamMember.php │ │ ├── Project.php │ │ ├── Allocation.php │ │ ├── Actual.php │ │ └── ... │ ├── Policies/ │ │ ├── ProjectPolicy.php │ │ ├── AllocationPolicy.php │ │ └── ... │ └── Services/ │ ├── CapacityService.php │ ├── AllocationService.php │ └── ReportService.php ├── database/ │ ├── migrations/ │ └── seeders/ ├── routes/ │ └── api.php ├── tests/ │ ├── Unit/ │ └── Feature/ └── composer.json ``` **Key Packages:** ```json { "require": { "php": "^8.3", "laravel/framework": "^11.0", "tymon/jwt-auth": "^2.0", "predis/predis": "^2.0", "knuckleswtf/scribe": "^4.0" }, "require-dev": { "pestphp/pest": "^2.0", "phpunit/phpunit": "^10.0", "laravel/pint": "^1.0" } } ``` ### Frontend: SvelteKit **Project Structure:** ``` frontend/ ├── src/ │ ├── lib/ │ │ ├── components/ │ │ │ ├── AllocationTable.svelte │ │ │ ├── CapacityCalendar.svelte │ │ │ ├── ForecastChart.svelte │ │ │ └── ... │ │ ├── stores/ │ │ │ ├── auth.ts │ │ │ ├── filters.ts │ │ │ └── ui.ts │ │ ├── api/ │ │ │ ├── client.ts │ │ │ ├── allocations.ts │ │ │ ├── projects.ts │ │ │ └── ... │ │ └── schemas/ │ │ ├── allocation.ts (Zod schemas) │ │ ├── project.ts │ │ └── ... │ ├── routes/ │ │ ├── +layout.svelte │ │ ├── +page.svelte (dashboard) │ │ ├── login/ │ │ ├── capacity/ │ │ ├── projects/ │ │ ├── allocations/ │ │ ├── actuals/ │ │ └── reports/ │ └── app.html ├── static/ ├── tests/ │ ├── unit/ │ └── e2e/ └── package.json ``` **Key Packages:** ```json { "dependencies": { "@sveltejs/kit": "^2.0.0", "svelte": "^5.0.0", "tailwindcss": "^3.4.0", "daisyui": "^4.0.0", "recharts": "^2.10.0", "@tanstack/svelte-table": "^8.0.0", "sveltekit-superforms": "^2.0.0", "zod": "^3.22.0" }, "devDependencies": { "vitest": "^1.0.0", "@playwright/test": "^1.40.0", "prettier": "^3.1.0", "eslint": "^8.55.0" } } ``` --- ## Quality Standards ### Testing Strategy ```mermaid graph TB Code[Code Change] --> UnitTests[Unit Tests] UnitTests --> FeatureTests[Feature Tests] FeatureTests --> E2ETests[E2E Tests] UnitTests -->|PHPUnit/Vitest| UnitPass{Pass?} FeatureTests -->|PHPUnit/Pest| FeaturePass{Pass?} E2ETests -->|Playwright| E2EPass{Pass?} UnitPass -->|No| FailBuild[❌ Build Failed] FeaturePass -->|No| FailBuild E2EPass -->|No| FailBuild E2EPass -->|Yes| Coverage[Check Coverage] Coverage -->|< 70%| FailBuild Coverage -->|>= 70%| CodeReview[Code Review] CodeReview --> StyleCheck{Style OK?} StyleCheck -->|No| FailBuild StyleCheck -->|Yes| SecurityCheck{Security OK?} SecurityCheck -->|No| FailBuild SecurityCheck -->|Yes| Merge[✅ Merge] style FailBuild fill:#ff0000,color:#fff style Merge fill:#00ff00,color:#000 ``` ### Test Coverage Targets | Layer | Target | Tools | |-------|--------|-------| | **Backend Unit** | >80% | PHPUnit | | **Backend Feature** | >70% | Pest | | **Frontend Unit** | >70% | Vitest | | **E2E** | Critical paths | Playwright | | **Overall** | >70% | Combined | ### Code Quality Tools **Backend (Laravel):** - **Linting:** Laravel Pint (PSR-12) - **Static Analysis:** PHPStan (level 5+) - **Security:** Laravel Security Checker **Frontend (SvelteKit):** - **Linting:** ESLint + Svelte plugin - **Formatting:** Prettier - **Type Checking:** TypeScript strict mode ### Pre-Commit Hooks ```bash # .git/hooks/pre-commit #!/bin/bash echo "Running pre-commit checks..." # Backend checks cd backend ./vendor/bin/pint --test || exit 1 ./vendor/bin/phpstan analyse || exit 1 php artisan test || exit 1 # Frontend checks cd ../frontend npm run lint || exit 1 npm run type-check || exit 1 npm run test:unit || exit 1 echo "✅ All checks passed!" ``` --- ## Development Workflow ### OpenSpec Integration ```mermaid graph TD Idea[New Feature/Fix] --> Explore[/opsx-explore
Think through problem] Explore --> NewChange[/opsx-new headroom-feature
Create change] NewChange --> Proposal[Write Proposal
Why + What] Proposal --> Specs[Write Specs
Requirements + Scenarios] Specs --> Design[Write Design
How + Decisions] Design --> Tasks[Write Tasks
Implementation checklist] Tasks --> Apply[/opsx-apply
Implement tasks] Apply --> WriteTests[Write Tests
Unit + E2E] WriteTests --> RunTests[Run Tests] RunTests --> TestPass{Pass?} TestPass -->|No| FixCode[Fix Code] FixCode --> RunTests TestPass -->|Yes| Verify[/opsx-verify
Check coverage + standards] Verify --> VerifyPass{Pass?} VerifyPass -->|No| FixCode VerifyPass -->|Yes| Commit[Commit Changes
Granular commits] Commit --> Archive[/opsx-archive
Archive change] Archive --> Done[✅ Feature Complete] style Explore fill:#ffd700 style Apply fill:#00bfff style Verify fill:#ff8c00 style Done fill:#00ff00 ``` ### Branching Strategy **Branch Types:** - `main` - Production-ready code - `develop` - Integration branch (not needed for solo dev initially) - `feature/opsx-` - Feature branches (one per OpenSpec change) - `hotfix/description` - Emergency fixes **Workflow:** ```bash # Start new change git checkout -b feature/opsx-capacity-planning # Implement (from /opsx-apply) # ... make changes, commit granularly ... # Before merge /opsx-verify capacity-planning # Merge git checkout main git merge feature/opsx-capacity-planning git push origin main ``` ### Commit Message Format ``` [Type] Brief description (50 chars max) Detailed explanation (optional, 72 char wrap) Refs: openspec/changes/ ``` **Types:** - `feat`: New feature - `fix`: Bug fix - `refactor`: Code restructuring - `test`: Add/update tests - `docs`: Documentation only - `chore`: Tooling, config, etc. **Examples:** ``` [feat] Add allocation validation rules Implemented over/under allocation detection with RED flag indicators. Validation checks approved estimate vs allocated hours per project. Refs: openspec/changes/allocation-validation ``` --- ## Appendix: Data Flow Examples ### Capacity Planning Flow ```mermaid sequenceDiagram participant Manager participant UI as SvelteKit UI participant API as Laravel API participant DB as PostgreSQL participant Cache as Redis Manager->>UI: Navigate to Capacity Planning UI->>API: GET /api/capacity?month=2026-02 API->>Cache: Check cache Cache-->>API: Cache miss API->>DB: Query team members API->>DB: Query holidays API->>DB: Query PTOs API->>API: Calculate capacity API->>Cache: Store result (TTL: 1h) API-->>UI: Return capacity data UI->>UI: Render calendar + summary Manager->>UI: Mark PTO for Team Member A (2026-02-10 to 2026-02-12) UI->>API: POST /api/pto API->>DB: Insert PTO record API->>Cache: Invalidate capacity:month:2026-02 API-->>UI: Success UI->>API: GET /api/capacity?month=2026-02 API->>DB: Recalculate (cache invalidated) API-->>UI: Updated capacity UI->>UI: Re-render with reduced capacity ``` ### Allocation Flow with Validation ```mermaid sequenceDiagram participant Manager participant UI as SvelteKit UI participant API as Laravel API participant DB as PostgreSQL Manager->>UI: Open allocation matrix (Feb 2026) UI->>API: GET /api/allocations?month=2026-02 API->>DB: Query allocations API-->>UI: Return allocations Manager->>UI: Allocate Dev A: 50h to Project X UI->>UI: Superforms validation (Zod) UI->>API: POST /api/allocations API->>API: Validate request (Laravel) API->>DB: Check project approved estimate DB-->>API: Approved: 100h, Currently allocated: 55h API->>API: Calculate: 55 + 50 = 105h > 100h API->>API: Over-allocation detected! API-->>UI: 422 Unprocessable Entity
{error: "Over-allocated by 5h"} UI->>UI: Show RED flag indicator UI->>Manager: "⚠️ Project X will be over-allocated by 5h" Manager->>UI: Reduce allocation to 45h UI->>API: POST /api/allocations (45h) API->>API: Validate: 55 + 45 = 100h ✅ API->>DB: Insert allocation API->>Cache: Invalidate related caches API-->>UI: Success UI->>UI: Show GREEN indicator (100% allocated) ``` --- **Document Control:** - **Owner:** Santhosh J - **Approver:** Santhosh J - **Next Review:** Post-MVP implementation - **Change History:** - v1.0 (2026-02-17): Initial architecture approved --- *End of Architecture Document*