fix(management): validate_config补grade_bounds形状与跨维度code唯一性校验
This commit is contained in:
@@ -96,23 +96,42 @@ DEFAULT_SCORE_CONFIG: dict = {
|
||||
|
||||
|
||||
def validate_config(config: dict) -> list[str]:
|
||||
"""配置校验:权重总和/结构/引用的 metric_code 有元数据/阈值形状。
|
||||
"""配置校验:权重总和/结构/引用的 metric_code 有元数据/阈值形状/
|
||||
grade_bounds 形状/指标 code 跨维度唯一。
|
||||
|
||||
阈值形状(随评分引擎落地的 B0 评审预埋要求):
|
||||
- type 须为 target/reverse/band 三者之一
|
||||
- target : floor < target
|
||||
- reverse: target_ms < floor_ms(100 分线在 0 分线之前)
|
||||
- band : low < high
|
||||
B4 预埋(配置将自 DB 加载,写入侧不再重跑种子校验):
|
||||
- grade_bounds 须含 A/B/C/D 且严格降序
|
||||
- 指标 code 跨维度不得重复:metrics_detail 为扁平 dict,
|
||||
同名 code 会相互覆盖(后写胜出)
|
||||
"""
|
||||
problems: list[str] = []
|
||||
dims = config.get("dimensions", {})
|
||||
total = sum(d.get("weight", 0) for d in dims.values())
|
||||
if total != 100:
|
||||
problems.append(f"维度权重总和须=100,当前 {total}")
|
||||
gb = config.get("grade_bounds")
|
||||
if not isinstance(gb, dict) or not all(
|
||||
isinstance(gb.get(k), int) for k in ("A", "B", "C", "D")
|
||||
):
|
||||
problems.append("grade_bounds 须含 A/B/C/D 且严格降序")
|
||||
elif not (gb["A"] > gb["B"] > gb["C"] > gb["D"]):
|
||||
problems.append(
|
||||
f"grade_bounds 须严格降序 A>B>C>D"
|
||||
f"(当前 A={gb['A']} B={gb['B']} C={gb['C']} D={gb['D']})"
|
||||
)
|
||||
seen_codes: dict[str, str] = {}
|
||||
for key, dim in dims.items():
|
||||
if dim.get("status") == "pending_data":
|
||||
continue
|
||||
for code, spec in dim.get("metrics", {}).items():
|
||||
if code in seen_codes:
|
||||
problems.append(f"指标 code 重复定义于多维度: {code}")
|
||||
seen_codes[code] = key
|
||||
src = spec.get("metric", code)
|
||||
if src not in METRIC_DEFINITIONS:
|
||||
problems.append(f"{key}.{code} 引用未定义指标 {src}")
|
||||
|
||||
@@ -277,3 +277,29 @@ def test_validate_config_threshold_shapes():
|
||||
.update(low=300)) # low>high 200
|
||||
assert any("call_band" in p and "low<high" in p
|
||||
for p in validate_config(bad_band))
|
||||
|
||||
|
||||
def test_validate_config_grade_bounds_shape():
|
||||
"""grade_bounds 须含 A/B/C/D 且严格降序(B4 DB 配置加载预埋)。"""
|
||||
missing = json.loads(json.dumps(DEFAULT_SCORE_CONFIG)) # deep copy
|
||||
del missing["grade_bounds"]["A"]
|
||||
assert any("grade_bounds" in p for p in validate_config(missing))
|
||||
|
||||
non_desc = json.loads(json.dumps(DEFAULT_SCORE_CONFIG))
|
||||
non_desc["grade_bounds"].update(A=80, B=90) # A<B 非降序
|
||||
assert any("grade_bounds" in p and "严格降序" in p
|
||||
for p in validate_config(non_desc))
|
||||
|
||||
|
||||
def test_validate_config_cross_dimension_code_unique():
|
||||
"""同指标 code 出现于两维度:metrics_detail 扁平 dict 会碰撞,须报问题。"""
|
||||
dup = json.loads(json.dumps(DEFAULT_SCORE_CONFIG)) # deep copy
|
||||
# 把 customer 维的 active_share 整个抄进 service 维 → code 跨维度重复
|
||||
dup["dimensions"]["service"]["metrics"]["active_share"] = \
|
||||
dup["dimensions"]["customer"]["metrics"]["active_share"]
|
||||
assert any("active_share" in p and "重复" in p for p in validate_config(dup))
|
||||
|
||||
|
||||
def test_validate_config_default_config_clean():
|
||||
"""默认配置自身必须全绿(空问题列表)。"""
|
||||
assert validate_config(DEFAULT_SCORE_CONFIG) == []
|
||||
|
||||
Reference in New Issue
Block a user