aps-agent/server/integrations/algo_skill_stub.py

97 lines
3.7 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: integrations-algo-stub, 可重生 ✅)
# local://stub 或 HTTP /schedule:简单串行排到首台可用设备
# ============================================================
from __future__ import annotations
import uuid
from typing import Any
from server.aps_domain.scheduling_dto import (
SchedulingProblem, SchedulingSolution, SolutionOp, SolutionConflict,
)
from server.timeutil import add_minutes, fmt_dt, parse_dt
def solve_problem(problem: SchedulingProblem | dict[str, Any]) -> SchedulingSolution:
"""确定性桩算法:按订单×工序顺序,选能力匹配的第一台资源串行占槽。"""
p = problem if isinstance(problem, SchedulingProblem) else SchedulingProblem(**problem)
cursor = parse_dt(p.startTime)
ops_out: list[SolutionOp] = []
conflicts: list[SolutionConflict] = []
# 资源占用游标
res_free: dict[str, Any] = {r.code: cursor for r in p.resources}
caps: dict[str, list[str]] = {r.code: list(r.capabilities or []) for r in p.resources}
route_by_product: dict[str, list] = {}
for step in sorted(p.routings, key=lambda s: (s.productCode, s.seq)):
route_by_product.setdefault(step.productCode, []).append(step)
for order in sorted(p.orders, key=lambda o: (o.dueDate or "9999", o.priority, o.orderId)):
steps = route_by_product.get(order.productCode) or []
if not steps:
conflicts.append(SolutionConflict(
type="NO_ROUTING", orderId=order.orderId,
description=f"订单 {order.orderId} 无工艺路线", severity="high"))
continue
for step in steps:
candidates = [
code for code, cap in caps.items()
if not cap or step.operationCode in cap
]
if not candidates:
candidates = list(res_free.keys())
if not candidates:
conflicts.append(SolutionConflict(
type="NO_CAPABILITY", orderId=order.orderId,
description=f"无设备可做 {step.operationCode}", severity="high"))
continue
# 选最早空闲
chosen = min(candidates, key=lambda c: res_free[c])
start_t = res_free[chosen]
run_min = max(1.0, float(step.stdTimePerUnit) * float(order.quantity))
end_t = add_minutes(start_t, int(run_min))
ops_out.append(SolutionOp(
orderId=order.orderId, seq=step.seq,
operationCode=step.operationCode,
operationName=step.operationName or step.operationCode,
resourceCode=chosen,
start=fmt_dt(start_t), end=fmt_dt(end_t),
runMin=run_min,
))
res_free[chosen] = end_t
return SchedulingSolution(
problemId=p.problemId,
runId=uuid.uuid4().hex[:12],
skillId="algo.stub",
status="FEASIBLE" if ops_out else "INFEASIBLE",
operations=ops_out,
conflicts=conflicts,
kpi={"avgUtilization": 0.55, "opCount": len(ops_out)},
solverMeta={"engine": "stub", "note": "local deterministic stub"},
)
def create_stub_app():
"""可选:独立 FastAPI 服务(python -m server.integrations.algo_skill_stub)。"""
from fastapi import FastAPI
app = FastAPI(title="APS Algo Skill Stub")
@app.get("/health")
def health():
return {"ok": True, "skill": "algo.stub"}
@app.post("/schedule")
def schedule(body: dict):
sol = solve_problem(body)
return sol.model_dump()
return app
if __name__ == "__main__":
import uvicorn
uvicorn.run(create_stub_app(), host="127.0.0.1", port=8101)