import { type BudgetForecastRow, getDashboardProjectHealth } from "@capakraken/application"; import { fmtEur } from "../lib/format-utils.js"; type DashboardBudgetForecastCalendarLocation = { countryCode: string | null; countryName: string | null; federalState: string | null; metroCityName: string | null; activeAssignmentCount: number; burnRateCents: number; }; type DashboardBudgetForecastDerivation = { periodStart: string; periodEnd: string; calendarContextCount: number; holidayAwareAssignmentCount: number; fallbackAssignmentCount: number; baseBurnRateCents: number; adjustedBurnRateCents: number; publicHolidayDayEquivalent: number; publicHolidayCostDeductionCents: number; absenceDayEquivalent: number; absenceCostDeductionCents: number; }; export type DashboardBudgetForecastDetail = { forecasts: Array<{ projectId: string | null; projectName: string; shortCode: string; clientId: string | null; clientName: string | null; budget: string; budgetCents: number; spent: string; spentCents: number; remaining: string; remainingCents: number; projected: string; projectedCents: number; burnRate: string; burnRateCents: number; utilization: string; estimatedExhaustionDate: string | null; activeAssignmentCount: number | null; calendarLocations: DashboardBudgetForecastCalendarLocation[]; derivation: DashboardBudgetForecastDerivation | null; burnStatus: "ahead" | "on_track" | "not_started"; }>; }; type DashboardProjectHealthCalendarLocation = { countryCode: string | null; countryName: string | null; federalState: string | null; metroCityName: string | null; assignmentCount: number; spentCents: number; }; type DashboardProjectHealthDerivation = { periodStart: string; periodEnd: string; calendarContextCount: number; holidayAwareAssignmentCount: number; fallbackAssignmentCount: number; baseSpentCents: number; adjustedSpentCents: number; publicHolidayDayEquivalent: number; publicHolidayCostDeductionCents: number; absenceDayEquivalent: number; absenceCostDeductionCents: number; }; export type DashboardProjectHealthDetail = { projects: Array<{ projectId: string; projectName: string; shortCode: string; status: string; overall: number; budget: number; staffing: number; timeline: number; rating: "healthy" | "at_risk" | "critical"; budgetBasis: { budgetCents: number | null; spentCents: number; remainingBudgetCents: number | null; budgetUtilizationPercent: number | null; calendarLocations: DashboardProjectHealthCalendarLocation[]; derivation: DashboardProjectHealthDerivation | null; }; staffingBasis: { demandHeadcountTotal: number; demandHeadcountFilled: number; demandHeadcountOpen: number; demandRequirementCount: number; }; timelineBasis: { plannedEndDate: string | null; daysUntilEndDate: number | null; timelineStatus: "ON_TRACK" | "DUE_SOON" | "OVERDUE" | "UNSCHEDULED"; }; context: { clientId: string | null; clientName: string | null; }; }>; summary: { healthy: number; atRisk: number; critical: number; }; }; export function mapProjectHealthDetailRows( rows: Awaited>, ): DashboardProjectHealthDetail { const projects: DashboardProjectHealthDetail["projects"] = rows .map((project): DashboardProjectHealthDetail["projects"][number] => { const overall = project.compositeScore; const rating: DashboardProjectHealthDetail["projects"][number]["rating"] = overall >= 80 ? "healthy" : overall >= 50 ? "at_risk" : "critical"; return { projectId: project.id, projectName: project.projectName, shortCode: project.shortCode, status: project.status, overall, budget: project.budgetHealth, staffing: project.staffingHealth, timeline: project.timelineHealth, rating, budgetBasis: { budgetCents: project.budgetCents ?? null, spentCents: project.spentCents ?? 0, remainingBudgetCents: project.remainingBudgetCents ?? null, budgetUtilizationPercent: project.budgetUtilizationPercent ?? null, calendarLocations: project.calendarLocations ?? [], derivation: project.derivation ?? null, }, staffingBasis: { demandHeadcountTotal: project.demandHeadcountTotal ?? 0, demandHeadcountFilled: project.demandHeadcountFilled ?? 0, demandHeadcountOpen: project.demandHeadcountOpen ?? 0, demandRequirementCount: project.demandRequirementCount ?? 0, }, timelineBasis: { plannedEndDate: project.plannedEndDate?.toISOString() ?? null, daysUntilEndDate: project.daysUntilEndDate ?? null, timelineStatus: project.timelineStatus ?? "UNSCHEDULED", }, context: { clientId: project.clientId ?? null, clientName: project.clientName ?? null, }, }; }) .sort((left, right) => left.overall - right.overall); return { projects, summary: { healthy: projects.filter((project) => project.rating === "healthy").length, atRisk: projects.filter((project) => project.rating === "at_risk").length, critical: projects.filter((project) => project.rating === "critical").length, }, }; } export function mapBudgetForecastDetailRows( rows: BudgetForecastRow[], ): DashboardBudgetForecastDetail { return { forecasts: rows.map((forecast) => ({ projectId: forecast.projectId ?? null, projectName: forecast.projectName, shortCode: forecast.shortCode, clientId: forecast.clientId, clientName: forecast.clientName, budget: fmtEur(forecast.budgetCents), budgetCents: forecast.budgetCents, spent: fmtEur(forecast.spentCents), spentCents: forecast.spentCents, remaining: fmtEur(forecast.remainingCents ?? (forecast.budgetCents - forecast.spentCents)), remainingCents: forecast.remainingCents ?? (forecast.budgetCents - forecast.spentCents), projected: forecast.burnRate > 0 ? fmtEur(forecast.spentCents + Math.max(0, forecast.budgetCents - forecast.spentCents)) : fmtEur(forecast.spentCents), projectedCents: forecast.burnRate > 0 ? Math.max(forecast.spentCents, forecast.budgetCents) : forecast.spentCents, burnRate: fmtEur(forecast.burnRate), burnRateCents: forecast.burnRate, utilization: `${forecast.pctUsed}%`, estimatedExhaustionDate: forecast.estimatedExhaustionDate, activeAssignmentCount: forecast.activeAssignmentCount ?? null, calendarLocations: forecast.calendarLocations ?? [], derivation: forecast.derivation ?? null, burnStatus: forecast.pctUsed >= 100 ? "ahead" : forecast.burnRate > 0 ? "on_track" : "not_started", })), }; }