From e22093256b7e1c95cac4eb5ab228bac43249fd88 Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Fri, 11 Sep 2026 17:27:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(kpi):=20suggest=20=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E9=BB=98=E8=AE=A4=E5=8F=A3=E5=BE=84=20middle?= =?UTF-8?q?=5Fbest=20=E2=80=94=20=E4=BF=AE=E5=A4=8D=E6=9C=AA=E7=9F=A5?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E8=A2=AB=E8=AF=AF=E5=88=A4=E4=B8=BA=20higher?= =?UTF-8?q?=5Fbetter=EF=BC=88C=5FREBATE=5FRATE/BH=5FSALES=5F10=20=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=E9=80=80=E5=8C=96=EF=BC=89=20(project-20260911-thresh?= =?UTF-8?q?old-fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/thresholds.py | 16 +++++++++++---- backend/tests/test_threshold_parser.py | 28 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/backend/app/api/thresholds.py b/backend/app/api/thresholds.py index 9e3d09f1..4123dd4b 100644 --- a/backend/app/api/thresholds.py +++ b/backend/app/api/thresholds.py @@ -94,11 +94,19 @@ def suggest_by_type(kpi): def _kpi_type(kpi) -> str: - if kpi.kpi_code in KPI_TYPES["lower_better"]: + """KPI → 推荐类型(**保持历史默认语义**)。 + + 原实现为 if higher_better / elif lower_better / **else → middle_best**, + 即「不在任何清单里的编码」默认走适中型(如 C_REBATE_RATE 渠补率、 + BH_SALES_10 销售-任富海,其历史生成的 '52.33~107.67' / '偏离…%' 即出自该分支)。 + 此处默认值必须同为 middle_best,否则默认分支的推荐口径会被改变。 + """ + code = getattr(kpi, "kpi_code", None) + if code in KPI_TYPES["higher_better"]: + return "higher_better" + if code in KPI_TYPES["lower_better"]: return "lower_better" - if kpi.kpi_code in KPI_TYPES["middle_best"]: - return "middle_best" - return "higher_better" + return "middle_best" def _sanity_check(suggestion: dict) -> dict: diff --git a/backend/tests/test_threshold_parser.py b/backend/tests/test_threshold_parser.py index 197a8005..1b909a03 100644 --- a/backend/tests/test_threshold_parser.py +++ b/backend/tests/test_threshold_parser.py @@ -185,6 +185,34 @@ class TestSuggestFormat: for level in ("green", "yellow", "red"): assert is_valid_threshold(s2[level]["label"]), s2[level]["label"] + def test_kpi_type_default_preserves_legacy_middle_best(self): + """_kpi_type 默认分支必须保持历史语义 = middle_best(原实现的 else 分支)。""" + from app.api.thresholds import _kpi_type + + class K: + def __init__(self, code): + self.kpi_code = code + + assert _kpi_type(K("SALES_TOTAL")) == "higher_better" + assert _kpi_type(K("COST_CONTROL_RATE")) == "lower_better" + assert _kpi_type(K("C_REBATE_RATE")) == "middle_best" # 渠补率:历史为适中型 + assert _kpi_type(K("BH_SALES_10")) == "middle_best" # 销售-任富海:历史为适中型 + assert _kpi_type(K("MADE_UP_CODE")) == "middle_best" # 未知编码 → 默认适中型 + + def test_c_rebate_rate_suggestion_is_middle_best_range(self): + """回归:C_REBATE_RATE 建议仍走适中型区间口径(不得退化为更高的单边界)。""" + from app.api.thresholds import _kpi_type, build_suggestion + + class K: + kpi_code = "C_REBATE_RATE" + + t = _kpi_type(K()) + s = build_suggestion(t, 80.0, tolerance=27.67) + assert t == "middle_best" + assert s["green"]["label"] == "52.33~107.67" + assert is_valid_threshold(s["yellow"]["label"]) + assert is_valid_threshold(s["red"]["label"]) + def test_middle_best_zero_tolerance_guard(self): """容忍度退化时不得生成 A~A 之类不可用表达式""" s = build_suggestion("middle_best", 0.0, tolerance=0.0)