aps-agent/tests/golden/test_readiness.py

96 lines
3.8 KiB
Python
Raw Permalink Normal View History

# ============================================================
# M-B 黄金测试:数据齐备度 + 工时来源 + TIME_* 冲突标注
# ============================================================
from __future__ import annotations
from server.aps_domain.readiness import (
annotate_time_conflicts, check_readiness, readiness_text,
resolve_step_time, time_matrix,
)
from server.state.seed import seed_world
def test_demo_world_readiness_mostly_ready():
world = seed_world()
report = check_readiness(world)
s = report["summary"]
assert s["total"] == len([o for o in world["flexOrders"] if o["status"] not in ("DONE", "CANCELLED")])
# 演示厂路线无覆盖工时但设备 opStdTime 齐 → 不算待维护
assert s["timePending"] == 0
assert s["ready"] >= 1
assert "数据齐备度" in readiness_text(report) or "我帮你看了看" in readiness_text(report)
def test_missing_equipment_time_flags_unmaintained():
world = seed_world()
# 摘掉 OP-ASSY 的设备默认工时 → 该步变「待维护」
for e in world["flexEquipment"]:
e.get("opStdTime", {}).pop("OP-ASSY", None)
report = check_readiness(world)
assert report["summary"]["timePending"] > 0
assy_orders = [r for r in report["orders"] if r["productCode"] == "HV-HARNESS"]
assert any(i["type"] == "TIME_UNMAINTAINED" for r in assy_orders for i in r["issues"])
assert all(not r["ready"] for r in assy_orders)
def test_inferred_time_is_warning_not_blocker():
world = seed_world()
for r in world["flexRoutings"]:
if r["productCode"] == "HV-CONN" and r["operationCode"] == "OP-ASSY":
r["stdTimePerUnit"] = 30.0
r["stdTimeSource"] = "推断"
report = check_readiness(world)
conn = [r for r in report["orders"] if r["productCode"] == "HV-CONN"]
assert any(i["type"] == "TIME_INFERRED" and i["severity"] == "warn"
for r in conn for i in r["issues"])
assert all(r["ready"] for r in conn) # 推断不阻塞,只告警
def test_resolve_step_time_priority():
eq = [{"opStdTime": {"OP-X": 5.0}}]
assert resolve_step_time({"operationCode": "OP-X", "stdTimePerUnit": 8.0}, eq) == \
{"stdMin": 8.0, "source": "实测"}
assert resolve_step_time({"operationCode": "OP-X"}, eq) == \
{"stdMin": 5.0, "source": "设备默认"}
assert resolve_step_time({"operationCode": "OP-Y"}, eq)["source"] == "待维护"
def test_time_matrix_has_sources():
world = seed_world()
rows = time_matrix(world)
assert rows
assert {"productCode", "operationCode", "stdMin", "source"} <= set(rows[0].keys())
assert all(r["source"] in ("实测", "推断", "模板", "设备默认", "待维护") for r in rows)
def test_annotate_time_conflicts_after_schedule():
world = seed_world()
# 制造一个待维护步骤
for e in world["flexEquipment"]:
e.get("opStdTime", {}).pop("OP-PKG", None)
counters: dict[str, int] = {}
def nid(kind: str) -> int:
counters[kind] = counters.get(kind, 0) + 1
return counters[kind]
n = annotate_time_conflicts(world, version_id=99, next_id=nid, order_nos=None)
assert n > 0
cfs = [c for c in world["flexConflicts"] if c["versionId"] == 99]
assert any(c["conflictType"] == "TIME_UNMAINTAINED" for c in cfs)
assert all(c["severity"] in ("MAJOR", "MINOR") for c in cfs)
def test_analyze_project_data():
from server.aps_domain.readiness import analyze_project_data
world = seed_world()
text = analyze_project_data(world, "分析项目数据")
assert "项目数据概览" in text
assert "待排订单" in text and "可直接排产" in text
text2 = analyze_project_data(
world,
"【文件解析】\n文件:a.xlsx\n有效 2 行 · 错误 0 行(可入库)\n\n给我分析一下",
)
assert "附件解析结果" in text2
assert "a.xlsx" in text2