feat: 数据治理4条规则 — 入库必检+元数据+编码规范+战略分级
This commit is contained in:
+11
-28
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user