fix(cma): T4 residual bugs - predict 500 + reports restateDiffClass (cma-t4-residual-bugs-20260916)
ci/woodpecker/push/main Pipeline failed
ci/woodpecker/push/main Pipeline failed
B1: /api/cma/dashboard/predict hand-rolled threshold parsing called float() on the whole
expression -> ValueError on multi-clause thresholds like '<=36|>=124' -> 500. Now routes
through app.utils.threshold_parser.eval_threshold (single source of truth; red then yellow,
invalid/blank expr -> False, no crash).
B2: ReportCenter.vue restatement tab template called restateDiffClass while the script
defines restatementDiffClass (name mismatch since ec6af75) -> TypeError on render.
Verified: predict 200 on new code vs 500 on live code; /reports A/B browser run shows 0 vs 3
occurrences of the TypeError; pytest 1113 passed / 1 xfailed; vite build OK.
This commit is contained in:
@@ -10,6 +10,7 @@ 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
|
||||
from app.utils.threshold_parser import eval_threshold
|
||||
# 指标方向(反向/正向)唯一权威源 = app.api.kpis.REVERSE_INDICATORS(2026-09-15 M2 准入2 收口)。
|
||||
# 本文件原自带一份字面量副本,与 kpis.py 漂移后会与「能力评分」判定相反,故改为导入。
|
||||
from app.api.kpis import REVERSE_INDICATORS
|
||||
@@ -529,17 +530,15 @@ def predict_kpis(db: Session = Depends(get_db), entity_id: int = Depends(get_ent
|
||||
predicted_value = a + b * n
|
||||
|
||||
# 检查预测值是否触发阈值
|
||||
# 统一走 app.utils.threshold_parser(阈值语法单一事实源,2026-09-16 B1 修复):
|
||||
# 原实现手工切操作符 + float(),只识别单条件标量表达式;生产库存在多条件
|
||||
# 表达式(如 `<=36|>=124`)/区间/负数 → float() 抛 ValueError → 端点 500。
|
||||
# eval_threshold 对非法/空表达式返回 False(不致命),并支持 A~B、C1|C2、负数。
|
||||
alert_level = "none"
|
||||
if k.threshold_red:
|
||||
op = k.threshold_red[:2] if k.threshold_red[1] in "=<>" else k.threshold_red[0]
|
||||
val = float(k.threshold_red.replace(op, "").strip())
|
||||
if (op in (">=", ">") and predicted_value >= val) or (op in ("<=", "<") and predicted_value <= val):
|
||||
alert_level = "red"
|
||||
if alert_level == "none" and k.threshold_yellow:
|
||||
op = k.threshold_yellow[:2] if k.threshold_yellow[1] in "=<>" else k.threshold_yellow[0]
|
||||
val = float(k.threshold_yellow.replace(op, "").strip())
|
||||
if (op in (">=", ">") and predicted_value >= val) or (op in ("<=", "<") and predicted_value <= val):
|
||||
alert_level = "yellow"
|
||||
if eval_threshold(predicted_value, k.threshold_red):
|
||||
alert_level = "red"
|
||||
elif eval_threshold(predicted_value, k.threshold_yellow):
|
||||
alert_level = "yellow"
|
||||
|
||||
predictions.append({
|
||||
"kpi_id": k.id,
|
||||
|
||||
@@ -396,7 +396,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="差异" width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.difference != null" :class="restateDiffClass(row.difference)">
|
||||
<span v-if="row.difference != null" :class="restatementDiffClass(row.difference)">
|
||||
{{ row.difference > 0 ? '+' : '' }}{{ formatMoney(row.difference) }}
|
||||
</span>
|
||||
<span v-else-if="row.needs_adjustment" class="restate-new-badge">新</span>
|
||||
|
||||
Reference in New Issue
Block a user