feat(entity-dim): 账套维度收口批 — subjects/analysis_results 加列 + 分析结论账套化 + 模板实例化构造点补账套
ci/woodpecker/push/main Pipeline failed

- ① kpi_templates instantiate 构造点显式传会话账套(原缺 entity_id ⇒ 静默落 e1),kpi_code 判重按账套收紧
- ② kpi_subject_map 3 个读取点加护栏注释(不加列、不改语义)
- ③ subjects 分层:读口 union(NULL=全局标准准则科目 / N=账套私有),UPSERT 唯一键改 (entity_id, subject_code)
- ④ analysis_results 账套化:读/写/删三端点按会话账套判定归属(列未落地时 kpi_code JOIN 止血),
     无归属行进 unattributed 登记、删除一律 404
- 模型层按现网 DDL 补齐:KPIDefinition(template_id/is_system + uk_entity_kpi)、AnalysisResult.entity_id、Subject.entity_id
- 新增 migrate_{subjects,analysis_results}_entity_id_20260923.py(--check/--apply/--down)、
  22 条对账用例 test_entity_dimension_convergence_20260923.py

DDL 已由 ops 受控通道落地(subjects 21:31:56 / analysis_results 21:31:59 CST,审批 1e9b4d9a/5ffb1640)
本提交为切片提交,未 push(发布须另取放行)
This commit is contained in:
Hermes CI Fix
2026-09-23 21:39:24 +08:00
parent 0b3a8aa111
commit fb4609be14
14 changed files with 1429 additions and 65 deletions
+18 -5
View File
@@ -8,6 +8,7 @@ from datetime import datetime
from app.database import get_db
from app.auth_middleware import require_role
from app.deps import get_entity_id
from app.models import KPITemplate, KPIDefinition, OperationLog
from app.api.kpi_governance import validate_kpi_payload, kpi_issues_message
@@ -107,8 +108,16 @@ def delete_template(template_id: int, db: Session = Depends(get_db), user=WRITE_
@router.post("/{template_id}/instantiate")
def instantiate_template(template_id: int, data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
"""从模板实例化创建KPI,复制模板快照到kpi_definitions"""
def instantiate_template(template_id: int, data: dict, db: Session = Depends(get_db),
user=WRITE_ROLES, entity_id: int = Depends(get_entity_id)):
"""从模板实例化创建KPI,复制模板快照到kpi_definitions
账套维度(2026-09-23 账套维度收口批 ①):新 KPI 显式挂**会话账套**,不做静默兜底。
历史缺陷:本构造点未传 `entity_id` ⇒ 模型 `default=1` 把任意账套实例化出的 KPI
静默挂到酣客(e1),与「建预算未写账套」同源。
判重口径:`kpi_code` 唯一性按账套判定(库内 `uk_entity_kpi(UNIQUE entity_id,kpi_code)`);
全表判重会让博海无法建与酣客同码的 KPI(反之则放过同账套重复)。
"""
t = db.query(KPITemplate).filter(KPITemplate.id == template_id).first()
if not t:
raise HTTPException(404, "模板不存在")
@@ -116,10 +125,13 @@ def instantiate_template(template_id: int, data: dict, db: Session = Depends(get
kpi_code = data.get("kpi_code", t.kpi_code)
kpi_name = data.get("kpi_name", t.kpi_name)
# 检查编码唯一性
existing = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == kpi_code).first()
# 检查编码唯一性(按账套收紧:仅本账套内判重)
existing = db.query(KPIDefinition).filter(
KPIDefinition.kpi_code == kpi_code,
KPIDefinition.entity_id == entity_id,
).first()
if existing:
raise HTTPException(400, f"KPI编码 {kpi_code} 已存在,请修改")
raise HTTPException(400, f"KPI编码 {kpi_code} 在本账套已存在,请修改")
# 数据治理:入库必检(规则1) + 编码规范(规则3)
merged = {
@@ -136,6 +148,7 @@ def instantiate_template(template_id: int, data: dict, db: Session = Depends(get
raise HTTPException(422, detail={"message": "数据校验不通过", "errors": kpi_issues_message(gov_issues)})
kpi = KPIDefinition(
entity_id=entity_id, # 账套维度:显式挂会话账套(不得依赖模型 default=1)
template_id=t.id,
is_system=0, # 从模板实例化的KPI不是系统预置
kpi_code=kpi_code,