using MMDAL.DTO.Scheduling; namespace MMBLL.Scheduling.Engine { // Pure calculation — no DB access, no dependencies. // Score range: 0.0 – 1.0 (higher = better candidate to select). // RedCard EUs (Priority=0) always outscore Normal EUs for the same slot. public static class ScoringEngine { private const double WeightUrgency = 0.40; private const double WeightUtilization = 0.30; private const double WeightChangeover = 0.20; private const double WeightPriority = 0.10; private const double RedCardBonus = 1.00; // horizonMinutes: total working minutes in the scheduling horizon (for urgency normalisation) public static double Score( ExecutionUnit eu, MachineCandidateDTO candidate, SlotProposal proposal, DateTime horizonEnd, int horizonMinutes) { double urgency = ComputeUrgency(proposal.End, eu.Deadline, horizonMinutes); double utilization = ComputeUtilization(candidate.UtilizationRatio); double changeover = candidate.PatternAlreadyMounted ? 1.0 : 0.0; double priority = ComputePriority(eu.Priority); double score = WeightUrgency * urgency + WeightUtilization * utilization + WeightChangeover * changeover + WeightPriority * priority; // RedCard EUs (Priority=0) guaranteed to outscore any non-RedCard if (eu.Priority == 0) score += RedCardBonus; return score; } // Urgency: proportion of slack remaining — less slack = more urgent = higher score private static double ComputeUrgency(DateTime proposedEnd, DateTime deadline, int horizonMinutes) { if (horizonMinutes <= 0) return 1.0; double slackMinutes = (deadline - proposedEnd).TotalMinutes; double slackFraction = Math.Clamp(slackMinutes / horizonMinutes, 0.0, 1.0); return 1.0 - slackFraction; // high urgency = low slack = high score } // Utilization: prefer warmer machines (higher existing utilization = already productive) private static double ComputeUtilization(double utilizationRatio) => Math.Clamp(utilizationRatio, 0.0, 1.0); // Priority: lower priority number = more important = higher score // Maps priority 1..100 → 0.99..0.01; Priority 0 is handled via RedCardBonus private static double ComputePriority(int priority) { if (priority <= 0) return 1.0; // RedCard — bonus applied separately return Math.Clamp(1.0 - (priority / 100.0), 0.0, 1.0); } } }