aps-agent/server/engines/queries.py

75 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 主数据查询辅助(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)) # 累加