feat: 数据治理4条规则 — 入库必检+元数据+编码规范+战略分级

This commit is contained in:
Hermes CI Fix
2026-08-10 23:40:31 +08:00
parent e1ea5cd14d
commit 37f26148fe
5 changed files with 233 additions and 31 deletions
+11 -28
View File
@@ -9,6 +9,7 @@ import json
from app.database import get_db
from app.auth_middleware import require_auth, require_role, filter_kpis_by_role, kpi_visible_dims
from app.models import StrategicMap, MapObjective, KPIDefinition, KPIValue, KPIAlert, OperationLog, Entity, KPICausality, KPIHierarchy
from app.api.kpi_governance import validate_kpi_payload, kpi_issues_message
router = APIRouter(prefix="/api/cma/kpis", tags=["KPI字典"],
dependencies=[Depends(require_role("ceo", "finance", "business", "it"))],
@@ -27,6 +28,7 @@ def list_kpis(
epic: Optional[str] = None,
category: Optional[str] = None,
entity_id: Optional[int] = None,
kpi_level: Optional[str] = None,
db: Session = Depends(get_db),
current_user = Depends(require_auth),
):
@@ -45,6 +47,8 @@ def list_kpis(
query = query.filter(KPIDefinition.category.in_(cats_list))
if entity_id is not None:
query = query.filter(KPIDefinition.entity_id == entity_id)
if kpi_level:
query = query.filter(KPIDefinition.kpi_level == kpi_level)
total = query.count()
kpis = query.order_by(KPIDefinition.kpi_code).offset((page-1)*page_size).limit(page_size).all()
result = {"total": total, "page": page, "page_size": page_size, "data": [kpi_to_dict(k) for k in kpis]}
@@ -477,31 +481,10 @@ def get_kpi(kpi_id: int, db: Session = Depends(get_db)):
return kpi_to_dict(kpi)
def _validate_kpi_data(data: dict, is_update: bool = False):
"""数据治理:入库必检 + 元数据校验"""
errors = []
# 规则1: target_value 必填
tv = data.get("target_value")
if tv is None or (isinstance(tv, (int, float)) and tv < 0 and not is_update):
if not is_update or "target_value" in data:
if tv is None:
errors.append("目标值(target_value)不能为空")
# 规则1: unit 必填
unit = data.get("unit")
if not unit or (isinstance(unit, str) and unit.strip() == ""):
if not is_update or "unit" in data:
errors.append("单位(unit)不能为空")
# 规则2: 元数据必填 — formula/data_source/data_owner
for field, label in [("formula", "计算公式"), ("data_source", "数据来源"), ("data_owner", "数据责任人")]:
val = data.get(field)
if not val or (isinstance(val, str) and val.strip() == ""):
if not is_update or field in data:
errors.append(f"元数据字段'{label}'({field})不能为空")
return errors
def _validate_kpi_data(data: dict, db: Session, current_kpi_id: Optional[int] = None, is_update: bool = False):
"""数据治理4条规则校验(入库必检+元数据+编码规范+战略分级),返回错误信息列表"""
issues = validate_kpi_payload(data, db=db, current_kpi_id=current_kpi_id, is_update=is_update)
return kpi_issues_message(issues)
@router.post("")
@@ -510,8 +493,8 @@ def create_kpi(data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
existing = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == data.get("kpi_code", "")).first()
if existing:
raise HTTPException(400, f"KPI编码 {data['kpi_code']} 已存在")
# 数据治理校验
errs = _validate_kpi_data(data, is_update=False)
# 数据治理校验(规则1强制拦截)
errs = _validate_kpi_data(data, db=db, is_update=False)
if errs:
raise HTTPException(422, detail={"message": "数据校验不通过", "errors": errs})
kpi = KPIDefinition(**data)
@@ -528,7 +511,7 @@ def update_kpi(kpi_id: int, data: dict, db: Session = Depends(get_db), user=WRIT
if not kpi:
raise HTTPException(404, "KPI不存在")
# 数据治理校验(更新时只检查传了但为空的字段)
errs = _validate_kpi_data(data, is_update=True)
errs = _validate_kpi_data(data, db=db, current_kpi_id=kpi_id, is_update=True)
if errs:
raise HTTPException(422, detail={"message": "数据校验不通过", "errors": errs})
for k, v in data.items():