feat(platform): checkpoint current implementation state

This commit is contained in:
2026-04-01 07:42:03 +02:00
parent 3e53471f05
commit 8c5be51251
125 changed files with 10269 additions and 17808 deletions
@@ -36,14 +36,49 @@ export interface ProjectHealthRow {
assignmentCount: number;
spentCents: number;
}>;
derivation?: {
periodStart: string;
periodEnd: string;
calendarContextCount: number;
holidayAwareAssignmentCount: number;
fallbackAssignmentCount: number;
baseSpentCents: number;
adjustedSpentCents: number;
publicHolidayDayEquivalent: number;
publicHolidayCostDeductionCents: number;
absenceDayEquivalent: number;
absenceCostDeductionCents: number;
};
}
const DAY_KEYS: (keyof WeekdayAvailability)[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
function hasAvailability<T extends { availability?: unknown }>(
resource: T | null | undefined,
): resource is T & { availability: WeekdayAvailability } {
return resource !== null && resource !== undefined && resource.availability !== null && resource.availability !== undefined;
}
function toIsoDate(value: Date): string {
return value.toISOString().slice(0, 10);
}
function getDailyAvailabilityHours(
availability: WeekdayAvailability,
date: Date,
): number {
const dayKey = DAY_KEYS[date.getUTCDay()];
return dayKey ? (availability[dayKey] ?? 0) : 0;
}
function toUtcDayStart(value: Date): Date {
return new Date(Date.UTC(
value.getUTCFullYear(),
@@ -66,6 +101,75 @@ function buildLocationKey(input: {
});
}
function summarizeSpentDerivation(input: {
availability: WeekdayAvailability;
startDate: Date;
endDate: Date;
dailyCostCents: number;
context: Awaited<ReturnType<typeof loadDailyAvailabilityContexts>> extends Map<string, infer TValue>
? TValue | undefined
: never;
}) {
const baseSpentCents = calculateEffectiveAllocationCostCents({
availability: input.availability,
startDate: input.startDate,
endDate: input.endDate,
dailyCostCents: input.dailyCostCents,
periodStart: input.startDate,
periodEnd: input.endDate,
context: undefined,
});
const adjustedSpentCents = calculateEffectiveAllocationCostCents({
availability: input.availability,
startDate: input.startDate,
endDate: input.endDate,
dailyCostCents: input.dailyCostCents,
periodStart: input.startDate,
periodEnd: input.endDate,
context: input.context,
});
let publicHolidayDayEquivalent = 0;
let publicHolidayCostDeductionCents = 0;
let absenceDayEquivalent = 0;
let absenceCostDeductionCents = 0;
const cursor = new Date(input.startDate);
cursor.setUTCHours(0, 0, 0, 0);
const end = new Date(input.endDate);
end.setUTCHours(0, 0, 0, 0);
while (cursor <= end) {
const baseHours = getDailyAvailabilityHours(input.availability, cursor);
if (baseHours > 0) {
const isoDate = toIsoDate(cursor);
if (input.context?.holidayDates.has(isoDate)) {
publicHolidayDayEquivalent += 1;
publicHolidayCostDeductionCents += input.dailyCostCents;
} else {
const vacationFraction = Math.min(
1,
Math.max(0, input.context?.vacationFractionsByDate.get(isoDate) ?? 0),
);
if (vacationFraction > 0) {
absenceDayEquivalent += vacationFraction;
absenceCostDeductionCents += input.dailyCostCents * vacationFraction;
}
}
}
cursor.setUTCDate(cursor.getUTCDate() + 1);
}
return {
baseSpentCents,
adjustedSpentCents,
publicHolidayDayEquivalent,
publicHolidayCostDeductionCents: Math.round(publicHolidayCostDeductionCents),
absenceDayEquivalent,
absenceCostDeductionCents: Math.round(absenceCostDeductionCents),
};
}
export async function getDashboardProjectHealth(
db: PrismaClient,
): Promise<ProjectHealthRow[]> {
@@ -147,20 +251,67 @@ export async function getDashboardProjectHealth(
contextEnd,
);
const spentByProject = new Map<string, number>();
const derivationByProject = new Map<string, {
periodStart: string;
periodEnd: string;
calendarContextCount: number;
holidayAwareAssignmentCount: number;
fallbackAssignmentCount: number;
baseSpentCents: number;
adjustedSpentCents: number;
publicHolidayDayEquivalent: number;
publicHolidayCostDeductionCents: number;
absenceDayEquivalent: number;
absenceCostDeductionCents: number;
}>();
const calendarLocationsByProject = new Map<string, Map<string, NonNullable<ProjectHealthRow["calendarLocations"]>[number]>>();
for (const a of assignments) {
const cost = hasAvailability(a.resource)
? calculateEffectiveAllocationCostCents({
const derivation = hasAvailability(a.resource)
? summarizeSpentDerivation({
availability: a.resource.availability as unknown as WeekdayAvailability,
startDate: a.startDate,
endDate: a.endDate,
dailyCostCents: a.dailyCostCents ?? 0,
periodStart: a.startDate,
periodEnd: a.endDate,
context: contexts.get(a.resource.id),
})
: (a.dailyCostCents ?? 0) * calculateInclusiveDays(a.startDate, a.endDate);
: null;
const cost = derivation?.adjustedSpentCents
?? (a.dailyCostCents ?? 0) * calculateInclusiveDays(a.startDate, a.endDate);
spentByProject.set(a.projectId, (spentByProject.get(a.projectId) ?? 0) + cost);
const existingDerivation = derivationByProject.get(a.projectId) ?? {
periodStart: toIsoDate(a.startDate),
periodEnd: toIsoDate(a.endDate),
calendarContextCount: 0,
holidayAwareAssignmentCount: 0,
fallbackAssignmentCount: 0,
baseSpentCents: 0,
adjustedSpentCents: 0,
publicHolidayDayEquivalent: 0,
publicHolidayCostDeductionCents: 0,
absenceDayEquivalent: 0,
absenceCostDeductionCents: 0,
};
if (a.startDate < new Date(existingDerivation.periodStart)) {
existingDerivation.periodStart = toIsoDate(a.startDate);
}
if (a.endDate > new Date(existingDerivation.periodEnd)) {
existingDerivation.periodEnd = toIsoDate(a.endDate);
}
if (derivation) {
existingDerivation.calendarContextCount += 1;
existingDerivation.holidayAwareAssignmentCount += 1;
existingDerivation.baseSpentCents += derivation.baseSpentCents;
existingDerivation.adjustedSpentCents += derivation.adjustedSpentCents;
existingDerivation.publicHolidayDayEquivalent += derivation.publicHolidayDayEquivalent;
existingDerivation.publicHolidayCostDeductionCents += derivation.publicHolidayCostDeductionCents;
existingDerivation.absenceDayEquivalent += derivation.absenceDayEquivalent;
existingDerivation.absenceCostDeductionCents += derivation.absenceCostDeductionCents;
} else {
existingDerivation.fallbackAssignmentCount += 1;
existingDerivation.baseSpentCents += cost;
existingDerivation.adjustedSpentCents += cost;
}
derivationByProject.set(a.projectId, existingDerivation);
if (a.resource) {
const projectLocations = calendarLocationsByProject.get(a.projectId) ?? new Map();
const locationKey = buildLocationKey({
@@ -229,6 +380,7 @@ export async function getDashboardProjectHealth(
(budgetHealth + staffingHealth + timelineHealth) / 3,
);
const remainingBudgetCents = p.budgetCents == null ? null : p.budgetCents - spentCents;
const derivation = derivationByProject.get(p.id);
return {
id: p.id,
@@ -254,6 +406,7 @@ export async function getDashboardProjectHealth(
timelineStatus,
calendarLocations: Array.from(calendarLocationsByProject.get(p.id)?.values() ?? [])
.sort((left, right) => right.spentCents - left.spentCents),
...(derivation ? { derivation } : {}),
};
});
@@ -371,7 +371,7 @@ export async function createEstimateExport(
? (targetVersion.projectSnapshot as Record<string, unknown>)
: null;
const payload = serializeEstimateExport(
const payload = await serializeEstimateExport(
{
estimate,
version: targetVersion,