# ============================================================ # 主数据查询辅助(moduleId: engines-queries, 可重生 ✅) # 从 legacy common.js 的全局查找器按行移植(P0 只读): # findRoutingSteps / findBomItems / findProductLines / # findWorkstationForOperation / getShiftMinutes / getLineShifts / getAvailableMinutes # ============================================================ from __future__ import annotations # 前向类型引用 from typing import Any # 类型标注 # 世界状态的类型别名(camelCase 键的字典) World = dict[str, Any] def find_routing_steps(world: World, product_id: int) -> list[dict]: """取产品默认工艺路线的步骤(按顺序号排序)。""" routing = next((r for r in world["routings"] if r["productId"] == product_id and r["isDefault"]), None) # 默认路线 if not routing: # 无路线 → 空列表(上游生成 NO_LINE 类冲突) return [] steps = [s for s in world["routingSteps"] if s["routingId"] == routing["id"]] # 该路线全部步骤 return sorted(steps, key=lambda s: s["sequenceNo"]) # 按工序顺序返回 def find_bom_items(world: World, product_id: int) -> list[dict]: """取产品默认 BOM 的明细行。""" bom = next((b for b in world["boms"] if b["productId"] == product_id and b["isDefault"]), None) # 默认 BOM if not bom: # 无 BOM → 空(视为无料耗) return [] return [i for i in world["bomItems"] if i["bomId"] == bom["id"]] # 该 BOM 全部明细 def find_product_lines(world: World, product_id: int) -> list[dict]: """取产品可用产线配置(priority 越小越优先;仅 ACTIVE 产线,停用产线不参与新排产 MD-01)。""" active_line_ids = {ln["id"] for ln in world["lines"] if ln.get("status", "ACTIVE") == "ACTIVE"} # 在用产线集 rows = [lp for lp in world["lineProducts"] if lp["productId"] == product_id and lp["lineId"] in active_line_ids] # 该产品的在用产线配置 return sorted(rows, key=lambda lp: lp["priority"]) # 按优先级排序 def find_workstation_for_operation(world: World, line_id: int, operation_id: int) -> dict | None: """在指定产线上找能执行某工序的工位(工位工序配置 ∩ 产线工位;仅 ACTIVE 工位 MD-01)。""" ws_ids = {wo["workstationId"] for wo in world["workstationOperations"] if wo["operationId"] == operation_id} # 能做该工序的工位集 return next((ws for ws in world["workstations"] if ws["lineId"] == line_id and ws["id"] in ws_ids and ws.get("status", "ACTIVE") == "ACTIVE"), None) # 限定产线取第一个在用工位 def _hm_to_minutes(hm: str) -> int: """'HH:MM' → 当日分钟数(班次时间换算的基元)。""" h, m = hm.split(":") # 拆时与分 return int(h) * 60 + int(m) # 换算为分钟 def get_shift_minutes(world: World, shift_id: int) -> int: """某班次的有效工作分钟(扣除休息段,与 legacy getShiftMinutes 对齐)。""" shift = next((s for s in world["shifts"] if s["id"] == shift_id), None) # 定位班次 if not shift: # 班次缺失防御 return 0 total = _hm_to_minutes(shift["endTime"]) - _hm_to_minutes(shift["startTime"]) # 毛时长 for bp in shift.get("breakPeriods", []): # 逐个休息段扣减 total -= _hm_to_minutes(bp["end"]) - _hm_to_minutes(bp["start"]) return max(total, 0) # 不为负 def get_line_shifts(world: World, line_id: int, date_str: str) -> list[dict]: """某产线某日的工作班次列表(班次日历 isWorking 过滤)。""" shift_ids = [sc["shiftId"] for sc in world["shiftCalendar"] # 扫描班次日历 if sc["lineId"] == line_id and sc["date"] == date_str and sc["isWorking"]] return [s for s in world["shifts"] if s["id"] in shift_ids] # 映射回班次对象 def get_available_minutes(world: World, line_id: int, date_str: str) -> int: """某产线某日的总可用分钟(各工作班次有效分钟之和)。""" return sum(get_shift_minutes(world, s["id"]) for s in get_line_shifts(world, line_id, date_str)) # 累加