fix: validate_and_return_gjson bug
This commit is contained in:
parent
afe53f3687
commit
ce38115255
|
@ -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']:
|
||||
|
|
Loading…
Reference in New Issue