# Nearshore-Ratio Indikator — Plan ## Anforderungsanalyse **Was:** Ein Nearshore/Offshore-Ratio Indikator pro Projekt, der zeigt wieviel % der gebuchten Ressourcen Standort Deutschland vs. andere Standorte haben. **Kernkonzept:** - **Onshore** = Resource hat `countryId` → Country mit Code "DE" (Deutschland) - **Offshore/Nearshore** = Resource hat `countryId` → Country mit Code != "DE" (oder kein Country gesetzt) - **Shoring-Ratio** = (Offshore-Stunden / Gesamt-Stunden) * 100 - **Threshold** = Default 55%, pro Projekt aenderbar - **Anzeige** = Farbiger Indikator (gruen < Threshold, rot >= Threshold) **Datenquelle:** Assignments eines Projekts → Resource.countryId → Country.code --- ## Betroffene Pakete & Dateien | Paket | Dateien | Art der Aenderung | |-------|---------|------------------| | `packages/db` | `prisma/schema.prisma` | **edit** — `shoringThreshold Int?` auf Project | | `packages/engine` | `src/allocation/shoring-ratio.ts` | **create** — Pure Berechnung | | `packages/engine` | `src/__tests__/shoring-ratio.test.ts` | **create** — Unit Tests | | `packages/api` | `src/router/project.ts` | **edit** — `getShoringRatio` Query + Threshold im Update | | `packages/api` | `src/router/assistant-tools.ts` | **edit** — `get_shoring_ratio` AI Tool | | `apps/web` | `src/components/projects/ShoringIndicator.tsx` | **create** — Visueller Indikator | | `apps/web` | `src/app/(app)/projects/[id]/page.tsx` | **edit** — Indikator auf Detail-Seite | | `apps/web` | `src/app/(app)/projects/ProjectsClient.tsx` | **edit** — Indikator-Spalte in Projektliste | | `apps/web` | `src/components/projects/ProjectModal.tsx` | **edit** — Threshold-Feld im Edit | | `apps/web` | `src/components/dashboard/widgets/ProjectHealthWidget.tsx` | **edit** — Optional: Shoring in Health-Score | --- ## Task-Liste ### Phase 1: Schema + Engine (sequenziell) - [ ] **Task 1:** Schema erweitern → `packages/db/prisma/schema.prisma` - `shoringThreshold Int? @default(55)` auf dem Project Model - Repraesntiert den kritischen Shoring-Prozentsatz (Default 55%) - `prisma db push` - [ ] **Task 2:** Pure Shoring-Ratio Berechnung → `packages/engine/src/allocation/shoring-ratio.ts` ```typescript interface ShoringInput { resourceId: string; countryCode: string | null; // "DE", "ES", "IN", etc. hoursPerDay: number; workingDays: number; // oder totalHours direkt } interface ShoringResult { totalHours: number; onshoreHours: number; // countryCode === "DE" offshoreHours: number; // countryCode !== "DE" offshoreRatio: number; // 0-100 (%) onshoreRatio: number; // 0-100 (%) threshold: number; // Default 55 isAboveThreshold: boolean; byCountry: Record; } export function calculateShoringRatio( assignments: ShoringInput[], threshold?: number, // Default 55 onshoreCountryCode?: string, // Default "DE" ): ShoringResult ``` - [ ] **Task 3:** Unit Tests → `packages/engine/src/__tests__/shoring-ratio.test.ts` - 100% Deutsche Ressourcen → offshoreRatio = 0, isAboveThreshold = false - 100% Offshore → offshoreRatio = 100, isAboveThreshold = true - 50/50 Mix → offshoreRatio = 50, isAboveThreshold = false (< 55) - 60/40 Mix → offshoreRatio = 60, isAboveThreshold = true (> 55) - Custom Threshold (30%) → 40% Offshore = above threshold - Leere Assignments → offshoreRatio = 0 - Resource ohne Country → zaehlt als Offshore - [ ] **Task 4:** Export → `packages/engine/src/allocation/index.ts` ### Phase 2: API + UI (parallel) - [ ] **Task 5:** API Query → `packages/api/src/router/project.ts` - `getShoringRatio` Query (protectedProcedure): - Input: `{ projectId: string }` - Laedt Assignments mit Resource.country - Ruft `calculateShoringRatio()` auf - Returns: `ShoringResult` - `update` Mutation erweitern: `shoringThreshold` im Input akzeptieren - `getById` erweitern: `shoringThreshold` im Response zurueckgeben - [ ] **Task 6:** Visueller Indikator → `ShoringIndicator.tsx` - Kompakte Anzeige: ``` [====DE 45%====|==ES 30%==|IN 25%] ⚠ 55% offshore (Limit: 55%) ``` - Stacked horizontal bar: Laender-Segmente farbig - Gruen/Gelb/Rot Badge je nach Threshold - Gruen: < Threshold - 10pp - Gelb: Threshold - 10pp bis Threshold - Rot: >= Threshold - Tooltip: Aufschluesselung nach Land - Props: `projectId: string` (laedt Daten selbst via tRPC) - [ ] **Task 7:** Threshold im ProjectModal → `ProjectModal.tsx` - Neues Feld: "Max Offshore %" (Number Input, 0-100, Default 55) - Unter "Budget" oder als eigene Section "Shoring" - Label: "Maximum Offshore/Nearshore Ratio (%)" - Hinweis: "Alert when offshore staffing exceeds this percentage" - [ ] **Task 8:** Indikator auf Projekt-Detail-Seite → `projects/[id]/page.tsx` - `` neben dem Budget-Status - Oder als eigene Karte unter den Budget-Karten - [ ] **Task 9:** Indikator-Spalte in Projektliste → `ProjectsClient.tsx` - Neue Spalte "Shoring" mit Mini-Indikator (nur Badge gruen/gelb/rot + %) - InfoTooltip: "Offshore staffing ratio vs project threshold" ### Phase 3: AI + Dashboard (parallel) - [ ] **Task 10:** AI Assistant Tool → `assistant-tools.ts` - `get_shoring_ratio` Tool: - Input: `{ projectId: string }` - Returns: menschenlesbare Aufschluesselung - "Project X: 45% onshore (DE), 55% offshore (ES 30%, IN 25%). ⚠ Above 55% threshold." - [ ] **Task 11:** Optional: Dashboard Widget oder ProjectHealth Integration - Shoring-Warnung in ProjectHealthWidget einbauen - Oder neues Mini-Widget "Shoring Alerts" (Projekte ueber Threshold) --- ## Abhaengigkeiten ``` Task 1 (Schema) → Task 2 (Engine) → Task 3 (Tests) → Task 4 (Export) Task 4 → Task 5 (API) Task 5 → Task 6 + Task 7 + Task 8 + Task 9 (alle parallel) Task 5 → Task 10 (AI Tool) Task 5 → Task 11 (Dashboard) ``` - Tasks 6-9 **vollstaendig parallel** (verschiedene Dateien) - Tasks 10-11 parallel zu Tasks 6-9 --- ## Akzeptanzkriterien - [ ] `pnpm test:unit` laeuft gruen (inkl. neue shoring-ratio Tests) - [ ] `pnpm --filter @planarchy/web exec tsc --noEmit` — keine neuen Errors - [ ] **Projekt-Detail:** Shoring-Indikator sichtbar mit Laender-Aufschluesselung - [ ] **Projektliste:** Shoring-Badge-Spalte (gruen/gelb/rot) - [ ] **Projekt-Edit:** Threshold aenderbar (Default 55%) - [ ] **AI Assistant:** "Wie ist der Shoring-Mix bei Projekt X?" liefert Antwort - [ ] **Farben:** Gruen < 45%, Gelb 45-55%, Rot >= 55% (bei Default-Threshold) - [ ] **Resource ohne Country:** Zaehlt als Offshore - [ ] **Projekt ohne Assignments:** Zeigt "No data" statt Fehler --- ## Risiken & offene Fragen ### Risiken - **Country-Daten unvollstaendig:** Viele Resources haben moeglicherweise kein `countryId` gesetzt → Mitigation: Zaehlt als Offshore + Warning "X resources have no country assigned" - **Stunden vs Headcount:** Soll nach Stunden oder Koepfen gemessen werden? → Vorschlag: Stunden (gewichtet), da ein 50%-FTE weniger zaehlt als ein 100%-FTE ### Offene Fragen 1. **Nur "DE" als Onshore?** Oder soll der Onshore-Laendercode konfigurierbar sein? → Vorschlag: Default "DE", aber konfigurierbar pro Projekt 2. **Welche Assignment-Status zaehlen?** Nur CONFIRMED? Auch PROPOSED? → Vorschlag: CONFIRMED + PROPOSED (beide relevant fuer Planung) 3. **Soll der Indikator auch in der Timeline sichtbar sein?** → Vorschlag: Erst mal nur Projektliste + Detail-Seite