feat: DAMA数据治理规则检查(财务七规则)— 后端/check-governance + 前端数据质量中心升级
后端 backend/app/api/data_quality.py: - 新增 GET /api/cma/data-quality/check-governance?entity_id= 端点 - 7条规则: unit_check单位校验/dup_alert重复预警/orphan_check孤儿预警/virtual_pollution虚拟污染/entity_check实体归属/kpi_completeness KPI完整性/reconciliation勾稽验证 - 质量评分: error扣10/warning扣5, 每条规则最多扣一次, 最低0分 - 兼容实体筛选(0全部/1酣客/2博海), 向后兼容既有/check端点 前端 frontend/src/api/index.ts + views/DataQuality.vue: - dataQualityApi.checkGovernance() 新增 - 页面新增治理规则区: 质量评分仪表盘 + 7规则红黄绿状态表 + 异常明细展开 + 重新检查按钮 + 实体筛选
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func, and_
|
from sqlalchemy import func, and_, text
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
@@ -322,3 +322,197 @@ def quality_stats(db: Session = Depends(get_db)):
|
|||||||
"threshold_days": 180,
|
"threshold_days": 180,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DAMA 数据治理规则检查(财务七规则)— 2026-08-30 P1
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
RULES_META = {
|
||||||
|
"unit_check": {"name": "单位校验", "level": "error", "desc": "cash_plans.amount > 10000(万元口径可疑,疑似单位错乱)"},
|
||||||
|
"dup_alert": {"name": "重复预警", "level": "error", "desc": "同一plan_id存在多条pending应收预警(去重键错误)"},
|
||||||
|
"orphan_check": {"name": "孤儿预警", "level": "error", "desc": "预警suggestion.plan_id指向不存在的cash_plans记录"},
|
||||||
|
"virtual_pollution": {"name": "虚拟污染", "level": "error", "desc": "cash_plans.source含test/虚拟等测试标识混入真实数据"},
|
||||||
|
"entity_check": {"name": "实体归属", "level": "error", "desc": "kpi_values.entity_id与kpi_definitions.entity_id不一致"},
|
||||||
|
"kpi_completeness": {"name": "KPI完整性", "level": "warning", "desc": "active状态KPI无任何实际值的数量"},
|
||||||
|
"reconciliation": {"name": "勾稽验证", "level": "warning", "desc": "预算月度合计 vs 年度目标差异>20%"},
|
||||||
|
}
|
||||||
|
|
||||||
|
DETAIL_LIMIT = 10 # 每条规则detail最多列出的条数(避免响应过大)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_rule_checks(db: Session, entity_id: int = 0):
|
||||||
|
"""执行7条DAMA治理规则,返回 issues 列表。entity_id=0 表示全部实体。"""
|
||||||
|
entity_filter = " AND cp.entity_id = :eid" if entity_id else ""
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# ── 规则1 单位校验 ──
|
||||||
|
rows = db.execute(text(
|
||||||
|
"SELECT cp.id, cp.entity_id, cp.amount, cp.source, cp.description "
|
||||||
|
"FROM cash_plans cp WHERE cp.amount > 10000" + entity_filter + " ORDER BY cp.amount DESC"
|
||||||
|
), {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "unit_check", "level": "error",
|
||||||
|
"count": len(rows),
|
||||||
|
"detail": [f"plan#{r.id} 金额{r.amount}(疑似元)" for r in rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d条" % len(rows)] if len(rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则2 重复预警(同plan_id多条pending应收预警)──
|
||||||
|
if entity_id:
|
||||||
|
dup_sql = text(
|
||||||
|
"SELECT JSON_EXTRACT(a.suggestion, '$.plan_id') AS pid, COUNT(*) c, MAX(p.entity_id) eid "
|
||||||
|
"FROM kpi_alerts a JOIN cash_plans p ON p.id = JSON_EXTRACT(a.suggestion, '$.plan_id') "
|
||||||
|
"WHERE a.alert_type='cash_plan' AND a.status='pending' AND JSON_VALID(a.suggestion) "
|
||||||
|
"AND a.suggestion LIKE '%plan_id%' AND p.entity_id = :eid "
|
||||||
|
"GROUP BY pid HAVING c > 1 ORDER BY c DESC"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
dup_sql = text(
|
||||||
|
"SELECT JSON_EXTRACT(a.suggestion, '$.plan_id') AS pid, COUNT(*) c, MAX(p.entity_id) eid "
|
||||||
|
"FROM kpi_alerts a JOIN cash_plans p ON p.id = JSON_EXTRACT(a.suggestion, '$.plan_id') "
|
||||||
|
"WHERE a.alert_type='cash_plan' AND a.status='pending' AND JSON_VALID(a.suggestion) "
|
||||||
|
"AND a.suggestion LIKE '%plan_id%' "
|
||||||
|
"GROUP BY pid HAVING c > 1 ORDER BY c DESC"
|
||||||
|
)
|
||||||
|
dup_rows = db.execute(dup_sql, {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "dup_alert", "level": "error",
|
||||||
|
"count": len(dup_rows),
|
||||||
|
"detail": [f"plan#{r.pid} 重复预警×{r.c}" for r in dup_rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d个plan" % len(dup_rows)] if len(dup_rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则3 孤儿预警(plan_id指向不存在的cash_plans)──
|
||||||
|
orphan_sql = text(
|
||||||
|
"SELECT a.id, a.kpi_id, JSON_EXTRACT(a.suggestion, '$.plan_id') AS pid "
|
||||||
|
"FROM kpi_alerts a "
|
||||||
|
"WHERE a.alert_type='cash_plan' AND a.status='pending' AND JSON_VALID(a.suggestion) "
|
||||||
|
"AND a.suggestion LIKE '%plan_id%' "
|
||||||
|
"AND NOT EXISTS (SELECT 1 FROM cash_plans p WHERE p.id = JSON_EXTRACT(a.suggestion, '$.plan_id')) "
|
||||||
|
"ORDER BY a.id LIMIT 200"
|
||||||
|
)
|
||||||
|
orphan_rows = db.execute(orphan_sql).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "orphan_check", "level": "error",
|
||||||
|
"count": len(orphan_rows),
|
||||||
|
"detail": [f"预警#{r.id}(kpi#{r.kpi_id}) → plan#{r.pid} 不存在" for r in orphan_rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d条" % len(orphan_rows)] if len(orphan_rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则4 虚拟污染(source含test/虚拟标识)──
|
||||||
|
rows = db.execute(text(
|
||||||
|
"SELECT cp.id, cp.entity_id, cp.source, cp.description FROM cash_plans cp "
|
||||||
|
"WHERE cp.source LIKE '%test%' OR cp.source LIKE '%虚拟%' OR cp.source LIKE '%demo%'"
|
||||||
|
+ entity_filter + " ORDER BY cp.id LIMIT 200"
|
||||||
|
), {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "virtual_pollution", "level": "error",
|
||||||
|
"count": len(rows),
|
||||||
|
"detail": [f"plan#{r.id} source={r.source}" for r in rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d条" % len(rows)] if len(rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则5 实体归属(kpi_values.entity_id != kpi_definitions.entity_id)──
|
||||||
|
if entity_id:
|
||||||
|
ent_sql = text(
|
||||||
|
"SELECT v.id, v.kpi_id, d.kpi_code, v.entity_id AS v_eid, d.entity_id AS d_eid "
|
||||||
|
"FROM kpi_values v JOIN kpi_definitions d ON v.kpi_id = d.id "
|
||||||
|
"WHERE v.entity_id != d.entity_id AND v.entity_id = :eid ORDER BY v.id LIMIT 200"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ent_sql = text(
|
||||||
|
"SELECT v.id, v.kpi_id, d.kpi_code, v.entity_id AS v_eid, d.entity_id AS d_eid "
|
||||||
|
"FROM kpi_values v JOIN kpi_definitions d ON v.kpi_id = d.id "
|
||||||
|
"WHERE v.entity_id != d.entity_id ORDER BY v.id LIMIT 200"
|
||||||
|
)
|
||||||
|
ent_rows = db.execute(ent_sql, {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "entity_check", "level": "error",
|
||||||
|
"count": len(ent_rows),
|
||||||
|
"detail": [f"值#{r.id} {r.kpi_code} 实体{r.v_eid}≠定义实体{r.d_eid}" for r in ent_rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d条" % len(ent_rows)] if len(ent_rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则6 KPI完整性(active KPI无任何值)──
|
||||||
|
if entity_id:
|
||||||
|
comp_sql = text(
|
||||||
|
"SELECT d.id, d.kpi_code, d.kpi_name FROM kpi_definitions d "
|
||||||
|
"WHERE d.status='active' AND d.entity_id = :eid "
|
||||||
|
"AND NOT EXISTS (SELECT 1 FROM kpi_values v WHERE v.kpi_id = d.id) ORDER BY d.id LIMIT 300"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
comp_sql = text(
|
||||||
|
"SELECT d.id, d.kpi_code, d.kpi_name FROM kpi_definitions d "
|
||||||
|
"WHERE d.status='active' "
|
||||||
|
"AND NOT EXISTS (SELECT 1 FROM kpi_values v WHERE v.kpi_id = d.id) ORDER BY d.id LIMIT 300"
|
||||||
|
)
|
||||||
|
comp_rows = db.execute(comp_sql, {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "kpi_completeness", "level": "warning",
|
||||||
|
"count": len(comp_rows),
|
||||||
|
"detail": [f"{r.kpi_code} {r.kpi_name}(无值)" for r in comp_rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d个KPI" % len(comp_rows)] if len(comp_rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── 规则7 勾稽验证(预算月度合计 vs 年度目标差异>20%)──
|
||||||
|
if entity_id:
|
||||||
|
recon_sql = text(
|
||||||
|
"SELECT d.kpi_code, d.kpi_name, d.target_yearly, "
|
||||||
|
"SUM(b.budget_value) AS monthly_sum, "
|
||||||
|
"ROUND((SUM(b.budget_value) - d.target_yearly) / d.target_yearly * 100, 1) AS diff_pct "
|
||||||
|
"FROM kpi_definitions d JOIN budget_plans b ON b.kpi_id = d.id "
|
||||||
|
"WHERE d.status='active' AND d.target_yearly > 0 AND d.entity_id = :eid "
|
||||||
|
"GROUP BY d.id HAVING ABS(diff_pct) > 20 ORDER BY ABS(diff_pct) DESC LIMIT 200"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
recon_sql = text(
|
||||||
|
"SELECT d.kpi_code, d.kpi_name, d.target_yearly, "
|
||||||
|
"SUM(b.budget_value) AS monthly_sum, "
|
||||||
|
"ROUND((SUM(b.budget_value) - d.target_yearly) / d.target_yearly * 100, 1) AS diff_pct "
|
||||||
|
"FROM kpi_definitions d JOIN budget_plans b ON b.kpi_id = d.id "
|
||||||
|
"WHERE d.status='active' AND d.target_yearly > 0 "
|
||||||
|
"GROUP BY d.id HAVING ABS(diff_pct) > 20 ORDER BY ABS(diff_pct) DESC LIMIT 200"
|
||||||
|
)
|
||||||
|
recon_rows = db.execute(recon_sql, {"eid": entity_id}).fetchall()
|
||||||
|
issues.append({
|
||||||
|
"rule": "reconciliation", "level": "warning",
|
||||||
|
"count": len(recon_rows),
|
||||||
|
"detail": [f"{r.kpi_code} 预算合计{round(r.monthly_sum, 1)} vs 年度目标{r.target_yearly} 差异{r.diff_pct}%" for r in recon_rows[:DETAIL_LIMIT]]
|
||||||
|
+ (["…等%d个KPI" % len(recon_rows)] if len(recon_rows) > DETAIL_LIMIT else []),
|
||||||
|
})
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/check-governance")
|
||||||
|
def check_governance(
|
||||||
|
entity_id: Optional[int] = Query(0, description="实体ID: 0=全部, 1=酣客, 2=博海"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""DAMA数据治理规则检查(财务七规则)— 返回质量评分+异常清单。
|
||||||
|
|
||||||
|
评分规则: 满分100,error级规则每条扣10分,warning级规则每条扣5分,
|
||||||
|
每条规则最多扣一次分(按规则是否命中,不按count累扣),最低0分。
|
||||||
|
"""
|
||||||
|
issues = _run_rule_checks(db, entity_id or 0)
|
||||||
|
|
||||||
|
# 计算评分
|
||||||
|
score = 100
|
||||||
|
for item in issues:
|
||||||
|
if item["count"] > 0:
|
||||||
|
score -= 10 if item["level"] == "error" else 5
|
||||||
|
score = max(0, score)
|
||||||
|
|
||||||
|
passed = [item["rule"] for item in issues if item["count"] == 0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"checked_at": datetime.now().isoformat(timespec="seconds"),
|
||||||
|
"entity_id": entity_id or 0,
|
||||||
|
"score": score,
|
||||||
|
"total_rules": len(issues),
|
||||||
|
"issues": issues,
|
||||||
|
"passed": passed,
|
||||||
|
"rules_meta": RULES_META,
|
||||||
|
}
|
||||||
|
|||||||
@@ -344,6 +344,8 @@ export const dataQualityApi = {
|
|||||||
logs: (params?: any) => api.get('/data-quality/logs', { params }),
|
logs: (params?: any) => api.get('/data-quality/logs', { params }),
|
||||||
updateLog: (id: number, data: any) => api.put(`/data-quality/logs/${id}`, data),
|
updateLog: (id: number, data: any) => api.put(`/data-quality/logs/${id}`, data),
|
||||||
deleteLog: (id: number) => api.delete(`/data-quality/logs/${id}`),
|
deleteLog: (id: number) => api.delete(`/data-quality/logs/${id}`),
|
||||||
|
// DAMA数据治理规则检查(财务七规则)
|
||||||
|
checkGovernance: (params?: any) => api.get('/data-quality/check-governance', { params }),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const biReportApi = {
|
export const biReportApi = {
|
||||||
|
|||||||
@@ -121,6 +121,80 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- DAMA 数据治理规则检查(财务七规则) -->
|
||||||
|
<el-card shadow="never" class="section-gap">
|
||||||
|
<template #header>
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||||
|
<span>🛡️ DAMA数据治理规则检查(财务七规则)</span>
|
||||||
|
<div>
|
||||||
|
<el-select v-model="govEntity" style="width:130px;margin-right:8px;" @change="runGovernanceCheck">
|
||||||
|
<el-option label="全部实体" :value="0" />
|
||||||
|
<el-option label="酣客" :value="1" />
|
||||||
|
<el-option label="博海" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" @click="runGovernanceCheck" :loading="govChecking">重新检查</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="6">
|
||||||
|
<div style="text-align:center;padding:20px 0;">
|
||||||
|
<el-progress type="dashboard" :percentage="govScore" :color="govScoreColor" :width="150">
|
||||||
|
<template #default>
|
||||||
|
<div style="font-size:32px;font-weight:700;line-height:1.2;">{{ govScore }}</div>
|
||||||
|
<div style="font-size:12px;color:#999;">质量评分</div>
|
||||||
|
</template>
|
||||||
|
</el-progress>
|
||||||
|
<div style="font-size:12px;color:#999;margin-top:10px;line-height:1.8;">
|
||||||
|
检查时间: {{ govCheckedAt || '-' }}<br />
|
||||||
|
通过 {{ govPassedCount }} / {{ govIssues.length }} 条规则
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="18">
|
||||||
|
<el-table :data="govIssues" size="small" border>
|
||||||
|
<el-table-column type="expand">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div style="padding:10px 18px;background:#fafafa;">
|
||||||
|
<div v-if="row.detail && row.detail.length" style="font-size:12px;line-height:24px;color:#606266;">
|
||||||
|
<div v-for="(d, i) in row.detail" :key="i">• {{ d }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else style="font-size:12px;color:#67c23a;">✓ 未发现异常</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="govRuleStatus(row)" size="small">{{ govRuleStatusText(row) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规则" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="font-weight:600;">{{ govRuleName(row.rule) }}</span>
|
||||||
|
<div style="font-size:11px;color:#999;">{{ row.rule }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="检查内容" min-width="240">
|
||||||
|
<template #default="{ row }">{{ govRuleDesc(row.rule) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="级别" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.level === 'error' ? 'danger' : 'warning'" size="small">
|
||||||
|
{{ row.level === 'error' ? '异常' : '警告' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="异常数" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :style="{ color: row.count > 0 ? '#f56c6c' : '#67c23a', fontWeight: 600 }">{{ row.count }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
<!-- 质量日志列表 -->
|
<!-- 质量日志列表 -->
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -199,6 +273,14 @@ const checking = ref(false)
|
|||||||
const filterSeverity = ref('')
|
const filterSeverity = ref('')
|
||||||
const filterStatus = ref('')
|
const filterStatus = ref('')
|
||||||
|
|
||||||
|
// DAMA 数据治理规则检查状态
|
||||||
|
const govScore = ref(0)
|
||||||
|
const govIssues = ref<any[]>([])
|
||||||
|
const govCheckedAt = ref('')
|
||||||
|
const govEntity = ref(0)
|
||||||
|
const govChecking = ref(false)
|
||||||
|
const govRuleMeta = ref<any>({})
|
||||||
|
|
||||||
const typeChartOption = computed(() => ({
|
const typeChartOption = computed(() => ({
|
||||||
tooltip: { trigger: 'item' },
|
tooltip: { trigger: 'item' },
|
||||||
series: [{
|
series: [{
|
||||||
@@ -228,6 +310,33 @@ const staleDataColor = computed(() => {
|
|||||||
return '#f56c6c'
|
return '#f56c6c'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ── DAMA治理规则 computed ──
|
||||||
|
const govPassedCount = computed(() => govIssues.value.filter((i: any) => i.count === 0).length)
|
||||||
|
|
||||||
|
const govScoreColor = computed(() => {
|
||||||
|
if (govScore.value >= 90) return '#67c23a'
|
||||||
|
if (govScore.value >= 60) return '#e6a23c'
|
||||||
|
return '#f56c6c'
|
||||||
|
})
|
||||||
|
|
||||||
|
function govRuleStatus(row: any) {
|
||||||
|
if (row.count > 0) return row.level === 'error' ? 'danger' : 'warning'
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
function govRuleStatusText(row: any) {
|
||||||
|
if (row.count > 0) return row.level === 'error' ? '异常' : '警告'
|
||||||
|
return '通过'
|
||||||
|
}
|
||||||
|
|
||||||
|
function govRuleName(rule: string) {
|
||||||
|
return govRuleMeta.value?.[rule]?.name || rule
|
||||||
|
}
|
||||||
|
|
||||||
|
function govRuleDesc(rule: string) {
|
||||||
|
return govRuleMeta.value?.[rule]?.desc || ''
|
||||||
|
}
|
||||||
|
|
||||||
async function loadStats() {
|
async function loadStats() {
|
||||||
try { const r: any = await dataQualityApi.stats(); stats.value = r } catch (e) {}
|
try { const r: any = await dataQualityApi.stats(); stats.value = r } catch (e) {}
|
||||||
}
|
}
|
||||||
@@ -257,6 +366,24 @@ async function runCheck() {
|
|||||||
checking.value = false
|
checking.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runGovernanceCheck() {
|
||||||
|
govChecking.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {}
|
||||||
|
if (govEntity.value) params.entity_id = govEntity.value
|
||||||
|
const r: any = await dataQualityApi.checkGovernance(params)
|
||||||
|
govScore.value = r.score ?? 0
|
||||||
|
govIssues.value = r.issues || []
|
||||||
|
govCheckedAt.value = (r.checked_at || '').replace('T', ' ').slice(0, 19)
|
||||||
|
govRuleMeta.value = r.rules_meta || {}
|
||||||
|
const bad = (r.issues || []).filter((i: any) => i.count > 0).length
|
||||||
|
ElMessage.success(`治理检查完成: 评分${r.score}分, 异常规则${bad}条`)
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.response?.data?.detail || '治理检查失败')
|
||||||
|
}
|
||||||
|
govChecking.value = false
|
||||||
|
}
|
||||||
|
|
||||||
async function updateStatus(id: number, status: string) {
|
async function updateStatus(id: number, status: string) {
|
||||||
try {
|
try {
|
||||||
await dataQualityApi.updateLog(id, { status })
|
await dataQualityApi.updateLog(id, { status })
|
||||||
@@ -269,6 +396,7 @@ async function updateStatus(id: number, status: string) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadStats()
|
loadStats()
|
||||||
loadLogs()
|
loadLogs()
|
||||||
|
runGovernanceCheck()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user