Просмотр исходного кода

docs(kpi): 指标模型动态配置方案补档 Phase 5/6/7 + 扩充后续待办 + bump 2.4.89/1.0.56

- 顶部状态更新至 Phase 1-7 完成
- §6 Demo 租户隔离标记为已完成(2026-04 部署)
- 新增 §7 Phase 6 S1-S7/S9 统一动态看板纪要
- 新增 §8 Phase 7 公式结构化 v1 纪要(DDL/DSL/校验策略/接口/字典初始化/验收)
- 重写 §9「遗留与后续待办」,分 6 块:
  - §9.1 Phase 8 公式驱动 ETL(主线:FactCode→SQL 映射、求值器、调度器、租户开关、环检测、回看)
  - §9.2 存量 Formula 渐进迁移(半自动脚本、进度面板、冻结保护)
  - §9.3 业务事实字典增强(导入导出、频次反查、单位审核、同名合并)
  - §9.4 FormulaEditor 增强(tag 化、悬浮、依赖链、历史版本)
  - §9.5 框架补强(AidopMenuLinkSync 建菜单、Id 冲突、权限收敛)
  - §9.6 Phase 1-4 历史遗留保留原列

Made-with: Cursor
skygu 1 месяц назад
Родитель
Сommit
863b935b1c

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.88",
+	"version": "2.4.89",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 128 - 5
doc/指标模型动态配置方案.md

@@ -1,6 +1,6 @@
 # 指标模型动态配置方案
 
-> 最后更新:2026-04-12 | 状态:Phase 1–4 已完成并部署
+> 最后更新:2026-04-19 | 状态:Phase 1–4(S4 统一主数据)+ Phase 5(Demo 租户隔离)+ Phase 6(S1–S7/S9 统一动态看板)+ Phase 7(公式结构化 v1)已完成并部署
 
 ---
 
@@ -210,7 +210,7 @@ Dockerfile 已优化为分层缓存策略:
 
 ## 6. Demo 数据租户隔离方案
 
-> 状态:待执行
+> 状态:已完成(2026-04 部署)。Demo 租户 `1300000000888`、`DemoAdmin` 账号、全部业务数据 `TenantId` 迁移、前端去 tenantId 硬编码均已落地并验证双账号并行登录。
 
 ### 6.1 背景与需求
 
@@ -329,11 +329,134 @@ mysql -h... -u... -p... aidopdev < scripts/import-demo-data.sql
 
 ---
 
-## 7. 遗留与后续
+## 7. Phase 6 — S1~S7/S9 统一动态看板(2026-04 完成)
+
+> 对应提交 `d12b00cb feat(kanban): S1-S7/S9 统一动态指标看板(对齐 S4 样式)+ bump 2.4.87/1.0.54`
+
+- `ModuleDashboardPage.vue` 抽出共享看板组件,各模块页面退化为薄壳,通过 `module-code` + 额外筛选槽注入差异
+- 后端新增 `AidopKanbanController.Generic.cs` 三个泛型端点:`/home-grid/{moduleCode}`、`/detail-kpis/{moduleCode}`、`/operation-layout/{moduleCode}`
+- `LayoutItem`/`HomeModule` 为非 S4 模块按 `MetricName` 模式自动写入 PanelZone(Python 脚本 `seed_layout_items_all_modules.py`),两租户均已落地
+- S9 保留汇总风格但数据动态化(移除 `slice(0, 6)` 限制)
+- S8 本阶段不改
+
+---
+
+## 8. Phase 7 — 公式结构化 v1(2026-04 完成)
+
+> 对应提交 `8a35c0aa feat(kpi): 公式结构化 v1(@指标/$事实 DSL + 校验 + 预览)+ 业务事实字典 + bump 2.4.88/1.0.55`
+> 本阶段的核心决策:**分租户生效模式**(Demo 租户继续走 Python 手工导入;生产租户后续由 `FormulaExpr` 驱动 ETL)。本阶段**只做"文档层"**:结构化表达式、校验、人读预览;ETL 不在本阶段范围。
+
+### 8.1 新增数据模型
+
+| 表 / 列 | 说明 |
+|---------|------|
+| 新表 `ado_smart_ops_business_fact` | 业务事实字典:`FactCode`(英文稳定码)+ `FactName`(中文展示)+ `ModuleCode`/`Unit`/`AggType`/`DataSource`/`Description`/`IsEnabled`/`SortNo`/`TenantId`。唯一键 `(FactCode, TenantId)` |
+| `ado_smart_ops_kpi_master.FormulaExpr` | varchar(500),结构化表达式,语法见 §8.2 |
+| `ado_smart_ops_kpi_master.FormulaPreview` | varchar(1000),人读预览(引用替换中文名,`/`→`÷`) |
+| `ado_smart_ops_kpi_master.FormulaRefs` | varchar(500),JSON `{metrics:[],facts:[]}` 便于反查 |
+
+旧字段 `Formula` / `CalcRule` / `DopFields` **保留不动**(Q3-B 决策),前端编辑时并列显示为「原始公式(兼容)」供历史数据展示与渐进迁移。
+
+### 8.2 DSL v1 语法
+
+```
+FormulaExpr ::= Token (Operator Token)*
+Token       ::= MetricRef | FactRef | Number | '(' FormulaExpr ')'
+MetricRef   ::= '@' MetricCode        例:@S4_L1_001
+FactRef     ::= '$' FactCode          例:$F_S1_001, $F_COMMON_002
+Operator    ::= '+' | '-' | '*' | '/'
+Number      ::= 数字(支持小数、百分号 %)
+```
+
+仅做语法解析与引用抽取,**不做执行求值**。执行求值在 Phase 8 ETL 阶段接入。
+
+### 8.3 校验策略(Q4-C 决策)
+
+| 指标 `IsEnabled` | 语法错误 / 未知引用 | 行为 |
+|-----------------|-------------------|------|
+| `true`(启用) | 错误 | **Errors**,阻止保存,返回 400 + `errors[]` |
+| `false`(草稿/禁用) | 错误 | **Warnings**,允许保存,前端黄框提示 |
+
+### 8.4 新增接口
+
+| Method | URL | 用途 |
+|--------|-----|------|
+| GET  | `/api/AdoSmartOpsBusinessFact/page` | 分页查询(keyword/moduleCode 过滤) |
+| GET  | `/api/AdoSmartOpsBusinessFact/picker` | 精简列表(FormulaEditor `$` 候选用) |
+| GET  | `/api/AdoSmartOpsBusinessFact/{id}` | 详情 |
+| POST | `/api/AdoSmartOpsBusinessFact/add` | 新增 |
+| POST | `/api/AdoSmartOpsBusinessFact/update` | 更新 |
+| POST | `/api/AdoSmartOpsBusinessFact/delete` | 删除 |
+| POST | `/api/AdoSmartOpsKpiMaster/preview-formula` | 实时预览(不落库),返回 `{preview, metrics[], facts[], errors[], warnings[]}` |
+
+`AdoSmartOpsKpiMaster` 的 POST/PUT 在保存时同样走完整校验管线,触发 `FormulaParser → FormulaValidator → FormulaPreviewBuilder`,落库 `FormulaPreview` / `FormulaRefs` 两个衍生字段。
+
+### 8.5 前端
+
+- 新菜单「业务事实字典」:`/aidop/smart-ops/business-fact` → `businessFact.vue`
+- 新组件 `components/FormulaEditor.vue`:`@` 弹指标候选、`$` 弹事实候选(前缀模糊过滤,Tab/点击确认);500 ms 防抖调用 `/preview-formula`;errors 红框 / warnings 黄框分级显示
+- `kpiMaster.vue` 顶部增「结构化公式」字段;原「计算公式」降级为兼容只读型
+
+### 8.6 字典初始化
+
+从 230 条启用指标的 `Formula` 自动抽取高频短语,规范化为 `F_<MODULE>_<序号>` 编码,单位/聚合靠词尾启发式猜测,每租户入库 148 条(两租户合计 296 条)。脚本 `ai-dop-platform/tools/seed_business_facts.py`,幂等可重跑。
+
+### 8.7 验收
+
+| 场景 | 预期 | 实测 |
+|------|------|------|
+| 合法 `$F_S1_001 / $F_COMMON_001 * 100` | 预览 "规定时间内交期确认的订单数量 ÷ 订单总数 × 100" | ✅ |
+| 未知引用 `$F_BOGUS / @S99_L99_999` | errors 两条精准定位 | ✅ |
+| 非法中文 + `IsEnabled=false` | 降为 warnings | ✅ |
+| Picker `moduleCode=S1` | 返回 COMMON+S1 共 21 条 | ✅ |
+| Page 默认分页 | total = 148 | ✅ |
+
+---
+
+## 9. 遗留与后续待办
+
+### 9.1 Phase 8 — 公式驱动 ETL(生产租户)⭐ 主线
+
+> 本阶段是 Phase 7 的自然延续,将 `FormulaExpr` 从"文档"升级为"可执行";Demo 租户维持手工导入不变。
+
+- [ ] **FactCode → 数据源映射表**:为每条 `BusinessFact` 补 `DataSource` 字段(SQL 模板 / 中间层视图名 / REST 服务调用),并约定模板变量 `{start_date}` `{end_date}` `{tenant_id}`
+- [ ] **公式求值器**:扩展 `Infrastructure/FormulaExpr/` 增 `FormulaEvaluator.cs`,按 AST 递归求值;基础算符 + 简单聚合 + 除零 / 空值策略
+- [ ] **ETL 调度器**:新增 `AidopKpiEtlJob`(IJob,按日/按小时),读取启用指标的 `FormulaExpr` + `FormulaRefs`,拓扑排序依赖链(先事实→后 L3→L2→L1),结果写入对应 `kpi_value_l*_day` 表
+- [ ] **租户开关**:`ado_smart_ops_home_module` 或新增 `tenant_kpi_config` 表上增 `value_source ∈ {manual, etl}`,Demo 默认 manual,生产默认 etl
+- [ ] **依赖环检测**:保存 `FormulaExpr` 时做有向图环检测,`@A → @B → @A` 返回 Errors
+- [ ] **失败回看**:新增 `ado_smart_ops_kpi_etl_log` 记录每日每指标的求值 SQL + 结果 + 耗时 + 错误栈;看板底部提供"上次计算时间 / 错误"小标
+
+### 9.2 存量 Formula 渐进迁移
+
+- [ ] **批量半自动迁移脚本** `ai-dop-platform/tools/migrate_formula_to_expr.py`:扫描 230 条旧 `Formula`,把已入字典的短语自动替换为 `$FactCode`,剩余自由文本留原样让人工补;生成迁移清单 CSV
+- [ ] **迁移进度面板**:在 `kpiMaster.vue` 顶部加一个进度条「已结构化 X / 230」,帮助 PM 推进
+- [ ] **冻结保护**:全量迁移完成后可选关闭旧 `Formula` 字段编辑(只读),避免新老公式并存漂移
+
+### 9.3 业务事实字典增强
+
+- [ ] 批量导入 / 导出 Excel,便于业务方离线维护
+- [ ] **使用频次反查**:字典管理页每行显示"被 X 条公式引用",点开展示引用清单;删除前弹确认(有引用时禁止硬删)
+- [ ] **单位/聚合人工审核**:`seed_business_facts.py` 的词尾启发式不完全准确,需业务校对一轮;字典页提供"待审核"筛选
+- [ ] **同名合并工具**:148 条里存在相近短语(如"订单总数" vs "统计期内订单总数"),提供合并界面把冗余项指向同一 FactCode,原引用自动改写
+
+### 9.4 FormulaEditor 增强
+
+- [ ] **tag 化渲染**:从 `el-input textarea` 升级为 contenteditable,把 `@xxx` / `$xxx` 渲染成彩色标签,可点击删除;键盘方向键跨 tag
+- [ ] **引用悬浮提示**:鼠标停在 `@/$` token 上弹详情(名称、单位、当前值、所在模块)
+- [ ] **依赖链可视化**:在编辑器下方以树形展示当前公式的直接依赖与二级依赖(复用 Phase 8 的拓扑图)
+- [ ] **历史版本**:保存时把旧 `FormulaExpr` 推入审计表 `ado_smart_ops_kpi_master_formula_history`,编辑界面提供"回滚"
+
+### 9.5 框架补强
+
+- [ ] **AidopMenuLinkSync 建菜单**:当前仅补 `SysTenantMenu` / `SysRoleMenu` 链接,不会为种子中已有但 DB 缺失的菜单**创建 SysMenu 记录**;Phase 7 已通过 `tools/insert_business_fact_menu.py` 手工补,Phase 8 应让启动逻辑自动建,避免每加一个菜单都要跑脚本
+- [ ] **SysRoleMenu Id 冲突**:启动日志观察到 `AidopMenuLinkSync` 批量插入时出现 `Duplicate entry '1322000000208' for key 'SysRoleMenu.PRIMARY'`(历史遗留,与 `1322000000107`「计划联动看板」Id 公式撞号),应改为 `MAX(Id)+1` 或雪花 ID
+- [ ] **权限落地**:`AdoSmartOpsBusinessFactController` / `AdoSmartOpsKpiMasterController` 当前 `[AllowAnonymous]`,待项目整体鉴权方案就绪后按目录 / 按钮级权限收敛
+
+### 9.6 Phase 1–4 历史遗留(保留原列)
 
 | 项目 | 状态 | 说明 |
 |------|------|------|
-| `S4_ONTIME` 编码 | 待确认 | 存在于 `kpi_value_l1_day` 但无对应 KpiMaster 行,可能是历史残留 |
-| 其他模块(S1~S3, S5~S9)看板 | 未接入 | 目前不走 LayoutItem/KpiMaster 看板流程,仅 S4 完成了全链路 |
+| `S4_ONTIME` 编码 | 待确认 | 存在于 `kpi_value_l1_day` 但无对应 KpiMaster 行,疑似历史残留 |
 | MetricCatalog 旧表 | 可清理 | 实体和种子已删除,数据库表 `ado_smart_ops_metric_catalog` 可手动 DROP |
 | 建模页面非 S4 模块 | 本地草稿 | S1~S3、S5~S7 的图谱编辑保存在浏览器 localStorage,未接入服务端 |
+| S8 异常监控看板 | 维持原样 | Phase 6 有意不纳入统一看板;S8 交互与其他模块差异大,如需统一需专项 |

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.55</AssemblyVersion>
-    <FileVersion>1.0.55</FileVersion>
-    <Version>1.0.55</Version>
+    <AssemblyVersion>1.0.56</AssemblyVersion>
+    <FileVersion>1.0.56</FileVersion>
+    <Version>1.0.56</Version>
   </PropertyGroup>
 
   <ItemGroup>