Engine (packages/engine): - calculateShoringRatio() pure function: onshore/offshore hours, country breakdown, threshold check, weighted by hours not headcount - 12 unit tests: empty, 100% onshore/offshore, mixed ratios, custom threshold, case-insensitive, unknown country, FTE weighting Schema: - Project.shoringThreshold (default 55%) — per-project configurable - Project.onshoreCountryCode (default "DE") — configurable onshore country API (project router): - getShoringRatio query: loads assignments with resource.country, computes ratio, returns full breakdown - update mutation: accepts shoringThreshold + onshoreCountryCode UI: - ShoringIndicator: stacked horizontal bar with country segments, severity badge (green/yellow/red), hover tooltip, dark theme - ShoringBadge: mini colored dot + % for project list column - ProjectModal: "Max Offshore %" number input - Project detail: indicator after budget status card - Project list: "Shoring" column (default hidden, toggleable) AI Assistant: - get_shoring_ratio tool: human-readable breakdown with threshold alert Colors: green (<threshold-10), yellow (threshold-10 to threshold), red (>=threshold) Default: 55% offshore threshold, "DE" as onshore country Co-Authored-By: claude-flow <ruv@ruv.net>
7.4 KiB
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.prismashoringThreshold 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.tsinterface 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<string, { hours: number; pct: number; resourceCount: number }>; } 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.tsgetShoringRatioQuery (protectedProcedure):- Input:
{ projectId: string } - Laedt Assignments mit Resource.country
- Ruft
calculateShoringRatio()auf - Returns:
ShoringResult
- Input:
updateMutation erweitern:shoringThresholdim Input akzeptierengetByIderweitern:shoringThresholdim 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)
- Kompakte Anzeige:
-
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<ShoringIndicator projectId={id} />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.tsget_shoring_ratioTool:- Input:
{ projectId: string } - Returns: menschenlesbare Aufschluesselung
- "Project X: 45% onshore (DE), 55% offshore (ES 30%, IN 25%). ⚠ Above 55% threshold."
- Input:
-
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:unitlaeuft 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
countryIdgesetzt → 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
- Nur "DE" als Onshore? Oder soll der Onshore-Laendercode konfigurierbar sein? → Vorschlag: Default "DE", aber konfigurierbar pro Projekt
- Welche Assignment-Status zaehlen? Nur CONFIRMED? Auch PROPOSED? → Vorschlag: CONFIRMED + PROPOSED (beide relevant fuer Planung)
- Soll der Indikator auch in der Timeline sichtbar sein? → Vorschlag: Erst mal nur Projektliste + Detail-Seite