fix(h2-forecast): 预估区残余读路径 P2 收口(3 类 6 处)

任总 2026-10-02 11:35 直令「对外 /api/cma/** 读 KPIValue 默认只取实测区」同族残余
(ac4bbc6 范围外的 3 类 6 处)。6 处一律引 app.utils.kpi_value_scope.actual_only_clause(),
零新写字面量口径:
- customer_dashboard.py:76    年视图汇总(预估禁与实测相加)
- budget.py:260               按去年实绩加权派生基数(批量 auto-decompose)
- budget.py:333               同上(单 KPI auto-decompose)
- cash_forecast_engine.py:204 get_entity_kpi_history 历史/趋势序列
- cash_forecast_engine.py:259 get_current_cash_balance 最新值
- cash_forecast_engine.py:471 calculate_accuracy 实际值序列(预测输入)

证据:同脚本双树 A/B 15/15(改前 5/15,真库 kpi-forecast history 去掉 2026-H2、
next_target 336.32→250.99);分类计数 33/41/9/3 → 39 covered/38 period_guarded/9 write/0 unguarded;
pytest 3 failed/1529 passed,失败集与基线逐条同 ID。不 push / 不部署 / 不重启 / 不写库。
Task: project-20261002-h2-forecast-residual-reads-p2-fullstack
This commit is contained in:
Hermes CI Fix
2026-10-02 23:41:17 +08:00
parent 5d7b3cc728
commit 1da0a74568
3 changed files with 8 additions and 1 deletions
+2
View File
@@ -258,6 +258,7 @@ def auto_decompose_budget(
last_year = year - 1
values = db.query(KPIValue).filter(
KPIValue.kpi_id == kid,
actual_only_clause(), # 实测区口径:预估行禁作预算派生基数(kpi_value_scope)
KPIValue.period.like(f"{last_year}-%"),
KPIValue.actual_value.isnot(None),
).order_by(KPIValue.period.asc()).all()
@@ -331,6 +332,7 @@ def auto_decompose_budget(
last_year = year - 1
values = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi_id,
actual_only_clause(), # 实测区口径:预估行禁作预算派生基数(kpi_value_scope)
KPIValue.period.like(f"{last_year}-%"),
KPIValue.actual_value.isnot(None),
).order_by(KPIValue.period.asc()).all()
+2 -1
View File
@@ -9,6 +9,7 @@ from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import KPIDefinition, KPIValue, KPIAlert, User
from app.utils.alert_entity_scope import month_period_clause
from app.utils.kpi_value_scope import actual_only_clause
import logging
logger = logging.getLogger("cma.customer")
@@ -73,7 +74,7 @@ def list_customer_kpis(
latest_val = sum(v.actual_value for v in values if v.actual_value) if values else None
latest = type('obj', (object,), {"actual_value": latest_val, "period": f"{months[0]}~{months[-1]}"})() if latest_val else None
elif period == "year":
values = base_query.filter(KPIValue.period.like(f"{period_str[:4]}%")).all()
values = base_query.filter(actual_only_clause(), KPIValue.period.like(f"{period_str[:4]}%")).all()
latest_val = sum(v.actual_value for v in values if v.actual_value) if values else None
latest = type('obj', (object,), {"actual_value": latest_val, "period": period_str[:4]})() if latest_val else None
elif period == "custom" and start_date and end_date: