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)