From 5d0884cc3bc87a39c439ed3e4663085ef3be8ea7 Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Tue, 15 Sep 2026 17:48:56 +0800 Subject: [PATCH] fix(kpi): single source of truth for REVERSE_INDICATORS + drop F_FACTORY_REBATE_RATE from reverse set P-6 (M2 gate 2, FIN-CE-REVIEW-20260915-01): kpis.REVERSE_INDICATORS is now the only authoritative direction definition; dashboard.py imports it instead of carrying a literal copy; bot_kpis.py derives REVERSE_BOT_INDICATORS from it (dead import removed). F_FACTORY_REBATE_RATE (upstream factory rebate rate, higher=better) was wrongly treated as reverse: 278.7% vs target 80 scored 1/danger, now 5/success and workbench three-color red -> green. No DB write, no scoring-engine logic change, no data_level/frontend touch. --- backend/app/api/bot_kpis.py | 15 +++++++++++---- backend/app/api/dashboard.py | 11 +++++------ backend/app/api/kpis.py | 14 +++++++++++++- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/backend/app/api/bot_kpis.py b/backend/app/api/bot_kpis.py index 285ec894..a94e6eea 100644 --- a/backend/app/api/bot_kpis.py +++ b/backend/app/api/bot_kpis.py @@ -14,8 +14,17 @@ router = APIRouter(prefix="/api/cma/bot-kpis", tags=["Bot KPI管理"], dependencies=[Depends(require_auth)], ) -# 反向指标编码(值越低越好) -REVERSE_BOT_INDICATORS = ['FB_RESPONSE_TIME'] +# 反向指标编码(值越低越好)。 +# 方向权威源 = app.api.kpis.REVERSE_INDICATORS(2026-09-15 M2 准入2 收口): +# 本文件原 import 了权威表却未使用、另立字面量 ['FB_RESPONSE_TIME'],会让同一 kpi_code +# 在「能力评分」(取权威表=反向) 与「Bot 自评」(取本表=正向) 方向打架 +# (实测同一宇宙内 F_COST_RATIO / F_AR_DAYS / F_REBATE_RATE 三码即为此类)。 +# 现改为派生:权威表 ∪ Bot 专有反向指标(零漂移)。 +from app.api.kpis import REVERSE_INDICATORS as _AUTHORITATIVE_REVERSE_INDICATORS + +BOT_ONLY_REVERSE_INDICATORS = ['FB_RESPONSE_TIME'] +REVERSE_BOT_INDICATORS = list(_AUTHORITATIVE_REVERSE_INDICATORS) + [ + c for c in BOT_ONLY_REVERSE_INDICATORS if c not in _AUTHORITATIVE_REVERSE_INDICATORS] def _calc_bot_kpi_score(current_value, target_value, is_reverse=False): @@ -68,8 +77,6 @@ def list_bot_kpis( "overall": None, } - from app.api.kpis import REVERSE_INDICATORS as _ri - result_kpis = [] for k in kpis: # 取最新实际值 diff --git a/backend/app/api/dashboard.py b/backend/app/api/dashboard.py index 68283f92..7211ee24 100644 --- a/backend/app/api/dashboard.py +++ b/backend/app/api/dashboard.py @@ -10,6 +10,9 @@ from app.deps import get_entity_id from app.models import KPIDefinition, KPIValue, KPIAlert, User, VoucherDetail from app.utils.cache import get as cache_get, set as cache_set from app.utils.voucher_guard import apply_voucher_guard +# 指标方向(反向/正向)唯一权威源 = app.api.kpis.REVERSE_INDICATORS(2026-09-15 M2 准入2 收口)。 +# 本文件原自带一份字面量副本,与 kpis.py 漂移后会与「能力评分」判定相反,故改为导入。 +from app.api.kpis import REVERSE_INDICATORS import json import logging @@ -619,12 +622,8 @@ def my_dashboard( target = kpi_target_by_frequency(k) level = "gray" if actual is not None and target: - # 反向指标(越低越好):费用率/渠补率/应收天数/返利率/成本率/存货天数 - REVERSE_INDICATORS = { - "F_COST_RATIO", "C_REBATE_RATE", "F_AR_DAYS", - "F_REBATE_RATE", "F_FACTORY_REBATE_RATE", "F_COST_CONTROL_RATE", - "F_INV_DAYS", "F_DEBT_RATIO", "P_BUG_RATE", "P_REWORK_PCT", - } + # 反向指标(越低越好)集合 = 单一权威源 app.api.kpis.REVERSE_INDICATORS + # (2026-09-15 M2 准入2:删本文件原字面量副本,避免与能力评分引擎方向漂移) if k.kpi_code in REVERSE_INDICATORS: # 反向:实际≤目标=绿;实际≤目标*1.1=黄;否则红 level = "green" if actual <= target else ( diff --git a/backend/app/api/kpis.py b/backend/app/api/kpis.py index 783d539d..67eea627 100644 --- a/backend/app/api/kpis.py +++ b/backend/app/api/kpis.py @@ -111,9 +111,21 @@ def get_kpi_categories(current_user = Depends(require_auth), db: Session = Depen # KPI-5: 五档评分引擎(静态路由必须在动态/{kpi_id}之前) # ============================================================ +# ── 反向指标(值越低越好)· 单一事实源(single source of truth) ── +# 本表是「指标方向」的唯一权威定义(2026-09-15 M2 准入2 收口):凡按方向判级别/评分的代码 +# 一律引用本表,禁止再复制字面量副本。 +# - app/api/dashboard.py「工作台三色」原自带一份副本 → 已改为 from app.api.kpis import +# - app/api/bot_kpis.py 原 import 本表却未使用 → 已改为派生自本表 +# 判定语义:在内 = 越低越好(实际/目标 ≤0.5 → 5 分);不在内 = 越高越好(比值 ≥1.2 → 5 分) +# 方向口径注意(两处「返补/返利」现金流方向相反,禁止误合并): +# - C_REBATE_RATE / F_REBATE_RATE = 渠道返补(对外付出,越低越好 → 反向,留在本表) +# - F_FACTORY_REBATE_RATE = 上游厂补率(厂补(捐赠收益)/主营成本,收到越多越好 → 正向,不属本表) +# 2026-09-15 修正:原被误列入本表 → 现网 278.7%(target_value=80)被判 1 分"失效"; +# 与其三色阈值文本(green>=100=1.25×target / yellow>=80=target / red<80,越高越好)方向相反。 +# P-6 定稿 = 改方向(阈值文本保持不动),修正后同一实测值判 5 分。 REVERSE_INDICATORS = ['C_REBATE_RATE', 'P_BUG_RATE', 'P_REWORK_PCT', 'F_DEBT_RATIO', 'F_COST_RATIO', 'F_AR_DAYS', 'F_REBATE_RATE', - 'F_FACTORY_REBATE_RATE', 'F_COST_CONTROL_RATE', 'F_INV_DAYS'] + 'F_COST_CONTROL_RATE', 'F_INV_DAYS'] def _static_threshold_level(value, kpi) -> str: