2026-07-28 02:12:46 +08:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-14 15:40:10 +08:00
|
|
|
|
def test_analyze_project_data():
|
2026-07-28 02:12:46 +08:00
|
|
|
|
from server.aps_domain.readiness import analyze_project_data
|
|
|
|
|
|
world = seed_world()
|
|
|
|
|
|
text = analyze_project_data(world, "分析项目数据")
|
2026-08-21 07:32:13 +08:00
|
|
|
|
assert "项目数据概览" in text
|
|
|
|
|
|
assert "待排订单" in text and "可直接排产" in text
|
2026-07-28 02:12:46 +08:00
|
|
|
|
text2 = analyze_project_data(
|
|
|
|
|
|
world,
|
|
|
|
|
|
"【文件解析】\n文件:a.xlsx\n有效 2 行 · 错误 0 行(可入库)\n\n给我分析一下",
|
|
|
|
|
|
)
|
2026-08-21 07:32:13 +08:00
|
|
|
|
assert "附件解析结果" in text2
|
2026-07-28 02:12:46 +08:00
|
|
|
|
assert "a.xlsx" in text2
|