@@ -146,16 +146,8 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
146146 candidates .add (new CandidateInfo (user , preferredTimes , preferredDepartments , firstDepartment , resume ));
147147 }
148148
149- // 按照偏好满足度排序候选人(偏好越多的候选人优先级越高)
150- candidates .sort ((c1 , c2 ) -> {
151- // 优先考虑偏好时间更多的候选人
152- int timePrefCompare = Integer .compare (c2 .preferredTimes .size (), c1 .preferredTimes .size ());
153- if (timePrefCompare != 0 ) {
154- return timePrefCompare ;
155- }
156- // 如果偏好时间数量相同,则考虑偏好部门数量
157- return Integer .compare (c2 .preferredDepartments .size (), c1 .preferredDepartments .size ());
158- });
149+ // 使用改进的排序策略:基于约束紧迫度排序
150+ candidates = sortCandidatesByUrgency (candidates , userPreferredTimes , departmentSlotAvailability );
159151
160152 // 分配面试时间
161153 List <InterviewAssignmentResultDTO .AssignedInterviewDTO > assignedInterviews = new ArrayList <>();
@@ -166,7 +158,7 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
166158 List <String > preferredTimes = candidate .preferredTimes ;
167159 String department = candidate .firstDepartment ;
168160
169- // 尝试分配面试时间
161+ // 严格按照用户偏好分配面试时间,不使用降级策略
170162 boolean assigned = tryAssignInterviewTime (
171163 user , resume , preferredTimes , department , departmentSlotAvailability , assignedInterviews );
172164
@@ -263,6 +255,115 @@ private String getResumeEmail(Resume resume) {
263255 return user != null ? user .getEmail () : "" ;
264256 }
265257
258+ /**
259+ * 基于约束紧迫度对候选人进行排序,优先满足选择少的候选人
260+ * 这样可以提高整体分配成功率
261+ */
262+ private List <CandidateInfo > sortCandidatesByUrgency (
263+ List <CandidateInfo > candidates ,
264+ Map <Integer , List <String >> userPreferredTimes ,
265+ Map <String , Map <LocalDateTime , Boolean >> departmentSlotAvailability ) {
266+
267+ // 统计每个时间段的竞争激烈程度
268+ Map <String , Integer > timeSlotDemand = calculateTimeSlotDemand (userPreferredTimes );
269+
270+ // 计算每个候选人的紧迫度分数
271+ candidates .forEach (candidate -> {
272+ double urgencyScore = calculateUrgencyScore (candidate , timeSlotDemand , departmentSlotAvailability );
273+ candidate .urgencyScore = urgencyScore ;
274+ logger .debug ("候选人 {} 的紧迫度分数: {}" ,
275+ candidate .user .getUsername (), urgencyScore );
276+ });
277+
278+ // 按紧迫度分数降序排列(分数越高越紧迫,越需要优先分配)
279+ candidates .sort ((c1 , c2 ) -> Double .compare (c2 .urgencyScore , c1 .urgencyScore ));
280+
281+ logger .info ("候选人排序完成,前5名紧迫度分数: {}" ,
282+ candidates .stream ().limit (5 ).map (c -> String .format ("%s:%.2f" ,
283+ c .user .getUsername (), c .urgencyScore )).collect (Collectors .joining (", " )));
284+
285+ return candidates ;
286+ }
287+
288+ /**
289+ * 统计每个时间段的需求人数
290+ */
291+ private Map <String , Integer > calculateTimeSlotDemand (Map <Integer , List <String >> userPreferredTimes ) {
292+ Map <String , Integer > demand = new HashMap <>();
293+
294+ for (List <String > preferredTimes : userPreferredTimes .values ()) {
295+ for (String timeSlot : preferredTimes ) {
296+ demand .merge (timeSlot , 1 , Integer ::sum );
297+ }
298+ }
299+
300+ logger .info ("时间段需求统计: {}" , demand );
301+ return demand ;
302+ }
303+
304+ /**
305+ * 计算候选人的紧迫度分数
306+ * 分数越高表示越需要优先分配
307+ */
308+ private double calculateUrgencyScore (CandidateInfo candidate ,
309+ Map <String , Integer > timeSlotDemand ,
310+ Map <String , Map <LocalDateTime , Boolean >> departmentSlotAvailability ) {
311+
312+ // 基础紧迫度:选择越少越紧迫(1/选择数量)
313+ double baseUrgency = 1.0 / candidate .preferredTimes .size ();
314+
315+ // 稀缺性加权:候选人偏好的时间段竞争越激烈,紧迫度越高
316+ double scarcityWeight = 0.0 ;
317+ for (String timeSlot : candidate .preferredTimes ) {
318+ int demand = timeSlotDemand .getOrDefault (timeSlot , 0 );
319+ int availableSlots = getAvailableSlotsCount (timeSlot , candidate .firstDepartment , departmentSlotAvailability );
320+ if (availableSlots > 0 ) {
321+ // 竞争激烈程度 = 需求人数 / 可用时间槽数
322+ double competitionRatio = (double ) demand / availableSlots ;
323+ scarcityWeight += competitionRatio ;
324+ } else {
325+ // 如果某个时间段已无可用时间槽,给予最高的稀缺性加权
326+ scarcityWeight += 10.0 ;
327+ }
328+ }
329+ scarcityWeight = scarcityWeight / candidate .preferredTimes .size (); // 取平均值
330+
331+ // 综合紧迫度分数:60%基础紧迫度 + 40%稀缺性加权
332+ return baseUrgency * 0.6 + scarcityWeight * 0.4 ;
333+ }
334+
335+ /**
336+ * 计算指定时间段和部门的可用时间槽数量
337+ * @param preferredTime 期望的时间段(如"Day 1 上午")
338+ * @param department 部门名称
339+ * @param departmentSlotAvailability 各部门时间槽可用性映射
340+ * @return 可用时间槽数量
341+ */
342+ private int getAvailableSlotsCount (String preferredTime , String department ,
343+ Map <String , Map <LocalDateTime , Boolean >> departmentSlotAvailability ) {
344+ Map <LocalDateTime , Boolean > slotAvailability = departmentSlotAvailability .get (department );
345+ if (slotAvailability == null ) {
346+ return 0 ;
347+ }
348+
349+ // 获取所有可用的时间槽
350+ List <LocalDateTime > availableSlots = slotAvailability .entrySet ().stream ()
351+ .filter (Map .Entry ::getValue ) // 只考虑可用的时间槽
352+ .map (Map .Entry ::getKey )
353+ .sorted ()
354+ .collect (Collectors .toList ());
355+
356+ // 计算符合期望时间段的时间槽数量
357+ int count = 0 ;
358+ for (LocalDateTime slotTime : availableSlots ) {
359+ if (isSlotMatchPreference (slotTime , preferredTime )) {
360+ count ++;
361+ }
362+ }
363+
364+ return count ;
365+ }
366+
266367 /**
267368 * 候选人信息类,用于存储分配过程中的相关信息
268369 */
@@ -272,13 +373,15 @@ private static class CandidateInfo {
272373 private final List <String > preferredDepartments ;
273374 private final String firstDepartment ;
274375 private final Resume resume ; // 添加简历字段
376+ private double urgencyScore ; // 紧迫度分数
275377
276378 public CandidateInfo (User user , List <String > preferredTimes , List <String > preferredDepartments , String firstDepartment , Resume resume ) {
277379 this .user = user ;
278380 this .preferredTimes = preferredTimes ;
279381 this .preferredDepartments = preferredDepartments ;
280382 this .firstDepartment = firstDepartment ;
281383 this .resume = resume ;
384+ this .urgencyScore = 0.0 ;
282385 }
283386 }
284387
@@ -488,7 +591,7 @@ private Map<String, Map<LocalDateTime, Boolean>> initializeDepartmentSlotAvailab
488591 }
489592
490593 /**
491- * 尝试为用户分配面试时间
594+ * 尝试为用户分配面试时间,严格按照用户偏好进行分配
492595 */
493596 private boolean tryAssignInterviewTime (User user , Resume resume , List <String > preferredTimes , String department ,
494597 Map <String , Map <LocalDateTime , Boolean >> departmentSlotAvailability ,
@@ -600,32 +703,6 @@ private LocalDateTime findAndReserveSlot(String preferredTime, String department
600703 return null ;
601704 }
602705
603- /**
604- * 查找任何可用的时间槽
605- */
606- private LocalDateTime findAnyAvailableSlot (String department ,
607- Map <String , Map <LocalDateTime , Boolean >> departmentSlotAvailability ) {
608- Map <LocalDateTime , Boolean > slotAvailability = departmentSlotAvailability .get (department );
609- if (slotAvailability == null ) {
610- return null ;
611- }
612-
613- // 获取所有可用的时间槽
614- Optional <LocalDateTime > availableSlot = slotAvailability .entrySet ().stream ()
615- .filter (Map .Entry ::getValue ) // 只考虑可用的时间槽
616- .map (Map .Entry ::getKey )
617- .sorted ()
618- .findFirst ();
619-
620- if (availableSlot .isPresent ()) {
621- // 预留该时间槽
622- slotAvailability .put (availableSlot .get (), false );
623- return availableSlot .get ();
624- }
625-
626- return null ;
627- }
628-
629706 /**
630707 * 检查时间槽是否符合用户的期望时间段
631708 * 严格按照用户选择的具体日期和时间段进行匹配
0 commit comments