feat(planning): ship holiday-aware planning and assistant upgrades

This commit is contained in:
2026-03-28 22:49:28 +01:00
parent 2a005794e7
commit 4f48afe7b4
151 changed files with 17738 additions and 1940 deletions
@@ -0,0 +1,243 @@
export interface AssistantInsightMetric {
label: string;
value: string;
tone?: "neutral" | "good" | "warn" | "danger" | "info";
}
export interface AssistantInsightSection {
title: string;
metrics: AssistantInsightMetric[];
}
export interface AssistantInsight {
kind: "chargeability" | "resource_match" | "holiday_region" | "resource_holidays";
title: string;
subtitle?: string;
metrics: AssistantInsightMetric[];
sections?: AssistantInsightSection[];
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function asString(value: unknown): string | null {
return typeof value === "string" && value.trim() ? value : null;
}
function asNumber(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function formatHours(value: unknown): string | null {
const num = asNumber(value);
return num == null ? null : `${num.toFixed(num % 1 === 0 ? 0 : 1)} h`;
}
function formatDays(value: unknown): string | null {
const num = asNumber(value);
return num == null ? null : `${num.toFixed(num % 1 === 0 ? 0 : 1)} d`;
}
function pushMetric(
metrics: AssistantInsightMetric[],
label: string,
value: string | null,
tone?: AssistantInsightMetric["tone"],
) {
if (!value) return;
metrics.push({ label, value, ...(tone ? { tone } : {}) });
}
function createLocationLabel(locationContext: Record<string, unknown> | undefined): string | null {
if (!locationContext) return null;
const parts = [
asString(locationContext.metroCity),
asString(locationContext.federalState),
asString(locationContext.country),
asString(locationContext.countryCode),
].filter(Boolean);
return parts.length > 0 ? parts.join(", ") : null;
}
function buildChargeabilityInsight(data: Record<string, unknown>): AssistantInsight | null {
const resource = asString(data.resource);
const month = asString(data.month);
if (!resource || !month) return null;
const holidaySummary = isRecord(data.holidaySummary) ? data.holidaySummary : undefined;
const absenceSummary = isRecord(data.absenceSummary) ? data.absenceSummary : undefined;
const capacityBreakdown = isRecord(data.capacityBreakdown) ? data.capacityBreakdown : undefined;
const locationContext = isRecord(data.locationContext) ? data.locationContext : undefined;
const chargeabilityPct = asNumber(data.chargeabilityPct);
const targetPct = asNumber(data.targetPct);
const metrics: AssistantInsightMetric[] = [];
pushMetric(metrics, "Chargeability", asString(data.chargeability), chargeabilityPct == null || targetPct == null
? "info"
: chargeabilityPct >= targetPct ? "good" : "warn");
pushMetric(metrics, "Available", formatHours(data.availableHours));
pushMetric(metrics, "Booked", formatHours(data.bookedHours));
pushMetric(metrics, "Unassigned", formatHours(data.unassignedHours));
pushMetric(metrics, "Target", formatHours(data.targetHours));
pushMetric(metrics, "Holidays", formatDays(holidaySummary?.workdayCount ?? holidaySummary?.count));
const sections: AssistantInsightSection[] = [];
const basisMetrics: AssistantInsightMetric[] = [];
pushMetric(basisMetrics, "Location", createLocationLabel(locationContext), "info");
pushMetric(basisMetrics, "Base working days", formatDays(data.baseWorkingDays));
pushMetric(basisMetrics, "Effective working days", formatDays(data.workingDays));
pushMetric(basisMetrics, "Base capacity", formatHours(data.baseAvailableHours));
if (basisMetrics.length > 0) {
sections.push({ title: "Basis", metrics: basisMetrics });
}
const deductionMetrics: AssistantInsightMetric[] = [];
pushMetric(deductionMetrics, "Holiday deduction", formatHours(holidaySummary?.hoursDeduction ?? capacityBreakdown?.holidayHoursDeduction), "warn");
pushMetric(deductionMetrics, "Absence deduction", formatHours(absenceSummary?.hoursDeduction ?? capacityBreakdown?.absenceHoursDeduction), "warn");
pushMetric(deductionMetrics, "Absence days", formatDays(absenceSummary?.dayEquivalent));
if (deductionMetrics.length > 0) {
sections.push({ title: "Deductions", metrics: deductionMetrics });
}
return {
kind: "chargeability",
title: `${resource} · ${month}`,
subtitle: "Holiday-aware monthly capacity",
metrics,
...(sections.length > 0 ? { sections } : {}),
};
}
function buildHolidayRegionInsight(data: Record<string, unknown>): AssistantInsight | null {
const locationContext = isRecord(data.locationContext) ? data.locationContext : undefined;
const periodStart = asString(data.periodStart);
const periodEnd = asString(data.periodEnd);
const metrics: AssistantInsightMetric[] = [];
pushMetric(metrics, "Region", createLocationLabel(locationContext), "info");
pushMetric(metrics, "Resolved holidays", asNumber(data.count)?.toString() ?? null);
pushMetric(metrics, "Period", periodStart && periodEnd ? `${periodStart} to ${periodEnd}` : null);
const summary = isRecord(data.summary) ? data.summary : undefined;
const scopeItems = Array.isArray(summary?.byScope) ? summary.byScope : [];
const scopeMetrics = scopeItems
.map((item) => {
if (!isRecord(item)) return null;
const scope = asString(item.scope);
const count = asNumber(item.count);
if (!scope || count == null) return null;
return { label: scope, value: String(count) } satisfies AssistantInsightMetric;
})
.filter((item): item is AssistantInsightMetric => item !== null);
return {
kind: "holiday_region",
title: createLocationLabel(locationContext) ?? "Regional holidays",
subtitle: "Resolved public holiday set",
metrics,
...(scopeMetrics.length > 0 ? { sections: [{ title: "Scopes", metrics: scopeMetrics }] } : {}),
};
}
function buildResourceHolidayInsight(data: Record<string, unknown>): AssistantInsight | null {
const resource = isRecord(data.resource) ? data.resource : undefined;
const summary = isRecord(data.summary) ? data.summary : undefined;
const periodStart = asString(data.periodStart);
const periodEnd = asString(data.periodEnd);
const metrics: AssistantInsightMetric[] = [];
pushMetric(metrics, "Employee", asString(resource?.name) ?? asString(resource?.eid));
pushMetric(metrics, "Location", createLocationLabel(resource), "info");
pushMetric(metrics, "Resolved holidays", asNumber(data.count)?.toString() ?? null);
pushMetric(metrics, "Period", periodStart && periodEnd ? `${periodStart} to ${periodEnd}` : null);
const scopeItems = Array.isArray(summary?.byScope) ? summary.byScope : [];
const scopeMetrics = scopeItems
.map((item) => {
if (!isRecord(item)) return null;
const scope = asString(item.scope);
const count = asNumber(item.count);
if (!scope || count == null) return null;
return { label: scope, value: String(count) } satisfies AssistantInsightMetric;
})
.filter((item): item is AssistantInsightMetric => item !== null);
return {
kind: "resource_holidays",
title: `${asString(resource?.name) ?? "Resource"} holidays`,
subtitle: "Location-specific holiday resolution",
metrics,
...(scopeMetrics.length > 0 ? { sections: [{ title: "Scopes", metrics: scopeMetrics }] } : {}),
};
}
function buildResourceMatchInsight(data: Record<string, unknown>): AssistantInsight | null {
const project = isRecord(data.project) ? data.project : undefined;
const period = isRecord(data.period) ? data.period : undefined;
const bestMatch = isRecord(data.bestMatch) ? data.bestMatch : undefined;
if (!project || !period || !bestMatch) return null;
const remainingHours = asNumber(bestMatch.remainingHours);
const remainingHoursPerDay = asNumber(bestMatch.remainingHoursPerDay);
const lcr = asString(bestMatch.lcr);
const holidaySummary = isRecord(bestMatch.holidaySummary) ? bestMatch.holidaySummary : undefined;
const absenceSummary = isRecord(bestMatch.absenceSummary) ? bestMatch.absenceSummary : undefined;
const capacityBreakdown = isRecord(bestMatch.capacityBreakdown) ? bestMatch.capacityBreakdown : undefined;
const metrics: AssistantInsightMetric[] = [];
pushMetric(metrics, "Best match", asString(bestMatch.name) ?? asString(bestMatch.eid), "good");
pushMetric(metrics, "Project", asString(project.name) ?? asString(project.shortCode));
pushMetric(metrics, "Remaining", formatHours(remainingHours), remainingHours != null && remainingHours > 0 ? "good" : "warn");
pushMetric(metrics, "Per workday", formatHours(remainingHoursPerDay));
pushMetric(metrics, "LCR", lcr);
pushMetric(metrics, "Holiday deduction", formatHours(holidaySummary?.hoursDeduction), "warn");
const sections: AssistantInsightSection[] = [];
const profileMetrics: AssistantInsightMetric[] = [];
pushMetric(profileMetrics, "Role", asString(bestMatch.role));
pushMetric(profileMetrics, "Chapter", asString(bestMatch.chapter));
pushMetric(profileMetrics, "Location", createLocationLabel(bestMatch), "info");
pushMetric(profileMetrics, "Candidate pool", asNumber(data.candidateCount)?.toString() ?? null);
if (profileMetrics.length > 0) {
sections.push({ title: "Selection", metrics: profileMetrics });
}
const basisMetrics: AssistantInsightMetric[] = [];
pushMetric(basisMetrics, "Window", asString(period.startDate) && asString(period.endDate) ? `${asString(period.startDate)} to ${asString(period.endDate)}` : null);
pushMetric(basisMetrics, "Ranking", asString(period.rankingMode));
pushMetric(basisMetrics, "Min/day", formatHours(period.minHoursPerDay));
pushMetric(basisMetrics, "Base capacity", formatHours(capacityBreakdown?.baseAvailableHours ?? bestMatch.baseAvailableHours));
pushMetric(basisMetrics, "Effective capacity", formatHours(bestMatch.availableHours));
pushMetric(basisMetrics, "Absence deduction", formatHours(absenceSummary?.hoursDeduction ?? capacityBreakdown?.absenceHoursDeduction), "warn");
if (basisMetrics.length > 0) {
sections.push({ title: "Capacity basis", metrics: basisMetrics });
}
return {
kind: "resource_match",
title: `${asString(project.shortCode) ?? asString(project.name) ?? "Project"} staffing`,
subtitle: "Holiday-aware best-fit resource",
metrics,
...(sections.length > 0 ? { sections } : {}),
};
}
export function buildAssistantInsight(toolName: string, data: unknown): AssistantInsight | null {
if (!isRecord(data)) return null;
switch (toolName) {
case "get_chargeability":
return buildChargeabilityInsight(data);
case "find_best_project_resource":
return buildResourceMatchInsight(data);
case "list_holidays_by_region":
return buildHolidayRegionInsight(data);
case "get_resource_holidays":
return buildResourceHolidayInsight(data);
default:
return null;
}
}