feat(api): explain chargeability derivation inputs

This commit is contained in:
2026-03-31 22:43:33 +02:00
parent cb363ca5b3
commit 1b5f19c72c
2 changed files with 128 additions and 0 deletions
@@ -168,12 +168,62 @@ export const chargeabilityReportDetailInputSchema = chargeabilityReportInputSche
type ChargeabilityReportInput = z.infer<typeof chargeabilityReportInputSchema>;
type ChargeabilityReportDetailInput = z.infer<typeof chargeabilityReportDetailInputSchema>;
type ChargeabilityExplainabilityInput = ChargeabilityReportInput & {
resourceQuery?: string | undefined;
resourceLimit?: number | undefined;
};
type ChargeabilityReportDbClient = Pick<
PrismaClient,
"assignment" | "resource" | "project" | "vacation" | "holidayCalendar" | "systemSettings"
>;
const CHARGEABILITY_LOCATION_FIELDS = [
"country",
"federalState",
"city",
"orgUnit",
"managementLevelGroup",
"managementLevel",
] as const;
const CHARGEABILITY_DERIVATION_FIELDS = [
"baseAvailableHours",
"publicHolidayCount",
"publicHolidayWorkdayCount",
"publicHolidayHoursDeduction",
"absenceDayEquivalent",
"absenceHoursDeduction",
"effectiveAvailableHours",
] as const;
function buildChargeabilityExplainability(input: ChargeabilityExplainabilityInput) {
const activeFilters = [
...(input.orgUnitId ? ["orgUnitId"] : []),
...(input.managementLevelGroupId ? ["managementLevelGroupId"] : []),
...(input.countryId ? ["countryId"] : []),
...(input.resourceQuery ? ["resourceQuery"] : []),
...(input.includeProposed ? ["includeProposed"] : []),
];
return {
locationFields: [...CHARGEABILITY_LOCATION_FIELDS],
monthDerivationFields: [...CHARGEABILITY_DERIVATION_FIELDS],
activeFilters,
formulas: {
sah: "baseAvailableHours - publicHolidayHoursDeduction - absenceHoursDeduction = effectiveAvailableHours",
chargeabilityPct: "chargeabilityHours / sahHours",
targetHours: "sahHours * targetPct",
gapHours: "chargeabilityHours - targetHours",
},
notes: [
"Location fields explain why two resources can have different SAH in the same month because country, federal state, and city holidays may differ.",
"Holiday deductions and absence deductions are tracked separately; absence does not deduct days that are already public holidays.",
"Include proposed work changes chargeability ratios and hours, but it does not change holiday or absence-based SAH derivation.",
],
};
}
async function queryChargeabilityReport(
db: ChargeabilityReportDbClient,
input: ChargeabilityReportInput,
@@ -227,6 +277,7 @@ async function queryChargeabilityReport(
chargeabilityRatio: 0,
targetRatio: 0,
})),
explainability: buildChargeabilityExplainability(input),
};
}
@@ -388,6 +439,7 @@ async function queryChargeabilityReport(
monthKeys,
resources: anonymizeResources(resourceRows, directory),
groupTotals,
explainability: buildChargeabilityExplainability(input),
};
}
@@ -462,6 +514,7 @@ export function buildChargeabilityReportDetail(
resourceCount: matchingResources.length,
returnedResourceCount: resources.length,
truncated: resources.length < matchingResources.length,
explainability: buildChargeabilityExplainability(input),
resources,
};
}