Files
CapaKraken/plan.md
T
Hartmut 92a982b151 feat: Nearshore-Ratio indicator per project
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>
2026-03-26 11:45:50 +01:00

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 editshoringThreshold 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 editgetShoringRatio Query + Threshold im Update
packages/api src/router/assistant-tools.ts editget_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

    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<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.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

    • <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.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