From c187397c3e126181b63a3ed12571a4b043d2f5b3 Mon Sep 17 00:00:00 2001 From: luotaiqian <1147642922@qq.com> Date: Thu, 16 Jul 2026 15:17:12 +0800 Subject: [PATCH] init --- .../utils/ComplianceCheckUtil.java | 219 +++++------------- 1 file changed, 58 insertions(+), 161 deletions(-) diff --git a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java index 4014eaf..f09760d 100644 --- a/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java +++ b/safety-eval-infrastructure/src/main/java/org/qinan/safetyeval/infrastructure/utils/ComplianceCheckUtil.java @@ -21,6 +21,7 @@ import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import static org.qinan.safetyeval.domain.exception.ErrorCode.PERSON_NOT_ENOUGH; @@ -361,32 +362,33 @@ public class ComplianceCheckUtil { return Pair.of(true, null); } - // 1. 汇总每个专业能力code的要求人数 - Map requiredMap = standardsDOList.stream() - .collect(Collectors.toMap( - IndustryProfessionalStandardsDO::getProfessionalCapabilityCode, - s -> s.getPersonNum() != null ? s.getPersonNum().intValue() : 0, - Integer::sum, - LinkedHashMap::new)); - - // 2. 获取本行业备案人员集合(确定参检人员范围) - Set filingPersonIds = industryProfessionalPersonCos.stream() - .map(IndustryProfessionalPersonCo::getSourcePersonnelId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + Map requiredMap = new HashMap<>(); + for (IndustryProfessionalStandardsDO standardsDO : standardsDOList) { + requiredMap.put(standardsDO.getProfessionalCapabilityCode(), standardsDO.getPersonNum()); + } // 3. 构建人员→可担任专业能力code集合(同一人可能具备多个专业能力) Map> personCapabilityMap = new HashMap<>(); - for (OrgPersonnelCertCapabilityComplianceCheckCO cert : certCapabilityList) { - Long pid = cert.getPersonnelId(); - if (pid != null && filingPersonIds.contains(pid) && cert.getProfessionalCapabilityCode() != null) { - personCapabilityMap.computeIfAbsent(pid, k -> new HashSet<>()) - .add(cert.getProfessionalCapabilityCode()); - } + for (IndustryProfessionalPersonCo industryProfessionalPersonCo : industryProfessionalPersonCos) { + Set codeList = personCapabilityMap + .getOrDefault(industryProfessionalPersonCo.getSourcePersonnelId(), new HashSet<>()); + codeList.add(industryProfessionalPersonCo.getProfessionalCapabilityCode()); + personCapabilityMap.put(industryProfessionalPersonCo.getSourcePersonnelId() + , codeList); } - // 4. 一人一岗最优分配 - Map assignedMap = assignPersonnelByMaxFlow(personCapabilityMap, requiredMap); + for (OrgPersonnelCertCapabilityComplianceCheckCO industryProfessionalPersonCo : certCapabilityList) { + Set codeList = personCapabilityMap + .getOrDefault(industryProfessionalPersonCo.getPersonnelId(), new HashSet<>()); + codeList.add(industryProfessionalPersonCo.getProfessionalCapabilityCode()); + personCapabilityMap.put(industryProfessionalPersonCo.getPersonnelId(), codeList); + } + + + + // 数据来源:industryProfessionalPersonCos(备案人员自带专业能力)+ certCapabilityList(证书专业能力) + // 4. 贪心分配(一人一岗):按可选人数从少到多处理,优先满足最紧缺的专业能力 + Map assignedMap = assignPersonnelGreedy(personCapabilityMap, requiredMap); // 5. 判断是否所有专业能力都达标,并生成失败消息 boolean allMet = true; @@ -394,9 +396,9 @@ public class ComplianceCheckUtil { String industryName = industryEnum != null ? industryEnum.getValue() : standardsDOList.get(0).getIndustryCode(); StringBuilder failureMsg = new StringBuilder(); - for (Map.Entry entry : requiredMap.entrySet()) { + for (Map.Entry entry : requiredMap.entrySet()) { String capCode = entry.getKey(); - int required = entry.getValue(); + long required = entry.getValue(); int assigned = assignedMap.getOrDefault(capCode, 0); if (assigned < required) { allMet = false; @@ -407,7 +409,7 @@ public class ComplianceCheckUtil { .append(capName) .append(" 需要") .append(required) - .append("人,因一人一岗最多可分配") + .append("人,当前可用") .append(assigned) .append("人;"); } @@ -416,157 +418,52 @@ public class ComplianceCheckUtil { } /** - * 一人一岗最优分配算法(最大流 / Dinic) + * 一人一岗贪心分配 *

- * 每个人员最多分配到一个专业能力岗位,每个岗位有最低人数要求, - * 求解在"一人一岗"约束下各专业能力实际可分配到的人数。 + * 每个人员最多分配到一个专业能力岗位,人员不能重复使用。 + * 按可选人员池从小到大排序优先处理最紧缺的专业能力,尽量满足所有要求。 *

- * 网络结构: source → 人员节点(cap=1) → 专业能力节点(cap=1) → sink(cap=要求人数) + * 注意:贪心为启发式,不保证全局最优,但满足"只需判断能否达标"的场景。 * * @param personCapabilityMap 人员→可担任专业能力code集合(同一人可能具备多个专业能力) * @param requiredMap 专业能力code→要求人数 * @return 专业能力code→实际分配人数 */ - private static Map assignPersonnelByMaxFlow(Map> personCapabilityMap - , Map requiredMap) { - List personIds = new ArrayList<>(personCapabilityMap.keySet()); - List capCodes = new ArrayList<>(requiredMap.keySet()); - Map capIndex = new HashMap<>(); - for (int j = 0; j < capCodes.size(); j++) { - capIndex.put(capCodes.get(j), j); - } - - int numPersons = personIds.size(); - int numCaps = capCodes.size(); - int source = 0; - int sink = numPersons + numCaps + 1; - MaxFlow mf = new MaxFlow(sink + 1); - - // source → person (capacity 1: 每人最多分配一个岗位) - for (int i = 0; i < numPersons; i++) { - mf.addEdge(source, 1 + i, 1); - } - // person → capability (capacity 1: 该人可担任该专业能力) - for (int i = 0; i < numPersons; i++) { - Set caps = personCapabilityMap.get(personIds.get(i)); - if (caps != null) { - for (String cap : caps) { - Integer cj = capIndex.get(cap); - if (cj != null) { - mf.addEdge(1 + i, 1 + numPersons + cj, 1); - } + private static Map assignPersonnelGreedy(Map> personCapabilityMap + , Map requiredMap) { + // 构建 专业能力code → 可用人员集合 + Map> capabilityPersonMap = new HashMap<>(); + for (Map.Entry> entry : personCapabilityMap.entrySet()) { + Long pid = entry.getKey(); + for (String cap : entry.getValue()) { + if (requiredMap.containsKey(cap)) { + capabilityPersonMap.computeIfAbsent(cap, k -> new HashSet<>()).add(pid); } } } - // capability → sink (capacity = 该专业能力要求人数) - int[] capEdgeIds = new int[numCaps]; - for (int j = 0; j < numCaps; j++) { - capEdgeIds[j] = mf.addEdgeReturnId(1 + numPersons + j, sink, requiredMap.get(capCodes.get(j))); - } - // 求解最大流 - mf.maxFlow(source, sink); + // 按可选人数从少到多排序,优先满足最紧缺的专业能力 + List capOrder = new ArrayList<>(requiredMap.keySet()); + capOrder.sort(Comparator.comparingInt(c -> capabilityPersonMap.getOrDefault(c, Collections.emptySet()).size())); - // 提取每个专业能力的实际分配人数 + Set usedPersonIds = new HashSet<>(); Map assignedMap = new LinkedHashMap<>(); - for (int j = 0; j < numCaps; j++) { - assignedMap.put(capCodes.get(j), mf.getFlow(capEdgeIds[j])); + for (String capCode : capOrder) { + long required = requiredMap.get(capCode); + Set candidates = capabilityPersonMap.get(capCode); + int assigned = 0; + if (candidates != null) { + for (Long pid : candidates) { + if (assigned >= required) { + break; + } + if (usedPersonIds.add(pid)) { + assigned++; + } + } + } + assignedMap.put(capCode, assigned); } return assignedMap; } - - /** - * Dinic 最大流算法(内部使用) - *

- * 用于求解"一人一岗"分配问题:将人员分配到专业能力岗位, - * 每人最多分配一个岗位,每个岗位有最低人数要求。 - *

- * 网络结构: source → 人员节点(cap=1) → 专业能力节点(cap=1) → sink(cap=要求人数) - * 若最大流 = 所有人数要求之和,则存在合法分配方案。 - */ - private static class MaxFlow { - private final int n; - private final List edges; - private final List> adj; - private final int[] level; - private final int[] iter; - - MaxFlow(int n) { - this.n = n; - this.edges = new ArrayList<>(); - this.adj = new ArrayList<>(); - for (int i = 0; i < n; i++) { - adj.add(new ArrayList<>()); - } - this.level = new int[n]; - this.iter = new int[n]; - } - - void addEdge(int from, int to, int cap) { - addEdgeReturnId(from, to, cap); - } - - int addEdgeReturnId(int from, int to, int cap) { - int edgeId = edges.size(); - adj.get(from).add(edgeId); - edges.add(new int[]{to, cap}); - adj.get(to).add(edgeId + 1); - edges.add(new int[]{from, 0}); - return edgeId; - } - - /** - * 获取某条正向边的实际流量 - */ - int getFlow(int edgeId) { - return edges.get(edgeId ^ 1)[1]; - } - - int maxFlow(int s, int t) { - int flow = 0; - while (bfs(s, t)) { - Arrays.fill(iter, 0); - int f; - while ((f = dfs(s, t, Integer.MAX_VALUE)) > 0) { - flow += f; - } - } - return flow; - } - - private boolean bfs(int s, int t) { - Arrays.fill(level, -1); - Queue queue = new ArrayDeque<>(); - level[s] = 0; - queue.add(s); - while (!queue.isEmpty()) { - int v = queue.poll(); - for (int ei : adj.get(v)) { - int[] e = edges.get(ei); - if (e[1] > 0 && level[e[0]] < 0) { - level[e[0]] = level[v] + 1; - queue.add(e[0]); - } - } - } - return level[t] >= 0; - } - - private int dfs(int v, int t, int f) { - if (v == t) return f; - for (; iter[v] < adj.get(v).size(); iter[v]++) { - int ei = adj.get(v).get(iter[v]); - int[] e = edges.get(ei); - if (e[1] > 0 && level[v] < level[e[0]]) { - int d = dfs(e[0], t, Math.min(f, e[1])); - if (d > 0) { - e[1] -= d; - edges.get(ei ^ 1)[1] += d; - return d; - } - } - } - return 0; - } - } }