From 9fbd572bb77ccdca82ce0953fef4e3e269cd31d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Wed, 9 Sep 2026 19:28:48 +0800 Subject: [PATCH] =?UTF-8?q?docs(management):=20=E6=89=B9=E6=AC=A1B?= =?UTF-8?q?=E7=BB=88=E8=AF=84=E6=94=B6=E5=B0=BE=E2=80=94=E2=80=94validate?= =?UTF-8?q?=5Fconfig=E8=A1=A5=E6=BA=90=E5=90=8D=E5=94=AF=E4=B8=80=E6=80=A7?= =?UTF-8?q?+=E9=81=97=E7=95=99=E8=A7=82=E5=AF=9F=E9=A1=B9=E5=BD=92?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 批次B终评Approve后两处闭环: - validate_config补源指标名跨维度唯一校验(comparisons按源名建键的 静默覆盖防线)+测试 - 观察项归档:call_band样本基数(批次F)/源名唯一(已修)/性能与winsorize(批次F) --- .../2026-09-08-management-batch-a-verification.md | 8 ++++++++ yunke-server/app/management/metric_definitions.py | 10 ++++++++++ yunke-server/tests/test_management_scoring.py | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md b/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md index ddd68f3..3d76f5a 100644 --- a/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md +++ b/docs/superpowers/plans/2026-09-08-management-batch-a-verification.md @@ -295,3 +295,11 @@ action_counts 内部:`unreplied(hours=2)` **2.38s**;`churn(real,30d)` 0.12s - **metric-definitions**:19 项定义(msg_sent…call_followup_rate)+ score_config + caliber_notes,`config_source="db"`(评分配置 v1.0 种子生效 ✓)。 **结论:六端点全 200、数据与库逐字段可对;唯一性能缺口=dashboard 的 unreplied 行动计数(channel 无前缀索引可用),暖态 2.2~2.9s,其余端点全部达标。** + +## 4. 批次B遗留观察项归档(终评补录,2026-09-09) + +| # | 项 | 归档 | +|---|---|---| +| 1 | call_band 样本基数:band 类指标样本量取 active_friends(规格字面),WA 通话量上来后"通话多客户少"员工会被误标 insufficient——当前数据零影响(实测 calls≥1 且 af<10 仅 5 人且 calls≤5,怎么选都不足);归批次F回测前一行修正(sample_basis 字段或按源指标取样本) | +| 2 | 配置源名唯一性:两个 code 指向同一源指标会使 employees comparisons 键静默覆盖——已在 validate_config 加源名唯一校验+测试(批次B终评闭环),默认配置六个源名互异不受影响 | +| 3 | dashboard 性能(2.2~2.9s,unreplied channel=None 无前缀索引)与 fr_ms 天级极值 winsorize:见上文 §2/§3,决策归批次F | diff --git a/yunke-server/app/management/metric_definitions.py b/yunke-server/app/management/metric_definitions.py index 8fe9891..5675f38 100644 --- a/yunke-server/app/management/metric_definitions.py +++ b/yunke-server/app/management/metric_definitions.py @@ -108,6 +108,8 @@ def validate_config(config: dict) -> list[str]: - grade_bounds 须含 A/B/C/D 且严格降序 - 指标 code 跨维度不得重复:metrics_detail 为扁平 dict, 同名 code 会相互覆盖(后写胜出) + - 源指标名跨维度不得重复:employees 行 comparisons 按源名建键 + (api_assembly._code_to_source),两个 code 指向同一源名会静默覆盖 """ problems: list[str] = [] dims = config.get("dimensions", {}) @@ -125,6 +127,7 @@ def validate_config(config: dict) -> list[str]: f"(当前 A={gb['A']} B={gb['B']} C={gb['C']} D={gb['D']})" ) seen_codes: dict[str, str] = {} + seen_sources: dict[str, str] = {} for key, dim in dims.items(): if dim.get("status") == "pending_data": continue @@ -133,6 +136,13 @@ def validate_config(config: dict) -> list[str]: problems.append(f"指标 code 重复定义于多维度: {code}") seen_codes[code] = key src = spec.get("metric", code) + if src in seen_sources: + problems.append( + f"{key}.{code} 与 {seen_sources[src]} 指向同一源指标 {src}" + f"(comparisons 键域将相互覆盖)" + ) + else: + seen_sources[src] = f"{key}.{code}" if src not in METRIC_DEFINITIONS: problems.append(f"{key}.{code} 引用未定义指标 {src}") mtype = spec.get("type") diff --git a/yunke-server/tests/test_management_scoring.py b/yunke-server/tests/test_management_scoring.py index 7170ab0..1167b7e 100644 --- a/yunke-server/tests/test_management_scoring.py +++ b/yunke-server/tests/test_management_scoring.py @@ -300,6 +300,16 @@ def test_validate_config_cross_dimension_code_unique(): assert any("active_share" in p and "重复" in p for p in validate_config(dup)) +def test_validate_config_source_name_unique(): + """两个 code 指向同一源指标:comparisons 按源名建键会静默覆盖,须报问题。""" + dup = json.loads(json.dumps(DEFAULT_SCORE_CONFIG)) + # service 里造一个不同 code 但同指向 active_friends 的 band(源名冲突) + dup["dimensions"]["service"]["metrics"]["load_band"] = { + "type": "band", "metric": "active_friends", "low": 1, "high": 50, + } + assert any("同一源指标 active_friends" in p for p in validate_config(dup)) + + def test_validate_config_default_config_clean(): """默认配置自身必须全绿(空问题列表)。""" assert validate_config(DEFAULT_SCORE_CONFIG) == []