ASK-20260924-15/17: Bot 通道缺省账套→400 + 差异/趋势入口期间口径回落

- deps.get_entity_id: Bot 通道(带 X-BOT-KEY)无显式账套 → 400(旧为兜底 entity 1=酣客);
  无 key 的存量公开/用户端点行为不变(仍 401/兜底 1)
- 新增 deps.require_bot_entity_id():写端点缺省账套一处收口 → 400(406/422 同族)
  接入 bot_bridge(×2) / bot_bridge_v2 / bot_suggestions / cash_vouchers_json,并同步文档口径
- deviation_engine.run_deviation_check: period=None 不再硬编码当月,逐 KPI 回落
  「该 KPI 自己的最近可用期间」(裁定2b,单一实现收口到 alert_entity_scope);(当月无
  kpi_values ⇒ 差异/趋势入口永久 0 新增);R2/R3 窗口判定改为逐 KPI;显式 period 语义不变
- alert_generator.resolve_latest_period 委托同一实现(消除期间口径两份)
- 测试:新增 tests/test_ask15_17_engine_and_entity_required_20260925.py(12 例);
  4 个既有测试文件把「旧兜底账套 1」显式化(断言未改)
This commit is contained in:
wecom-fullstack
2026-09-25 22:42:25 +08:00
parent 38787a006f
commit 52b693ddbb
13 changed files with 353 additions and 43 deletions
+11 -3
View File
@@ -9,7 +9,7 @@ from sqlalchemy import func, desc
from datetime import datetime
from typing import Optional
from app.database import get_db
from app.deps import get_entity_id
from app.deps import get_entity_id, require_bot_entity_id
from app.models import (
User, UserEntity, StrategicMap, KPIDefinition, KPITemplate, KPIValue,
DataSourceConfig, KPIAlert, OperationLog, NotificationChannel,
@@ -666,7 +666,8 @@ def bot_kpi_value_with_check(data: dict, db: Session = Depends(get_db), bot: dic
kpi_id = data.get("kpi_id")
actual_value = data.get("actual_value")
period = data.get("period")
entity_id = int(data.get("entity_id") or 1)
# ASK-20260924-17(任总 2026-09-24 放行):Bot 通道缺省账套 = 报错,不再静默落 entity 1
entity_id = require_bot_entity_id(data, source="bot_bridge.kpi-value-with-check")
run_check = bool(data.get("run_check", True))
if not kpi_id or actual_value is None:
raise HTTPException(400, "kpi_id 和 actual_value 必填")
@@ -753,7 +754,7 @@ def bot_kpi_create_with_links(data: dict, db: Session = Depends(get_db), bot: di
考核基准类(目标值)= 人拍板;target_value 一律显式置 0(满足治理必填,
后续由人工设定或走 POST /api/cma/bot/ai/suggestions 建议通道待审批)。
"""
entity_id = int(data.get("entity_id") or 1)
entity_id = require_bot_entity_id(data, source="bot_bridge.kpis.create-with-links")
from app.api.kpis import _validate_kpi_data, apply_calc_type_inference
kpi_data = {k: v for k, v in data.items() if k not in ("entity_id", "link_map_id", "link_causality")}
@@ -1202,6 +1203,13 @@ def bot_subjects_upsert(
true → 更新 name/parent/level/category —— **但只更新本账套私有行**;
同码若只以**全局标准科目**(`entity_id IS NULL`)存在,则**永不被账套 UPSERT 改写**(计入 `skipped_global`),
同时也不再插入本账套同码副本(避免标准科目重复)。
- **幂等语义(2026-09-24 R6 收口,判据勿误读)**:`update_existing=true` ⇒ **批内既存行全量重写**
(逐行 UPDATE,不做值级 diff)⇒ 返回的 `updated=N` 是「**批内既存行数**」,**不是**「值发生变化的行数」,
且**含本轮 `inserted` 的新行**:同一 payload 连跑两次,首跑 `inserted=k, updated=m`,
复跑 `inserted=0, updated=k+m`(实测例:首跑 `inserted=5/updated=2` → 复跑 `updated=7`,
见 `operation_logs` detail 原文 id=347/348)。此形态**属既定行为、非异常写入**:库内不产生重复行
(唯一键 `(entity_id, subject_code)` + 只 UPDATE 不 DELETE/INSERT)。幂等判据取 `inserted=0`
且 `before == after`(叠加 `COUNT(*)`/`MAX(id)` 不变),**不得用 `updated` 判幂等**。
- dry_run=true → 只校验与回显,零写入
返回: {"ok":true,"inserted":n,"updated":m,"skipped":k,"skipped_global":g,"entity_id":e,"errors":[]};未授权 401。
"""