fix: validate_and_return_gjson bug

This commit is contained in:
caoqianming 2025-03-27 13:36:34 +08:00
parent afe53f3687
commit ce38115255
1 changed files with 41 additions and 82 deletions

View File

@ -238,98 +238,53 @@ class RoutePack(CommonADModel):
all_routes = Route.objects.filter(routepack=self).order_by('sort', 'process__sort', 'create_time')
result = {}
# 1. 找出所有最终产品未被任何material_in引用的out_id
all_out_ids = {route.material_out.id for route in all_routes}
all_in_ids = {route.material_in.id for route in all_routes}
# 1. 构建正向和反向图material_id作为节点
forward_graph = defaultdict(list) # out_id -> [in_ids]
reverse_graph = defaultdict(list) # in_id -> [out_ids]
route_edges = defaultdict(list) # (in_id, out_id) -> [Route]
for route in all_routes:
if not (route.material_in and route.material_out):
raise ParseError(f"Route(ID:{route.id}) 缺失material_in或material_out")
in_id, out_id = route.material_in.id, route.material_out.id
forward_graph[out_id].append(in_id)
reverse_graph[in_id].append(out_id)
route_edges[(in_id, out_id)].append(route.id)
# 2. 识别所有最终节点未被任何in_id引用的out_id
all_out_ids = {r.material_out.id for r in all_routes}
all_in_ids = {r.material_in.id for r in all_routes}
final_ids = all_out_ids - all_in_ids
if not final_ids:
raise ParseError("未找到任何最终产品")
raise ParseError("未找到最终产品节点")
# 4. 为每个最终产品构建子图
# 3. 为每个最终产品构建子图
for final_id in final_ids:
graph = defaultdict(list)
# BFS收集子图所有material_id
# BFS逆向遍历所有上游节点
visited = set()
queue = deque([final_id])
subgraph_materials = set()
while queue:
current_id = queue.popleft()
if current_id in visited:
current_out_id = queue.popleft()
if current_out_id in visited:
continue
visited.add(current_id)
subgraph_materials.add(current_id)
queue.extend(graph.get(current_id, []))
# 收集子图关联的Route ID
visited.add(current_out_id)
# 将当前节点的所有上游in_id加入队列
for in_id in forward_graph.get(current_out_id, []):
if in_id not in visited:
queue.append(in_id)
# 收集所有关联Route只要out_id在子图内
subgraph_route_ids = []
for route in all_routes:
if route.material_out.id in visited:
subgraph_route_ids.append(route.id)
# 校验子图是否有效(无循环、路径可达)
is_valid, errors, levels = self._validate_subgraph(subgraph_materials, graph, final_id)
if errors and raise_exception:
raise ParseError(errors)
# 记录子图信息
result[final_id] = {
"routes": subgraph_route_ids,
"meta": {
"is_valid": is_valid,
"errors": errors,
"levels": levels # 层级结构,用于生产排程
}
}
result[final_id] = {"routes": subgraph_route_ids}
return result
def _validate_subgraph(self, materials, graph, final_id):
"""校验单个子图的有效性并返回层级结构"""
errors = []
levels = {}
# 层级计算(从最终物料逆向遍历)
queue = deque([(final_id, 0)])
while queue:
current_id, level = queue.popleft()
if current_id in levels:
if level > levels[current_id]:
levels[current_id] = level
continue
levels[current_id] = level
for in_id in graph.get(current_id, []):
queue.append((in_id, level + 1))
# 检查循环依赖
visited = set()
path = set()
def detect_cycle(m_id):
if m_id in path:
return True
if m_id in visited:
return False
visited.add(m_id)
path.add(m_id)
for out_id in graph.get(m_id, []):
if detect_cycle(out_id):
return True
path.remove(m_id)
return False
for m_id in materials:
if detect_cycle(m_id):
errors.append(f"物料(ID:{m_id})存在循环依赖")
return False, errors, {}
# 检查孤立节点(所有物料必须能到达最终产品)
unreachable = materials - set(levels.keys())
if unreachable:
errors.append(f"存在孤立物料: {unreachable}")
return False, errors, {}
return True, [], levels
def get_gjson(self, need_update=False, final_material_id=None):
if not self.gjson or need_update:
self.gjson = self.validate_and_return_gjson()
@ -338,18 +293,22 @@ class RoutePack(CommonADModel):
def get_dags(self):
gjson = self.get_gjson()
finalMaterialIdList = []
materialIdList = []
routeIdList = []
for final_material_id, data in gjson.items():
finalMaterialIdList.append(final_material_id)
materialIdList.append(final_material_id)
routeIdList.extend(data['routes'])
mat_qs = Material.objects.filter(id__in=finalMaterialIdList).order_by("process__sort", "create_time")
mat_dict = {mat.id: {"id": mat.id, "label": str(mat), "shape": "rect"}for mat in mat_qs}
route_qs = Route.objects.filter(id__in=routeIdList).select_related("material_in", "material_out", "process")
matids1 = route_qs.values_list("material_in__id", flat=True).distinct()
matids2 = route_qs.values_list("material_out__id", flat=True).distinct()
materialIdList.extend(list(matids1))
materialIdList.extend(list(matids2))
route_dict = {r.id: {"label": r.process.name, "source": r.material_in.id, "target": r.material_out.id} for r in route_qs}
mat_qs = Material.objects.filter(id__in=materialIdList).order_by("process__sort", "create_time")
mat_dict = {mat.id: {"id": mat.id, "label": str(mat), "shape": "rect"}for mat in mat_qs}
res = {}
for final_material_id, data in gjson.items():
item = {"name": mat_dict[final_material_id]}
item = {"name": mat_dict[final_material_id]["label"]}
edges = []
nodes_set = set()
for route_id in data['routes']: