# ============================================================ # EX-07 / EX-08 分析视图黄金测试 # ============================================================ from __future__ import annotations from server.aps_domain.analytics import ( build_compare_table, build_kpi_dashboard, matrix_from_flex_rows, ) from server.aps_domain.flex import run_flex_schedule from server.engines import get_engine from server.engines.base import EngineParams from server.state.seed import seed_world from server.timeutil import add_minutes, fmt_date, today0 class _MemStore: def __init__(self, data): self.data = data def next_id(self, kind: str) -> int: key = f"_c_{kind}" self.data[key] = self.data.get(key, 5000) + 1 return self.data[key] def save(self): pass def _run_fixed(world): start = fmt_date(add_minutes(today0(), 24 * 60)) params = EngineParams(orderIds=[], engineType="RULE", strategyTemplate="COMPREHENSIVE", planningHorizonDays=14, startDate=start) counters: dict[str, int] = {} def next_id(kind: str) -> int: counters[kind] = counters.get(kind, 0) + 1 return counters[kind] get_engine("RULE").solve(world, params, next_id) def test_kpi_dashboard_aggregates(): store = _MemStore(seed_world()) _run_fixed(store.data) run_flex_schedule(store, sort_mode="BOTTLENECK", actor="test") dash = build_kpi_dashboard(store.data) assert len(dash["cards"]) >= 5 assert dash["fixed"] and dash["fixed"].get("versionNo") assert dash["flex"] and dash["flex"].get("versionNo") assert "total" in dash["conflicts"] def test_compare_table_has_best_and_deltas(): store = _MemStore(seed_world()) table = build_compare_table(store.data, track="flex") assert table["sections"] sec = table["sections"][0] assert len(sec["columns"]) == 5 assert {column["sortMode"] for column in sec["columns"]} == { "ASC", "DESC", "BOTTLENECK", "KITTING_FIRST", "SKILL_FIRST", } assert sec["recommendation"] row = next(r for r in sec["rows"] if r["metric"] == "totalTardiness") assert row["bestIdx"] is not None assert row["deltas"][row["bestIdx"]] in (0, 0.0) def test_flex_matrix_excludes_degraded_best_kpi_and_marks_trial_only(): rows = [ {"sortMode": "SKILL_FIRST", "label": "技能优先", "strategyStatus": "DEGRADED", "strategyEvidence": [{"code": "NO_SKILL_LEVEL_VARIATION"}], "totalTardiness": 0, "conflictCount": 0, "avgUtilization": 90, "onTimeCount": 10, "orderCount": 10, "vlCount": 10}, {"sortMode": "ASC", "label": "正排", "strategyStatus": "READY", "totalTardiness": 12, "conflictCount": 1, "avgUtilization": 80, "onTimeCount": 9, "orderCount": 10, "vlCount": 10}, ] matrix = matrix_from_flex_rows( rows, data_quality={ "productionReady": False, "warnings": [{"message": "设备能力仅覆盖 1/41 台设备"}], }, ) assert len(matrix["columns"]) == 2 assert matrix["columns"][0]["strategyStatus"] == "DEGRADED" assert matrix["recommendation"] == "正排" assert matrix["recommendationId"] == "ASC" assert matrix["eligibleRecommendationIds"] == ["ASC"] assert matrix["productionReady"] is False assert matrix["recommendationStatus"] == "TRIAL_ONLY" assert "数据待补" in matrix["recommendationReason"] def test_flex_matrix_without_ready_strategy_is_unavailable(): rows = [ {"sortMode": "KITTING_FIRST", "label": "齐套优先", "strategyStatus": "PARTIAL", "totalTardiness": 5, "conflictCount": 0, "avgUtilization": 80, "onTimeCount": 9, "orderCount": 10, "vlCount": 10}, {"sortMode": "SKILL_FIRST", "label": "技能优先", "strategyStatus": "DEGRADED", "totalTardiness": 0, "conflictCount": 0, "avgUtilization": 90, "onTimeCount": 10, "orderCount": 10, "vlCount": 10}, ] matrix = matrix_from_flex_rows(rows, data_quality={"productionReady": False}) assert matrix["recommendationStatus"] == "UNAVAILABLE" assert matrix["recommendation"] is None assert matrix["recommendationId"] is None assert matrix["eligibleRecommendationIds"] == [] assert "没有 strategyStatus=READY" in matrix["recommendationReason"] def test_flex_matrix_excludes_partial_schedule_result_even_when_strategy_is_ready(): rows = [ {"sortMode": "ASC", "label": "正排", "strategyStatus": "READY", "resultStatus": "PARTIAL", "totalTardiness": 0, "conflictCount": 0, "avgUtilization": 95, "onTimeCount": 10, "orderCount": 10, "vlCount": 5}, {"sortMode": "DESC", "label": "倒排", "strategyStatus": "READY", "resultStatus": "WITH_CONFLICTS", "totalTardiness": 10, "conflictCount": 2, "avgUtilization": 80, "onTimeCount": 8, "orderCount": 10, "vlCount": 10}, ] matrix = matrix_from_flex_rows(rows, data_quality={"productionReady": False}) assert matrix["recommendationId"] == "DESC" assert matrix["eligibleRecommendationIds"] == ["DESC"] assert matrix["recommendationStatus"] == "TRIAL_ONLY" only_partial = matrix_from_flex_rows(rows[:1], data_quality={"productionReady": False}) assert only_partial["recommendationId"] is None assert only_partial["recommendationStatus"] == "UNAVAILABLE" def test_compare_table_propagates_flex_data_quality(): table = build_compare_table(seed_world(), track="flex") section = table["sections"][0] assert section["productionReady"] is False assert section["recommendationStatus"] == "TRIAL_ONLY" assert section["recommendationId"] in section["eligibleRecommendationIds"]