Skip to content

Commit 0e17556

Browse files
authored
Merge pull request #73 from Boyuan-IT-Club/Red_Moon
优化面试时间分配逻辑
2 parents cd671e6 + 946b2be commit 0e17556

File tree

1 file changed

+115
-38
lines changed

1 file changed

+115
-38
lines changed

src/main/java/club/boyuan/official/service/impl/InterviewAssignmentServiceImpl.java

Lines changed: 115 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)