fix(cma): T4 residual bugs - predict 500 + reports restateDiffClass (cma-t4-residual-bugs-20260916)
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:
Hermes CI Fix
2026-09-16 02:07:42 +08:00
parent ae6f71dbc0
commit d54ffaf531
2 changed files with 10 additions and 11 deletions
+9 -10
View File
@@ -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,