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')
|
all_routes = Route.objects.filter(routepack=self).order_by('sort', 'process__sort', 'create_time')
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
# 1. 找出所有最终产品(未被任何material_in引用的out_id)
|
# 1. 构建正向和反向图(material_id作为节点)
|
||||||
all_out_ids = {route.material_out.id for route in all_routes}
|
forward_graph = defaultdict(list) # out_id -> [in_ids]
|
||||||
all_in_ids = {route.material_in.id for route in all_routes}
|
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
|
final_ids = all_out_ids - all_in_ids
|
||||||
|
|
||||||
if not final_ids:
|
if not final_ids:
|
||||||
raise ParseError("未找到任何最终产品")
|
raise ParseError("未找到最终产品节点")
|
||||||
|
|
||||||
# 4. 为每个最终产品构建子图
|
# 3. 为每个最终产品构建子图
|
||||||
for final_id in final_ids:
|
for final_id in final_ids:
|
||||||
graph = defaultdict(list)
|
# BFS逆向遍历所有上游节点
|
||||||
# BFS收集子图所有material_id
|
|
||||||
visited = set()
|
visited = set()
|
||||||
queue = deque([final_id])
|
queue = deque([final_id])
|
||||||
subgraph_materials = set()
|
|
||||||
while queue:
|
|
||||||
current_id = queue.popleft()
|
|
||||||
if current_id in visited:
|
|
||||||
continue
|
|
||||||
visited.add(current_id)
|
|
||||||
subgraph_materials.add(current_id)
|
|
||||||
queue.extend(graph.get(current_id, []))
|
|
||||||
|
|
||||||
# 收集子图关联的Route ID
|
while queue:
|
||||||
|
current_out_id = queue.popleft()
|
||||||
|
if current_out_id in visited:
|
||||||
|
continue
|
||||||
|
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 = []
|
subgraph_route_ids = []
|
||||||
for route in all_routes:
|
for route in all_routes:
|
||||||
if route.material_out.id in visited:
|
if route.material_out.id in visited:
|
||||||
subgraph_route_ids.append(route.id)
|
subgraph_route_ids.append(route.id)
|
||||||
|
|
||||||
# 校验子图是否有效(无循环、路径可达)
|
result[final_id] = {"routes": subgraph_route_ids}
|
||||||
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 # 层级结构,用于生产排程
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
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):
|
def get_gjson(self, need_update=False, final_material_id=None):
|
||||||
if not self.gjson or need_update:
|
if not self.gjson or need_update:
|
||||||
self.gjson = self.validate_and_return_gjson()
|
self.gjson = self.validate_and_return_gjson()
|
||||||
|
@ -338,18 +293,22 @@ class RoutePack(CommonADModel):
|
||||||
|
|
||||||
def get_dags(self):
|
def get_dags(self):
|
||||||
gjson = self.get_gjson()
|
gjson = self.get_gjson()
|
||||||
finalMaterialIdList = []
|
materialIdList = []
|
||||||
routeIdList = []
|
routeIdList = []
|
||||||
for final_material_id, data in gjson.items():
|
for final_material_id, data in gjson.items():
|
||||||
finalMaterialIdList.append(final_material_id)
|
materialIdList.append(final_material_id)
|
||||||
routeIdList.extend(data['routes'])
|
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")
|
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}
|
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 = {}
|
res = {}
|
||||||
for final_material_id, data in gjson.items():
|
for final_material_id, data in gjson.items():
|
||||||
item = {"name": mat_dict[final_material_id]}
|
item = {"name": mat_dict[final_material_id]["label"]}
|
||||||
edges = []
|
edges = []
|
||||||
nodes_set = set()
|
nodes_set = set()
|
||||||
for route_id in data['routes']:
|
for route_id in data['routes']:
|
||||||
|
|
Loading…
Reference in New Issue