import { DAY_KEYS, type WeekdayAvailability } from "@capakraken/shared"; export interface ChargeabilityAllocation { startDate: Date; endDate: Date; hoursPerDay: number; } export interface ChargeabilityResult { availableHours: number; bookedHours: number; chargeability: number; // 0-100, rounded } /** Count working hours a resource has available in [start, end] based on their schedule. */ export function computeAvailableHours( availability: WeekdayAvailability, start: Date, end: Date, ): number { let hours = 0; const cur = new Date(start); cur.setHours(0, 0, 0, 0); const endNorm = new Date(end); endNorm.setHours(0, 0, 0, 0); while (cur <= endNorm) { const key = DAY_KEYS[cur.getDay()]; hours += key ? (availability[key] ?? 0) : 0; cur.setDate(cur.getDate() + 1); } return hours; } /** Count booked hours from allocations overlapping [start, end], working days only. */ export function computeBookedHours( availability: WeekdayAvailability, allocations: ChargeabilityAllocation[], start: Date, end: Date, ): number { let hours = 0; const startNorm = new Date(start); startNorm.setHours(0, 0, 0, 0); const endNorm = new Date(end); endNorm.setHours(0, 0, 0, 0); for (const alloc of allocations) { const aStart = new Date(alloc.startDate); aStart.setHours(0, 0, 0, 0); const aEnd = new Date(alloc.endDate); aEnd.setHours(0, 0, 0, 0); const overlapStart = aStart > startNorm ? aStart : startNorm; const overlapEnd = aEnd < endNorm ? aEnd : endNorm; if (overlapStart > overlapEnd) continue; const cur = new Date(overlapStart); while (cur <= overlapEnd) { const key = DAY_KEYS[cur.getDay()]; if (key && (availability[key] ?? 0) > 0) { hours += alloc.hoursPerDay; } cur.setDate(cur.getDate() + 1); } } return hours; } /** Compute chargeability metrics for a resource over a date range. */ export function computeChargeability( availability: WeekdayAvailability, allocations: ChargeabilityAllocation[], start: Date, end: Date, ): ChargeabilityResult { const availableHours = computeAvailableHours(availability, start, end); const bookedHours = computeBookedHours(availability, allocations, start, end); const chargeability = availableHours > 0 ? Math.min(100, Math.round((bookedHours / availableHours) * 100)) : 0; return { availableHours, bookedHours, chargeability }; }