aps-agent/server/engines/queries.py

75 lines
4.4 KiB
Python
Raw Permalink Normal View History

2026-07-21 11:05:57 +08:00
# ============================================================
# 主数据查询辅助(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)) # 累加