Files
mini-openclaw-v2/backend/test_review_plan.py
T
qiaoqichaoandClaude Code 019d5081df feat(v2): T06-a 方案维度解析(去重、刻度冲突、呈现判定)
T06 是大卡,按纪律拆子卡。本批为第一块:维度契约与解析。

新增 contracts:ScaleLevel、DimensionSpec。刻度层级必须升序且取值唯一
(乱序会让展示语义倒置);同 ID 必须标签/量纲/刻度层级完全一致。

新增 services/review_plan.py:
- resolve_dimensions():按 dimension_id 合并;**同 ID 不同定义直接报冲突并拒绝**,
  不让程序或模型临时挑一个(BR-28 / E-20);相同定义只留一份并合并组件引用,
  避免重复计数
- presentation():AC-44 的呈现判定——0 维 empty / 2 维 list / 3–8 同刻度 radar /
  9 维 grouped / 异刻度 faceted;硬阻断**单列**且永不进雷达面积
- assert_nothing_dropped():三组并集必须等于全部维度,"不默默丢失关键项" 有守卫
  且守卫自身有测试证明会报错

刻度改版(同 scale_id 不同 version)视为不同定义——语义可能已变,不得共用雷达。

未完成:T06-b 方案装配与依赖冻结(AC-28)、T06-c 维度在结果中的落位属后续子卡。

测试:新增 test_review_plan.py 18 项(去重 8 + 呈现 10)。全量 333 项 OK(315 → 333);
verify.py 10/10 PASS。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 20:16:39 +08:00

150 lines
6.7 KiB
Python

"""T06-a 方案维度解析测试:AC-29 / AC-44。
维度是 Skills/Rules 的元数据,不新增"维度卡"(14 §5)。本文件锁定三件事:
- 同 ID 不同定义必须拒绝合并(不让模型或程序临时挑一个)
- 相同定义只出现一次,引用被合并而不是重复计数
- 呈现判定覆盖 0 / 2 / 6 / 9 维与异刻度,且**不丢失任何维度**
"""
import unittest
from services import review_plan as plan
SCALE = "evidence-stage"
LEVELS = [
{"value": 0, "label": "未提供", "criterion": "材料未提供"},
{"value": 1, "label": "仅有说明", "criterion": "仅客户经理口头或文字说明"},
{"value": 2, "label": "材料待核", "criterion": "提供了材料但未交叉核对"},
{"value": 3, "label": "交叉核验", "criterion": "两个独立来源一致"},
{"value": 4, "label": "人工复核", "criterion": "评审员已人工核实"},
]
def spec(dimension_id="d1", label="回款依据", order=0, scale_id=SCALE, scale_version="v1",
levels=None, required=False, refs=None):
return {"id": dimension_id, "label": label, "order": order,
"scale_id": scale_id, "scale_version": scale_version,
"levels": levels or LEVELS, "component_refs": refs or [], "required": required}
def many(count, **over):
return [spec(f"d{index}", f"维度{index}", index, **over) for index in range(count)]
class DeduplicationTests(unittest.TestCase):
def test_identical_definitions_merge_into_one(self):
# AC-29:相同定义只出现一次
merged = plan.resolve_dimensions([
spec("d1", refs=[{"id": "skill.a", "version": "1"}]),
spec("d1", refs=[{"id": "skill.b", "version": "1"}]),
])
self.assertEqual(len(merged), 1)
self.assertEqual(sorted(ref["id"] for ref in merged[0]["component_refs"]),
["skill.a", "skill.b"])
def test_same_id_different_label_is_a_conflict(self):
with self.assertRaises(ValueError) as ctx:
plan.resolve_dimensions([spec("d1"), spec("d1", label="另一个名字")])
self.assertIn("不一致定义", str(ctx.exception))
self.assertIn("不得由程序或模型临时选择其一", str(ctx.exception))
def test_same_id_different_scale_id_is_a_conflict(self):
with self.assertRaises(ValueError):
plan.resolve_dimensions([spec("d1"), spec("d1", scale_id="other-scale")])
def test_same_scale_id_different_version_is_a_conflict(self):
# 刻度改版后语义可能变化,必须视为不同定义
with self.assertRaises(ValueError):
plan.resolve_dimensions([spec("d1"), spec("d1", scale_version="v2")])
def test_same_id_different_levels_is_a_conflict(self):
changed = [dict(level) for level in LEVELS]
changed[3] = {"value": 3, "label": "两来源一致", "criterion": "改了判据"}
with self.assertRaises(ValueError):
plan.resolve_dimensions([spec("d1"), spec("d1", levels=changed)])
def test_required_flag_is_unioned_on_merge(self):
merged = plan.resolve_dimensions([spec("d1"), spec("d1", required=True)])
self.assertTrue(merged[0]["required"])
def test_order_is_stable_and_sorted(self):
merged = plan.resolve_dimensions([spec("db", order=5), spec("da", order=1)])
self.assertEqual([item["id"] for item in merged], ["da", "db"])
def test_invalid_dimension_is_rejected(self):
broken = spec()
broken["levels"] = [{"value": 0, "label": "只有一个层级", "criterion": "c"}]
with self.assertRaises(ValueError) as ctx:
plan.resolve_dimensions([broken])
self.assertIn("维度不合法", str(ctx.exception))
class PresentationTests(unittest.TestCase):
def check(self, dimensions, hard=()):
result = plan.presentation(dimensions, hard)
plan.assert_nothing_dropped(result, dimensions) # 不丢项是每次都要过的硬断言
return result
def test_empty_plan_says_so_without_placeholder_axes(self):
# UI Spec:空维度显示"尚未配置",不补六轴占位
result = self.check([])
self.assertEqual(result["layout"], "empty")
self.assertIn("尚未配置核查维度", result["reason"])
def test_two_dimensions_use_list_not_radar(self):
result = self.check(many(2))
self.assertEqual(result["layout"], "list")
self.assertEqual(result["radar_ids"], [])
def test_six_dimensions_use_radar_plus_equivalent_list(self):
result = self.check(many(6))
self.assertEqual(result["layout"], "radar")
self.assertEqual(len(result["radar_ids"]), 6)
self.assertIn("等价文字列表", result["reason"])
def test_nine_dimensions_are_grouped_not_radar(self):
result = self.check(many(9))
self.assertEqual(result["layout"], "grouped")
self.assertEqual(result["radar_ids"], [])
self.assertEqual(len(result["listed_ids"]), 9, "全部维度仍可查看")
def test_three_and_eight_are_the_radar_boundaries(self):
self.assertEqual(self.check(many(3))["layout"], "radar")
self.assertEqual(self.check(many(8))["layout"], "radar")
def test_mixed_scales_force_faceted_display(self):
# 异刻度不强行归一化
dimensions = [spec("d1"), spec("d2", "负债率", 1, scale_id="ratio-scale")]
result = self.check(dimensions)
self.assertEqual(result["layout"], "faceted")
self.assertEqual(result["radar_ids"], [])
def test_hard_blocks_are_listed_separately_and_never_in_the_radar(self):
# 14 §5:硬阻断单列,不被平均数或面积覆盖
dimensions = many(6)
result = self.check(dimensions, hard={"d0"})
self.assertEqual(result["hard_block_ids"], ["d0"])
self.assertNotIn("d0", result["radar_ids"])
self.assertEqual(len(result["radar_ids"]), 5)
self.assertEqual(result["layout"], "radar")
def test_hard_blocks_are_separated_even_when_faceted(self):
dimensions = [spec("d1"), spec("d2", "负债率", 1, scale_id="ratio-scale")]
result = self.check(dimensions, hard={"d1"})
self.assertEqual(result["hard_block_ids"], ["d1"])
self.assertIn("d2", result["listed_ids"])
def test_dropping_a_dimension_would_be_caught(self):
# 守卫本身必须真的会报错,否则"不丢项"只是口号
with self.assertRaises(ValueError):
plan.assert_nothing_dropped(
{"radar_ids": ["d0"], "listed_ids": [], "hard_block_ids": []}, many(2))
def test_unknown_scale_versions_never_share_a_radar(self):
dimensions = [spec("d1"), spec("d2", "同刻度不同版本", 1, scale_version="v2")]
self.assertEqual(self.check(dimensions)["layout"], "faceted")
if __name__ == "__main__":
unittest.main()