feat: add A-card operations frontend and backend foundation

This commit is contained in:
郑龙捷
2026-09-02 09:54:03 +08:00
parent 9badd3597f
commit ad71259ba5
106 changed files with 12461 additions and 1796 deletions
@@ -0,0 +1,308 @@
# A 卡模型运维模块后端架构与数据库设计 V0.2
> 状态:设计评审稿;独立库 DDL 已生成但尚未执行,ORM、迁移和接口尚未实现
> 日期:2026-08-31
> 依据:当前 `develop` 工程、运维前端 V1.9、现有三条 API 契约及甲方提供的 `model_platform` 18 张表 DDL
## 1. 结论
采用“**同一工程、两个数据库、平台库只读、运维库自有**”的方式:
- 现有 `model_platform` 库保持不变,只读复用用户、角色、工作空间、对象存储、脚本版本和调度数据。
- 新建独立 `model_operations` 库;模型平台直接写入其中 7 张受控源表,不通过运维平台复制一份数据。
- 运维模块只读取状态为 `published` 的监控批次,未发布或失败批次对页面不可见。
- 判级、复核、报告、流程、文档、Prompt 和配置由运维模块独立维护,模型平台不得写入。
- 已发布监控数据不原地覆盖;勘误时新增修订批次,保留历史版本和审计链。
- 首期不新增微服务、Redis或消息中间件;复用现有 Nginx、React、FastAPI、对象存储和 Schedule Executor,新库自带独立 Outbox/Inbox。
这能满足“模型方直接写业务表”和“我方单独建库”的双重要求,同时避免取得现有平台库不必要的写权限。
## 2. 模块边界
### 2.1 模型平台负责
- 银行、模型实例、模型版本及生命周期数据的写入。
- 月度监控原始结果、特征指标和分箱分布数据的计算与写入。
- 写入批次的完整性校验、计数、校验和及发布。
- 仅获得 `model_operations` 7 张源表的受控写权限,以及发布事件所需的 Outbox 插入权限。
- 已发布数据发生错误时,以新修订批次更正,不修改旧记录。
### 2.2 运维模块负责
- 按已发布原始结果和已发布规则版本计算异常等级、监控结果等级和命中原因。
- 模型团队初审、业务团队终审、超时默认不处理及全程留痕。
- 监控/诊断报告生成、编辑、发送、汇总和历史修订。
- 七阶段开发评审流程、材料、知识库、规则、Prompt、报告模板和按银行配置。
- 工作台、模型大类、细分银行、监控明细和详情等查询 API。
### 2.3 明确不做
- 运维模块不重复建设账号、角色、权限表;只复用登录模块返回的用户、角色和工作空间。
- 运维模块不得读取 `model_platform.users.password_hash`,不得写现有平台任一业务表。
- 模型平台不写判级、处理、报告、流程或配置表。
- “手工同步”不再复制数据;其含义调整为“校验最新发布批次并重建派生判级”。
- 报告汇总、知识库和工作台不单独建冗余汇总表,首期由业务表查询生成。
## 3. 总体架构
现有服务拓扑保持不变。运维能力作为 FastAPI 后端内的新 bounded context 接入,但增加第二套数据库连接:
1. 浏览器继续经 Nginx 访问 React SPA 和同源 `/api/v1/*`
2. `DATABASE_URL` 继续连接 `model_platform`,只承担现有认证、工作空间、存储和调度能力。
3. 新增 `OPERATIONS_DATABASE_URL` 连接 `model_operations`,承载全部运维业务数据。
4. 模型平台模块或调度任务直接写新库的 `ops_*` 源表。
5. 批次发布后,运维服务计算并保存规则判级快照。
6. 运维 API 联查新库源数据、判级和处理结果;用户显示名等按需只读平台库。
7. 报告生成、提醒等动作写新库 `ops_outbox_events`;接入异步阶段时让 Schedule Executor 增加运维库轮询连接。
8. 报告文件、流程材料继续复用平台库 `storage_objects` 与现有对象存储服务,运维库只保存对象 ID。
配套图:
- `A卡模型运维模块-系统架构-V0.2.png/.svg`
- `A卡模型运维模块-数据域关系-V0.2.png/.svg`
修改 YAML 后运行 `python3 docs/architecture/render_diagrams.py` 可重新生成两种格式;脚本依赖 `diagrams``pyyaml` 和本机 Graphviz。
配套建库建表脚本位于 `docs/database/model_operations-V0.1/`,包含 24 张表、1 个视图、四类模型大类种子数据和静态安全校验脚本;原始平台 DDL 已只读归档到 `docs/database/reference/`
## 4. 数据设计约定
- 表名前缀统一为 `ops_`,与现有模型平台表隔离。
- 全部 `ops_*` 对象均位于独立 `model_operations` 库。
- 主键统一使用 ULID `CHAR(26)`;日期时间统一 `DATETIME(3)`
- 月份在库内用 `DATE` 且固定为当月 1 日,API 序列化为 `YYYY-MM`
- KS、PSI、IV、CSI、坏账率和降幅在数据库中统一存 **01 比率**,使用 `DECIMAL(12,8)`;API 转换为 0~100 的百分数。
- 沿用当前仓库约定:不创建物理外键,使用 `fk_*` 逻辑索引并在服务层校验完整性。
- 可修改业务状态表使用 `state_version INTEGER` 做乐观锁。
- 需要删除的配置或流程数据采用 `is_deleted``deleted_at` 软删除;监控历史不允许删除。
- 所有根业务表包含 `workspace_id`,避免未来多个项目空间数据串用。
- `workspace_id/user_id/storage_object_id/platform_versions_id` 是对 `model_platform` 的逻辑引用,不建立跨库物理外键。
## 5. 表清单与归属
### 5.1 模型平台可写源表(7 张)
| 表 | 用途 | 核心唯一性 |
|---|---|---|
| `ops_banks` | 银行主数据及无极银行标记 | `(workspace_id, bank_code)` |
| `ops_model_instances` | 银行下的模型实例、通用模型关系、当前版本 | `(workspace_id, model_id)` |
| `ops_model_versions` | 版本、开发指标和生命周期日期 | `(model_instance_id, version_label)` |
| `ops_monitor_batches` | 月度写入批次、修订号、发布状态和校验和 | `(workspace_id, source_system, monitor_month, revision_no)` |
| `ops_monitor_results` | 模型单月原始排序性、KS、PSI和样本量 | `(batch_id, model_instance_id)` |
| `ops_monitor_feature_metrics` | 单月特征级 IV、CSI及贡献变化 | `(monitor_result_id, feature_code)` |
| `ops_monitor_distributions` | 评分分箱或特征分箱的基准期/当期分布 | `(monitor_result_id, dimension_type, feature_code, bin_order)` |
`ops_model_categories` 为运维模块维护的固定字典,模型平台只有读取权限。首期种子为 `std / bai / big / afd` 四类。
### 5.2 运维模块独占业务表(15 张)
| 数据域 | 表 |
|---|---|
| 字典 | `ops_model_categories` |
| 判级与处理 | `ops_rule_versions``ops_rule_items``ops_monitor_evaluations``ops_monitor_reviews` |
| 报告治理 | `ops_reports``ops_report_revisions``ops_report_template_versions``ops_prompt_versions``ops_prompt_regression_runs``ops_bank_report_configs` |
| 流程与材料 | `ops_workflows``ops_workflow_stages``ops_documents` |
| 使用统计 | `ops_usage_events` |
不新增通知任务表;通知和后台报告任务复用新库 `ops_outbox_events`。若后续需要展示逐次送达结果,再增通知投递日志表。
### 5.3 运维库技术表(2 张)
- `ops_outbox_events`:与运维业务写入同事务提交的后台事件。
- `ops_consumer_inbox`:异步消费者幂等去重。
新库合计 **24 张表 + 1 个当前监控结果视图**。不直接写 `model_platform.outbox_events`,防止运维事件污染现有模型调度事件流。
## 6. 核心表字段初稿
### 6.1 模型与版本
`ops_model_instances`
- `model_instance_id``workspace_id``bank_id``category_id`
- `model_id``model_name``model_status`
- `current_version_id`
- `is_common_model``common_source_model_id``common_model_name`
- `source_updated_at``created_at``updated_at`
`ops_model_versions`
- `model_version_id``model_instance_id``version_label``version_status`
- `developer_user_ref``developer_display_name`
- `development_date``iteration_start_date``last_iteration_date``iteration_reason`
- `escort_start_date``escort_end_date``online_date``offline_date`
- `development_ks``development_psi``max_lift`
- `scoring_logic_storage_object_id``source_updated_at`
模型实例只保存当前版本指针,所有生命周期和开发指标按版本留存。
### 6.2 监控批次和原始结果
`ops_monitor_batches`
- `batch_id``workspace_id``source_system``source_batch_no`
- `monitor_month``revision_no`
- `batch_status``writing / published / failed / superseded`
- `expected_model_count``written_model_count``feature_row_count``distribution_row_count`
- `checksum_sha256``generated_at``published_at``failed_reason`
`ops_monitor_results`
- `monitor_result_id``batch_id``model_instance_id``model_version_id``monitor_month`
- `ranking_result``matched / unmatched / not_applicable`
- `ks_value``psi_value`
- `sample_count``good_count``bad_count`
- `source_result_json`:仅保存尚未结构化且确有追溯价值的源字段
- `calculated_at``created_at`
源表不保存 A/B/C、异常等级、处理建议或报告状态,避免两个模块同时改一行。
### 6.3 判级快照
`ops_monitor_evaluations`
- `evaluation_id``monitor_result_id``rule_version_id``rule_item_id`
- `ks_mom_drop_rate``secondary_level2_hits_6m`
- `abnormal_level``normal / level1 / level2 / level3`
- `monitor_grade``A / B / C`
- `reason_code``reason_text_snapshot``action_snapshot`
- `is_current``evaluated_at`
每次规则发布或源数据修订都新增判级记录;旧快照不覆盖。页面默认读取 `is_current=1` 的记录,历史报告仍绑定生成时的判级快照。
### 6.4 两段处理
`ops_monitor_reviews`
- `review_id``monitor_result_id``evaluation_id`
- `review_stage``model_initial / business_final`
- `review_status``pending / handled / auto_closed`
- `decision``no_action / tune_or_rebuild`
- `handling_note`(手工处理必填)
- `due_at``handled_by``handled_at``auto_closed_at`
- `state_version``created_at``updated_at`
唯一约束为 `(monitor_result_id, review_stage)`。模型团队阶段完成后才创建或激活业务团队阶段;超时任务通过 Outbox 执行默认“不处理”。
### 6.5 报告
`ops_reports` 保存报告主状态与绑定快照,`ops_report_revisions` 保存每次正文修改:
- 报告绑定 `monitor_result_id + evaluation_id + template_version_id + prompt_version_id`
- `ops_report_revisions` 使用 `(report_id, revision_no)` 唯一约束,正文和结构化快照均不可变。
- 监控报告页默认只查询最新监控月份;报告汇总页查询全部历史。数据库不删除旧月份报告。
- PDF/Excel 等文件通过 `storage_object_id` 指向现有 `storage_objects`
### 6.6 流程、材料与知识库
- `ops_workflows`:银行、模型大类、模型实例、独立开发/复用通用模型、当前阶段、状态和计划日期。
- `ops_workflow_stages`:1~7 阶段实例、负责人角色、开始/截止/完成时间、确认状态和停滞天数。
- `ops_documents`:同时支持 `storage_object_id``external_url`,可关联流程、阶段、模型和版本。
- 知识库不是新表,而是对 `ops_documents` 按银行、模型、版本、环节和通用模型标记查询。
## 7. 发布协议
模型平台每次写入必须执行以下协议:
1. 创建 `ops_monitor_batches`,状态为 `writing`,携带幂等的 `source_batch_no`
2. 幂等写入银行、模型实例和版本;插入本批次结果、特征指标和分布。
3. 校验模型数、明细行数、必填字段、指标范围和 SHA-256 校验和。
4. 在一个短事务内将批次改为 `published`;同月份上一修订改为 `superseded`
5. 写入 `monitor_batch.published` Outbox 事件,触发运维模块生成判级快照。
6. 运维查询只允许关联 `published` 批次,并按最高修订号取当前结果。
失败批次保留为 `failed` 供排查。任何已发布结果、特征和分布均不得执行 `UPDATE` 或物理删除。
## 8. 数据库权限
建议生产环境至少分四类账号:
| 账号 | 权限 |
|---|---|
| `schema_migrator` | 仅部署时使用,可对 `model_operations` 执行 DDL;不改 `model_platform` |
| `platform_reader` | 对平台库指定字段只读;禁止读取密码散列和权限内部表 |
| `model_writer` | 读取字典;对新库 7 张源表执行限定 `SELECT/INSERT/UPDATE`,并插入发布事件;无 `DELETE`,不能写运维独占表 |
| `operations_app` | 读写运维库;只读平台库必要表;服务层禁止修改已发布源结果 |
若模型任务与运维 API 暂时运行在同一 FastAPI 进程,代码层仍按平台库/运维库两个 session factory 和 repository 隔离;生产批处理脚本优先使用独立 `MODEL_WRITER_DATABASE_URL`,将权限边界落实到数据库。
## 9. API 设计
### 9.1 首条真实纵向链路
保持前端已写好的路径,不重新发明契约:
| 接口 | 主要数据来源 |
|---|---|
| `GET /api/v1/operations/models` | 模型实例 + 当前版本 + 最新已发布结果 + 当前判级 + 最近处理 |
| `GET /api/v1/operations/models/{model_id}` | 单模型完整元数据和最新状态 |
| `GET /api/v1/operations/models/{model_id}/monitor-results?month=YYYY-MM` | 指定月最新已发布修订 + 当前判级 |
所有接口返回现有 `{ data, meta? }` 包装。切换 `VITE_OPERATIONS_API_MODE=api` 前,前端 `operationsApi.ts` 需要统一附加当前 `workspace_id`,用于选择并校验数据空间。
### 9.2 后续资源接口
- 查询:`/model-categories/overview``/banks/overview``/monitor-results``/monitor-results/{id}/features``/monitor-results/{id}/distributions`
- 处理:`/monitor-results/{id}/reviews/model-initial``/reviews/business-final`
- 报告:`/reports``/reports/{id}``/reports/{id}/revisions``/reports/{id}/send`
- 流程:`/workflows``/workflows/{id}``/workflows/{id}/stages/{stage}``/documents`
- 治理:`/rule-versions``/prompt-versions``/report-template-versions``/bank-report-configs`
列表统一支持分页、排序和与 V1.9 筛选框一致的查询参数;Excel/PDF 导出走独立端点,不让普通列表接口返回超大数据集。
## 10. 登录与角色
- 不增加运维 RBAC 表,也不接管现有权限管理模块。
- API 复用登录 Cookie、`Users``Roles` 和当前工作空间,不创建新的角色或权限数据。
- 新增轻量 `operations_context`:校验用户已登录且可访问所选工作空间;业务角色取与 `/api/v1/auth/me`、前端 `AuthUser.role_code` 相同的登录角色来源,避免前后端角色口径不一致。
- 角色映射约定:`admin` → 管理员;`developer/model_team` → 模型团队;`business/business_team/biz` → 业务团队。
- 前端按角色决定页面呈现,后端仍必须按角色校验写操作,不能把前端隐藏按钮当安全边界。
- 角色代码最终值由登录/权限模块给出,运维模块只维护一处映射常量。
## 11. 代码落位建议
沿用现有 router / schema / service 分层,每个文件不超过 500 行:
```text
common/src/common/db/models/operations/
reference.py models.py monitoring.py governance.py
reviews.py reports.py workflows.py usage.py
backend/src/backend/api/operations/
__init__.py _deps.py models.py monitoring.py reviews.py
reports.py workflows.py governance.py
backend/src/backend/services/operations/
queries.py evaluation.py publishing.py reports.py workflows.py
backend/src/backend/schemas/operations/
common.py models.py monitoring.py reports.py workflows.py governance.py
```
运维 ORM 使用独立 Alembic 版本线和 `OPERATIONS_DATABASE_URL`,不能让现有 `model_platform` 基线迁移误扫新表。`backend.main` 创建第二个 engine/session factory 并只注册一个 `operations_router`;业务规则不写进路由文件。
## 12. 实施顺序
### P0:首条真实链路
1. DBA 审核并创建 `model_operations`;建模型/版本/批次/结果/特征/分布及规则判级 11 张核心表。
2. 导入一组 2026-07 样例数据并走完整 `writing → published` 协议。
3. 实现三条既有 API,前端追加 `workspace_id` 后切到 API 模式。
4. 验证列表、模型详情、单月监控详情数值一致。
### P1:业务闭环
实现两段复核、报告及修订、七阶段流程、材料和知识库。
### P2:治理与运营
实现规则版本、Prompt 回归、模板版本、使用统计、提醒和导出优化。
## 13. 开发前需共同确认的 5 项
以下均给出推荐默认值;未得到反对意见时可按推荐值实施:
1. **模型 ID 唯一性**:推荐 `model_id` 在工作空间内全局唯一,不只在银行内唯一。
2. **指标单位**:推荐数据库存 0~1 比率,API 输出百分数;禁止两种单位混写。
3. **批次粒度**:推荐一个来源系统每月一个全量批次,勘误整体升修订号。
4. **工作空间范围**:推荐全部根表带 `workspace_id` 并逻辑引用 `model_platform.workspaces`,即使首期只有一个 A 卡项目空间。
5. **模型开发材料**:推荐运维表同时支持对象存储文件和模型平台链接,避免重复上传。
这 5 项确认后即可开始 ORM、Alembic 迁移和首条真实接口实现。
Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

@@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: A 卡模型运维模块 · 双库数据域关系 V0.2
平台标识只读引用,运维业务全部落入独立库 Pages: 1 -->
<svg width="2450pt" height="2340pt"
viewBox="0.00 0.00 2450.00 2340.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1.34354 1.34354) rotate(0) translate(36 1705.66)">
<title>A 卡模型运维模块 · 双库数据域关系 V0.2
平台标识只读引用,运维业务全部落入独立库</title>
<polygon fill="white" stroke="none" points="-36,36 -36,-1705.66 1787.5,-1705.66 1787.5,36 -36,36"/>
<text xml:space="preserve" text-anchor="middle" x="875.75" y="-1642.86" font-family="PingFang SC" font-size="24.00" fill="#2d3436">A 卡模型运维模块 · 双库数据域关系 V0.2</text>
<text xml:space="preserve" text-anchor="middle" x="875.75" y="-1609.11" font-family="PingFang SC" font-size="24.00" fill="#2d3436">平台标识只读引用,运维业务全部落入独立库</text>
<g id="clust1" class="cluster">
<title>cluster_model_platform · 现有库(只读依赖)</title>
<path fill="#f5f6f8" stroke="#aeb7c4" stroke-dasharray="5,2" d="M20,-268.16C20,-268.16 430.85,-268.16 430.85,-268.16 436.85,-268.16 442.85,-274.16 442.85,-280.16 442.85,-280.16 442.85,-892.16 442.85,-892.16 442.85,-898.16 436.85,-904.16 430.85,-904.16 430.85,-904.16 20,-904.16 20,-904.16 14,-904.16 8,-898.16 8,-892.16 8,-892.16 8,-280.16 8,-280.16 8,-274.16 14,-268.16 20,-268.16"/>
<text xml:space="preserve" text-anchor="middle" x="146.88" y="-885.91" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_platform · 现有库(只读依赖)</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_model_operations · 模型方受控直写(7 张)</title>
<path fill="#eef5fd" stroke="#6f9ed4" d="M20,-912.16C20,-912.16 1238.9,-912.16 1238.9,-912.16 1244.9,-912.16 1250.9,-918.16 1250.9,-924.16 1250.9,-924.16 1250.9,-1334.16 1250.9,-1334.16 1250.9,-1340.16 1244.9,-1346.16 1238.9,-1346.16 1238.9,-1346.16 20,-1346.16 20,-1346.16 14,-1346.16 8,-1340.16 8,-1334.16 8,-1334.16 8,-924.16 8,-924.16 8,-918.16 14,-912.16 20,-912.16"/>
<text xml:space="preserve" text-anchor="middle" x="168.62" y="-1327.91" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_operations · 模型方受控直写(7 张)</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_model_operations · 运维判级与处理</title>
<path fill="#f1f8f5" stroke="#74a087" d="M20,-1354.16C20,-1354.16 1482.95,-1354.16 1482.95,-1354.16 1488.95,-1354.16 1494.95,-1360.16 1494.95,-1366.16 1494.95,-1366.16 1494.95,-1574.16 1494.95,-1574.16 1494.95,-1580.16 1488.95,-1586.16 1482.95,-1586.16 1482.95,-1586.16 20,-1586.16 20,-1586.16 14,-1586.16 8,-1580.16 8,-1574.16 8,-1574.16 8,-1366.16 8,-1366.16 8,-1360.16 14,-1354.16 20,-1354.16"/>
<text xml:space="preserve" text-anchor="middle" x="139.38" y="-1567.91" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_operations · 运维判级与处理</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_model_operations · 报告、流程与运营</title>
<path fill="#faf6ee" stroke="#c5a467" d="M20,-28.16C20,-28.16 1731.5,-28.16 1731.5,-28.16 1737.5,-28.16 1743.5,-34.16 1743.5,-40.16 1743.5,-40.16 1743.5,-248.16 1743.5,-248.16 1743.5,-254.16 1737.5,-260.16 1731.5,-260.16 1731.5,-260.16 20,-260.16 20,-260.16 14,-260.16 8,-254.16 8,-248.16 8,-248.16 8,-40.16 8,-40.16 8,-34.16 14,-28.16 20,-28.16"/>
<text xml:space="preserve" text-anchor="middle" x="146.88" y="-241.91" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_operations · 报告、流程与运营</text>
</g>
<!-- 5e390f22af134f76b948b6179f886986 -->
<g id="node1" class="node">
<title>5e390f22af134f76b948b6179f886986</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="324.05" y="-419.565"/>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-306.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">users + roles</text>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-291.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">用户与登录角色</text>
</g>
<!-- fa2425ff55b64fb68447fbe9df6124c2 -->
<g id="node15" class="node">
<title>fa2425ff55b64fb68447fbe9df6124c2</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1376.15" y="-1505.56"/>
<text xml:space="preserve" text-anchor="middle" x="1426.55" y="-1392.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_reviews</text>
<text xml:space="preserve" text-anchor="middle" x="1426.55" y="-1377.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型初审 → 业务终审</text>
</g>
<!-- 5e390f22af134f76b948b6179f886986&#45;&gt;fa2425ff55b64fb68447fbe9df6124c2 -->
<g id="edge1" class="edge">
<title>5e390f22af134f76b948b6179f886986&#45;&gt;fa2425ff55b64fb68447fbe9df6124c2</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M424.7,-437.64C433.79,-446.45 443.91,-454.39 454.85,-460.16 532.24,-500.97 562.86,-483.16 650.35,-483.16 650.35,-483.16 650.35,-483.16 1183.5,-483.16 1363.35,-483.16 1410.59,-1117.71 1422.1,-1361.41"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="1418.59,-1361.37 1422.55,-1371.2 1425.58,-1361.05 1418.59,-1361.37"/>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-489.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">处理人 ID / 显示名</text>
</g>
<!-- f7ad9eee12d4406182ca79edef7e10cc -->
<g id="node18" class="node">
<title>f7ad9eee12d4406182ca79edef7e10cc</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="600.95" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_workflows + stages</text>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">七阶段流程</text>
</g>
<!-- 5e390f22af134f76b948b6179f886986&#45;&gt;f7ad9eee12d4406182ca79edef7e10cc -->
<g id="edge2" class="edge">
<title>5e390f22af134f76b948b6179f886986&#45;&gt;f7ad9eee12d4406182ca79edef7e10cc</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M424.6,-326.26C471.75,-285.09 542.95,-222.93 592.75,-179.45"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="594.79,-182.31 600.03,-173.1 590.19,-177.04 594.79,-182.31"/>
<text xml:space="preserve" text-anchor="middle" x="511.48" y="-304.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">发起人 / 经办人</text>
</g>
<!-- 2f8f6abee8874b8083218fafe3dfceb4 -->
<g id="node2" class="node">
<title>2f8f6abee8874b8083218fafe3dfceb4</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-823.565"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-710.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">workspaces + members</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-695.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">项目空间与成员</text>
</g>
<!-- 9f7470d84e0f495d9e32d9e83ee777ae -->
<g id="node6" class="node">
<title>9f7470d84e0f495d9e32d9e83ee777ae</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="324.05" y="-1063.56"/>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-950.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_instances</text>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-935.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型实例</text>
</g>
<!-- 2f8f6abee8874b8083218fafe3dfceb4&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae -->
<g id="edge3" class="edge">
<title>2f8f6abee8874b8083218fafe3dfceb4&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M126.62,-813.07C178.48,-855.11 260.13,-921.3 315.18,-965.92"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="312.97,-968.64 322.94,-972.22 317.37,-963.2 312.97,-968.64"/>
<text xml:space="preserve" text-anchor="middle" x="225.43" y="-948.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">workspace_id 逻辑引用</text>
</g>
<!-- b5bf8bfd876b4a8298364b8832b1a1f8 -->
<g id="node3" class="node">
<title>b5bf8bfd876b4a8298364b8832b1a1f8</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="324.05" y="-621.565"/>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-508.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">storage_objects + versions</text>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-493.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">文件与开发制品</text>
</g>
<!-- d63ec4b0c1f54f28a1b6e9e25ced7720 -->
<g id="node7" class="node">
<title>d63ec4b0c1f54f28a1b6e9e25ced7720</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="600.95" y="-1063.56"/>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-950.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_versions</text>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-935.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">业务模型版本</text>
</g>
<!-- b5bf8bfd876b4a8298364b8832b1a1f8&#45;&gt;d63ec4b0c1f54f28a1b6e9e25ced7720 -->
<g id="edge4" class="edge">
<title>b5bf8bfd876b4a8298364b8832b1a1f8&#45;&gt;d63ec4b0c1f54f28a1b6e9e25ced7720</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M424.72,-640.7C464.86,-698.33 522.21,-782.89 568.1,-859.41 579.81,-878.94 591.73,-900.26 602.74,-920.63"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="599.53,-922.06 607.35,-929.21 605.7,-918.75 599.53,-922.06"/>
<text xml:space="preserve" text-anchor="middle" x="511.48" y="-865.81" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">可选开发制品引用</text>
</g>
<!-- 52370f80438a4639b04449518b48b710 -->
<g id="node19" class="node">
<title>52370f80438a4639b04449518b48b710</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="866.825" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_documents</text>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">材料 / 知识库索引</text>
</g>
<!-- b5bf8bfd876b4a8298364b8832b1a1f8&#45;&gt;52370f80438a4639b04449518b48b710 -->
<g id="edge5" class="edge">
<title>b5bf8bfd876b4a8298364b8832b1a1f8&#45;&gt;52370f80438a4639b04449518b48b710</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M424.56,-518.1C431.94,-507.53 438.52,-496.01 442.85,-484.16 464.69,-424.42 432.9,-257.87 454.85,-198.16 490.56,-101.06 505.12,-60.04 598.1,-14.66 685.51,27.99 793.05,-32.55 858.04,-80.56"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="855.61,-83.12 865.71,-86.33 859.82,-77.52 855.61,-83.12"/>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-15.81" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">storage_object_id</text>
</g>
<!-- 0eef55ad5f8e482d84f70346e8a07c51 -->
<g id="node4" class="node">
<title>0eef55ad5f8e482d84f70346e8a07c51</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="324.05" y="-823.565"/>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-710.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">schedules + runs</text>
<text xml:space="preserve" text-anchor="middle" x="374.45" y="-695.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型计算任务</text>
</g>
<!-- e39a175c784d4021862ca0594e08476b -->
<g id="node8" class="node">
<title>e39a175c784d4021862ca0594e08476b</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="600.95" y="-1265.56"/>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-1152.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_batches</text>
<text xml:space="preserve" text-anchor="middle" x="651.35" y="-1137.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">发布批次 / 修订</text>
</g>
<!-- 0eef55ad5f8e482d84f70346e8a07c51&#45;&gt;e39a175c784d4021862ca0594e08476b -->
<g id="edge6" class="edge">
<title>0eef55ad5f8e482d84f70346e8a07c51&#45;&gt;e39a175c784d4021862ca0594e08476b</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M424.59,-813.93C468.8,-853.29 532.16,-916.88 568.1,-985.41 595.39,-1037.45 577.78,-1059.04 598.1,-1114.16 599.04,-1116.7 600.02,-1119.26 601.05,-1121.83"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="597.8,-1123.14 604.9,-1131.01 604.26,-1120.43 597.8,-1123.14"/>
<text xml:space="preserve" text-anchor="middle" x="511.48" y="-991.81" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">计算任务产出</text>
</g>
<!-- cd5ab313d36f43718a23c57ae60110d3 -->
<g id="node5" class="node">
<title>cd5ab313d36f43718a23c57ae60110d3</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-1063.56"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-950.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_banks</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-935.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">银行</text>
</g>
<!-- cd5ab313d36f43718a23c57ae60110d3&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae -->
<g id="edge7" class="edge">
<title>cd5ab313d36f43718a23c57ae60110d3&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae</title>
<path fill="none" stroke="#9aa2ad" d="M126.62,-1013.16C177.73,-1013.16 257.78,-1013.16 312.78,-1013.16"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="312.6,-1016.66 322.6,-1013.16 312.6,-1009.66 312.6,-1016.66"/>
<text xml:space="preserve" text-anchor="middle" x="225.43" y="-1014.31" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 9f7470d84e0f495d9e32d9e83ee777ae&#45;&gt;d63ec4b0c1f54f28a1b6e9e25ced7720 -->
<g id="edge9" class="edge">
<title>9f7470d84e0f495d9e32d9e83ee777ae&#45;&gt;d63ec4b0c1f54f28a1b6e9e25ced7720</title>
<path fill="none" stroke="#9aa2ad" d="M424.6,-1013.16C470.87,-1013.16 540.31,-1013.16 589.95,-1013.16"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="589.65,-1016.66 599.65,-1013.16 589.65,-1009.66 589.65,-1016.66"/>
<text xml:space="preserve" text-anchor="middle" x="511.48" y="-1014.31" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 9f7470d84e0f495d9e32d9e83ee777ae&#45;&gt;f7ad9eee12d4406182ca79edef7e10cc -->
<g id="edge19" class="edge">
<title>9f7470d84e0f495d9e32d9e83ee777ae&#45;&gt;f7ad9eee12d4406182ca79edef7e10cc</title>
<path fill="none" stroke="#2f6fe0" d="M424.48,-994.4C469.91,-974.24 535.27,-937.58 568.1,-883.16 633.84,-774.22 647.2,-400.07 649.8,-223.21"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="653.29,-223.41 649.93,-213.36 646.29,-223.31 653.29,-223.41"/>
<text xml:space="preserve" text-anchor="middle" x="511.48" y="-985.1" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">开发 / 复用通用模型</text>
</g>
<!-- c270303679e04e67b03d65b8a341a0a3 -->
<g id="node9" class="node">
<title>c270303679e04e67b03d65b8a341a0a3</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="866.825" y="-1265.56"/>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-1152.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_results</text>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-1137.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">单月原始指标</text>
</g>
<!-- d63ec4b0c1f54f28a1b6e9e25ced7720&#45;&gt;c270303679e04e67b03d65b8a341a0a3 -->
<g id="edge11" class="edge">
<title>d63ec4b0c1f54f28a1b6e9e25ced7720&#45;&gt;c270303679e04e67b03d65b8a341a0a3</title>
<path fill="none" stroke="#9aa2ad" d="M701.67,-1050.92C746,-1084.86 811.16,-1134.73 858.03,-1170.61"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="855.87,-1173.37 865.94,-1176.67 860.13,-1167.81 855.87,-1173.37"/>
<text xml:space="preserve" text-anchor="middle" x="784.48" y="-1156.23" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">版本快照</text>
</g>
<!-- e39a175c784d4021862ca0594e08476b&#45;&gt;c270303679e04e67b03d65b8a341a0a3 -->
<g id="edge10" class="edge">
<title>e39a175c784d4021862ca0594e08476b&#45;&gt;c270303679e04e67b03d65b8a341a0a3</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2" d="M701.67,-1215.16C744.8,-1215.16 807.64,-1215.16 854.18,-1215.16"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2" points="854.12,-1218.66 864.12,-1215.16 854.12,-1211.66 854.12,-1218.66"/>
<text xml:space="preserve" text-anchor="middle" x="784.48" y="-1221.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">published 后可见</text>
</g>
<!-- a96305dd888e46aba9b6b0ff43b5317f -->
<g id="node10" class="node">
<title>a96305dd888e46aba9b6b0ff43b5317f</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1132.1" y="-1063.56"/>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-950.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_feature_metrics</text>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-935.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">IV / CSI / 贡献</text>
</g>
<!-- c270303679e04e67b03d65b8a341a0a3&#45;&gt;a96305dd888e46aba9b6b0ff43b5317f -->
<g id="edge12" class="edge">
<title>c270303679e04e67b03d65b8a341a0a3&#45;&gt;a96305dd888e46aba9b6b0ff43b5317f</title>
<path fill="none" stroke="#9aa2ad" d="M967.44,-1177.41C1011.67,-1143.47 1076.67,-1093.59 1123.43,-1057.72"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="1125.53,-1060.52 1131.33,-1051.66 1121.27,-1054.97 1125.53,-1060.52"/>
<text xml:space="preserve" text-anchor="middle" x="1051.1" y="-1150.98" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 164a58bf45ff4224aa7f5e8ec2378bf7 -->
<g id="node11" class="node">
<title>164a58bf45ff4224aa7f5e8ec2378bf7</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1132.1" y="-1265.56"/>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-1152.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_distributions</text>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-1137.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">评分 / 特征分箱</text>
</g>
<!-- c270303679e04e67b03d65b8a341a0a3&#45;&gt;164a58bf45ff4224aa7f5e8ec2378bf7 -->
<g id="edge13" class="edge">
<title>c270303679e04e67b03d65b8a341a0a3&#45;&gt;164a58bf45ff4224aa7f5e8ec2378bf7</title>
<path fill="none" stroke="#9aa2ad" d="M967.44,-1215.16C1010.93,-1215.16 1074.51,-1215.16 1121.08,-1215.16"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="1121.02,-1218.66 1131.02,-1215.16 1121.02,-1211.66 1121.02,-1218.66"/>
<text xml:space="preserve" text-anchor="middle" x="1051.1" y="-1216.31" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 5aef5a7b826344848389149b9879e85b -->
<g id="node14" class="node">
<title>5aef5a7b826344848389149b9879e85b</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1132.1" y="-1505.56"/>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-1392.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_evaluations</text>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-1377.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">判级快照</text>
</g>
<!-- c270303679e04e67b03d65b8a341a0a3&#45;&gt;5aef5a7b826344848389149b9879e85b -->
<g id="edge15" class="edge">
<title>c270303679e04e67b03d65b8a341a0a3&#45;&gt;5aef5a7b826344848389149b9879e85b</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2" d="M967.41,-1261.76C978.14,-1271.84 989.47,-1282.39 1000.1,-1292.16 1040.87,-1329.65 1087.34,-1371.38 1123.04,-1403.24"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2" points="1120.29,-1405.48 1130.08,-1409.52 1124.95,-1400.25 1120.29,-1405.48"/>
<text xml:space="preserve" text-anchor="middle" x="1051.1" y="-1387.63" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">原始指标 → 判级</text>
</g>
<!-- 12ba8a67ad2f4749b9fb5da5367a4c50 -->
<g id="node12" class="node">
<title>12ba8a67ad2f4749b9fb5da5367a4c50</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-1505.56"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-1392.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_categories</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-1377.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">固定大类字典</text>
</g>
<!-- 12ba8a67ad2f4749b9fb5da5367a4c50&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae -->
<g id="edge8" class="edge">
<title>12ba8a67ad2f4749b9fb5da5367a4c50&#45;&gt;9f7470d84e0f495d9e32d9e83ee777ae</title>
<path fill="none" stroke="#9aa2ad" d="M126.62,-1381.68C179.34,-1302.97 262.83,-1178.32 317.88,-1096.13"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="320.61,-1098.34 323.27,-1088.08 314.8,-1094.44 320.61,-1098.34"/>
<text xml:space="preserve" text-anchor="middle" x="225.43" y="-1325.71" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 08cea7b88ab94490a5a850f76ad9efd8 -->
<g id="node13" class="node">
<title>08cea7b88ab94490a5a850f76ad9efd8</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="866.825" y="-1505.56"/>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-1392.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_rule_versions + items</text>
<text xml:space="preserve" text-anchor="middle" x="917.23" y="-1377.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">规则版本与矩阵</text>
</g>
<!-- 08cea7b88ab94490a5a850f76ad9efd8&#45;&gt;5aef5a7b826344848389149b9879e85b -->
<g id="edge14" class="edge">
<title>08cea7b88ab94490a5a850f76ad9efd8&#45;&gt;5aef5a7b826344848389149b9879e85b</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M967.44,-1455.16C1010.93,-1455.16 1074.51,-1455.16 1121.08,-1455.16"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="1121.02,-1458.66 1131.02,-1455.16 1121.02,-1451.66 1121.02,-1458.66"/>
<text xml:space="preserve" text-anchor="middle" x="1051.1" y="-1461.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">按已发布规则计算</text>
</g>
<!-- 5aef5a7b826344848389149b9879e85b&#45;&gt;fa2425ff55b64fb68447fbe9df6124c2 -->
<g id="edge16" class="edge">
<title>5aef5a7b826344848389149b9879e85b&#45;&gt;fa2425ff55b64fb68447fbe9df6124c2</title>
<path fill="none" stroke="#2f6fe0" d="M1232.77,-1455.16C1270.98,-1455.16 1324.1,-1455.16 1364.91,-1455.16"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="1364.83,-1458.66 1374.83,-1455.16 1364.83,-1451.66 1364.83,-1458.66"/>
<text xml:space="preserve" text-anchor="middle" x="1304.53" y="-1461.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">触发两段处理</text>
</g>
<!-- d67d14f80f4b4ce39377cb6b59ae2311 -->
<g id="node16" class="node">
<title>d67d14f80f4b4ce39377cb6b59ae2311</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1376.15" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="1426.55" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_reports + revisions</text>
<text xml:space="preserve" text-anchor="middle" x="1426.55" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">报告与不可变修订</text>
</g>
<!-- 5aef5a7b826344848389149b9879e85b&#45;&gt;d67d14f80f4b4ce39377cb6b59ae2311 -->
<g id="edge17" class="edge">
<title>5aef5a7b826344848389149b9879e85b&#45;&gt;d67d14f80f4b4ce39377cb6b59ae2311</title>
<path fill="none" stroke="#2f6fe0" d="M1232.76,-1391.07C1239.78,-1379.59 1246.2,-1367.38 1250.9,-1355.16 1404.98,-954.7 1424.96,-433.5 1426.29,-223.22"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="1429.79,-223.46 1426.34,-213.44 1422.79,-223.42 1429.79,-223.46"/>
<text xml:space="preserve" text-anchor="middle" x="1304.53" y="-1299.03" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">绑定判级快照</text>
</g>
<!-- c395893276dd4282beb453109dd0f4c7 -->
<g id="node21" class="node">
<title>c395893276dd4282beb453109dd0f4c7</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1624.7" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="1675.1" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_outbox + inbox</text>
<text xml:space="preserve" text-anchor="middle" x="1675.1" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">独立异步事件流</text>
</g>
<!-- d67d14f80f4b4ce39377cb6b59ae2311&#45;&gt;c395893276dd4282beb453109dd0f4c7 -->
<g id="edge21" class="edge">
<title>d67d14f80f4b4ce39377cb6b59ae2311&#45;&gt;c395893276dd4282beb453109dd0f4c7</title>
<path fill="none" stroke="#e0902f" stroke-dasharray="5,2" d="M1476.85,-129.16C1516.16,-129.16 1571.38,-129.16 1613.41,-129.16"/>
<polygon fill="#e0902f" stroke="#e0902f" points="1613.37,-132.66 1623.37,-129.16 1613.37,-125.66 1613.37,-132.66"/>
<text xml:space="preserve" text-anchor="middle" x="1550.83" y="-135.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">生成 / 提醒事件</text>
</g>
<!-- 5ff3174966bd40cc958a6b27dd619911 -->
<g id="node17" class="node">
<title>5ff3174966bd40cc958a6b27dd619911</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1132.1" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模板 / Prompt / 回归</text>
<text xml:space="preserve" text-anchor="middle" x="1182.5" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">按银行报告配置</text>
</g>
<!-- 5ff3174966bd40cc958a6b27dd619911&#45;&gt;d67d14f80f4b4ce39377cb6b59ae2311 -->
<g id="edge18" class="edge">
<title>5ff3174966bd40cc958a6b27dd619911&#45;&gt;d67d14f80f4b4ce39377cb6b59ae2311</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M1232.77,-129.16C1270.98,-129.16 1324.1,-129.16 1364.91,-129.16"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="1364.83,-132.66 1374.83,-129.16 1364.83,-125.66 1364.83,-132.66"/>
<text xml:space="preserve" text-anchor="middle" x="1304.53" y="-135.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">模板 + Prompt</text>
</g>
<!-- f7ad9eee12d4406182ca79edef7e10cc&#45;&gt;52370f80438a4639b04449518b48b710 -->
<g id="edge20" class="edge">
<title>f7ad9eee12d4406182ca79edef7e10cc&#45;&gt;52370f80438a4639b04449518b48b710</title>
<path fill="none" stroke="#3f9ad0" d="M701.67,-129.16C745.27,-129.16 808.99,-129.16 855.67,-129.16"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="855.63,-132.66 865.63,-129.16 855.63,-125.66 855.63,-132.66"/>
<text xml:space="preserve" text-anchor="middle" x="784.48" y="-135.56" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">阶段材料</text>
</g>
<!-- 1cd6cd505e6d4bcb90641198590c283d -->
<g id="node20" class="node">
<title>1cd6cd505e6d4bcb90641198590c283d</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-179.565"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-66.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_usage_events</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-51.39" font-family="PingFang SC" font-size="10.50" fill="#2d3436">登录 / 需求 / 报告行为</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,71 @@
title: "A 卡模型运维模块 · 双库数据域关系 V0.2\n平台标识只读引用,运维业务全部落入独立库"
direction: LR
formats: [png, svg]
theme:
font: "PingFang SC"
title_size: 24
node_size: 10.5
edge_size: 8.8
splines: spline
nodesep: 0.5
ranksep: 0.82
dpi: 180
pad: 0.5
size: "21,13!"
clusters:
- name: "model_platform · 现有库(只读依赖)"
style: { bg: "#F5F6F8", pen: "#AEB7C4", dashed: true, margin: 18 }
nodes:
- { id: identity, label: "users + roles\n用户与登录角色", icon: flowdb }
- { id: workspace, label: "workspaces + members\n项目空间与成员", icon: flowdb }
- { id: artifacts, label: "storage_objects + versions\n文件与开发制品", icon: flowdb }
- { id: platform_jobs, label: "schedules + runs\n模型计算任务", icon: flowdb }
- name: "model_operations · 模型方受控直写(7 张)"
style: { bg: "#EEF5FD", pen: "#6F9ED4", margin: 18 }
nodes:
- { id: banks, label: "ops_banks\n银行", icon: flowdb }
- { id: models, label: "ops_model_instances\n模型实例", icon: flowdb }
- { id: versions, label: "ops_model_versions\n业务模型版本", icon: flowdb }
- { id: batches, label: "ops_monitor_batches\n发布批次 / 修订", icon: flowdb }
- { id: results, label: "ops_monitor_results\n单月原始指标", icon: flowdb }
- { id: features, label: "ops_monitor_feature_metrics\nIV / CSI / 贡献", icon: flowdb }
- { id: distributions, label: "ops_monitor_distributions\n评分 / 特征分箱", icon: flowdb }
- name: "model_operations · 运维判级与处理"
style: { bg: "#F1F8F5", pen: "#74A087", margin: 18 }
nodes:
- { id: categories, label: "ops_model_categories\n固定大类字典", icon: flowdb }
- { id: rules, label: "ops_rule_versions + items\n规则版本与矩阵", icon: flowdb }
- { id: evaluations, label: "ops_monitor_evaluations\n判级快照", icon: flowdb }
- { id: reviews, label: "ops_monitor_reviews\n模型初审 → 业务终审", icon: flowdb }
- name: "model_operations · 报告、流程与运营"
style: { bg: "#FAF6EE", pen: "#C5A467", margin: 18 }
nodes:
- { id: reports, label: "ops_reports + revisions\n报告与不可变修订", icon: flowdb }
- { id: governance, label: "模板 / Prompt / 回归\n按银行报告配置", icon: flowdb }
- { id: workflows, label: "ops_workflows + stages\n七阶段流程", icon: flowdb }
- { id: documents, label: "ops_documents\n材料 / 知识库索引", icon: flowdb }
- { id: usage, label: "ops_usage_events\n登录 / 需求 / 报告行为", icon: flowdb }
- { id: events, label: "ops_outbox + inbox\n独立异步事件流", icon: flowdb }
edges:
- { from: identity, to: reviews, label: "处理人 ID / 显示名", type: dep }
- { from: identity, to: workflows, label: "发起人 / 经办人", type: dep }
- { from: workspace, to: models, label: "workspace_id 逻辑引用", type: dep }
- { from: artifacts, to: versions, label: "可选开发制品引用", type: dep }
- { from: artifacts, to: documents, label: "storage_object_id", type: dep }
- { from: platform_jobs, to: batches, label: "计算任务产出", type: dep }
- { from: banks, to: models, label: "1:N", type: plain }
- { from: categories, to: models, label: "1:N", type: plain }
- { from: models, to: versions, label: "1:N", type: plain }
- { from: batches, to: results, label: "published 后可见", type: write, width: 2.0 }
- { from: versions, to: results, label: "版本快照", type: plain }
- { from: results, to: features, label: "1:N", type: plain }
- { from: results, to: distributions, label: "1:N", type: plain }
- { from: rules, to: evaluations, label: "按已发布规则计算", type: dep }
- { from: results, to: evaluations, label: "原始指标 → 判级", type: write, width: 2.0 }
- { from: evaluations, to: reviews, label: "触发两段处理", type: flow }
- { from: evaluations, to: reports, label: "绑定判级快照", type: flow }
- { from: governance, to: reports, label: "模板 + Prompt", type: dep }
- { from: models, to: workflows, label: "开发 / 复用通用模型", type: flow }
- { from: workflows, to: documents, label: "阶段材料", type: write }
- { from: reports, to: events, label: "生成 / 提醒事件", type: async }
Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: A 卡模型运维模块 · 推荐系统架构 V0.2
同一工程 / 双数据库 / 平台库只读 / 运维库自有 Pages: 1 -->
<svg width="2352pt" height="1980pt"
viewBox="0.00 0.00 2352.00 1980.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1.52754 1.52754) rotate(0) translate(39.6 1256.6)">
<title>A 卡模型运维模块 · 推荐系统架构 V0.2
同一工程 / 双数据库 / 平台库只读 / 运维库自有</title>
<polygon fill="white" stroke="none" points="-39.6,39.6 -39.6,-1256.6 1500.45,-1256.6 1500.45,39.6 -39.6,39.6"/>
<text xml:space="preserve" text-anchor="middle" x="730.43" y="-1189.25" font-family="PingFang SC" font-size="25.00" fill="#2d3436">A 卡模型运维模块 · 推荐系统架构 V0.2</text>
<text xml:space="preserve" text-anchor="middle" x="730.43" y="-1153.25" font-family="PingFang SC" font-size="25.00" fill="#2d3436">同一工程 / 双数据库 / 平台库只读 / 运维库自有</text>
<g id="clust1" class="cluster">
<title>cluster_入口层(现有)</title>
<path fill="#f5f7fa" stroke="#aeb7c4" d="M20,-889C20,-889 663.38,-889 663.38,-889 669.38,-889 675.38,-895 675.38,-901 675.38,-901 675.38,-1113 675.38,-1113 675.38,-1119 669.38,-1125 663.38,-1125 663.38,-1125 20,-1125 20,-1125 14,-1125 8,-1119 8,-1113 8,-1113 8,-901 8,-901 8,-895 14,-889 20,-889"/>
<text xml:space="preserve" text-anchor="middle" x="68.5" y="-1106.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">入口层(现有)</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_统一 FastAPI 工程(现有服务内新增模块)</title>
<path fill="#eef5fd" stroke="#7fa8d8" d="M465.1,-430C465.1,-430 745.85,-430 745.85,-430 751.85,-430 757.85,-436 757.85,-442 757.85,-442 757.85,-869 757.85,-869 757.85,-875 751.85,-881 745.85,-881 745.85,-881 465.1,-881 465.1,-881 459.1,-881 453.1,-875 453.1,-869 453.1,-869 453.1,-442 453.1,-442 453.1,-436 459.1,-430 465.1,-430"/>
<text xml:space="preserve" text-anchor="middle" x="605.48" y="-862.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">统一 FastAPI 工程(现有服务内新增模块)</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_model_platform(现有库,不改表)</title>
<path fill="#f5f6f8" stroke="#aeb7c4" stroke-dasharray="5,2" d="M904.85,-678C904.85,-678 1140.6,-678 1140.6,-678 1146.6,-678 1152.6,-684 1152.6,-690 1152.6,-690 1152.6,-1117 1152.6,-1117 1152.6,-1123 1146.6,-1129 1140.6,-1129 1140.6,-1129 904.85,-1129 904.85,-1129 898.85,-1129 892.85,-1123 892.85,-1117 892.85,-1117 892.85,-690 892.85,-690 892.85,-684 898.85,-678 904.85,-678"/>
<text xml:space="preserve" text-anchor="middle" x="1022.73" y="-1110.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_platform(现有库,不改表)</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_model_operations(新建独立库)</title>
<path fill="#f1f8f5" stroke="#78a88d" d="M912.35,-8C912.35,-8 1133.1,-8 1133.1,-8 1139.1,-8 1145.1,-14 1145.1,-20 1145.1,-20 1145.1,-658 1145.1,-658 1145.1,-664 1139.1,-670 1133.1,-670 1133.1,-670 912.35,-670 912.35,-670 906.35,-670 900.35,-664 900.35,-658 900.35,-658 900.35,-20 900.35,-20 900.35,-14 906.35,-8 912.35,-8"/>
<text xml:space="preserve" text-anchor="middle" x="1022.73" y="-651.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">model_operations(新建独立库)</text>
</g>
<g id="clust5" class="cluster">
<title>cluster_异步执行(现有服务扩展连接)</title>
<path fill="#faf6ee" stroke="#c6a96d" d="M1246.85,-10C1246.85,-10 1448.85,-10 1448.85,-10 1454.85,-10 1460.85,-16 1460.85,-22 1460.85,-22 1460.85,-234 1460.85,-234 1460.85,-240 1454.85,-246 1448.85,-246 1448.85,-246 1246.85,-246 1246.85,-246 1240.85,-246 1234.85,-240 1234.85,-234 1234.85,-234 1234.85,-22 1234.85,-22 1234.85,-16 1240.85,-10 1246.85,-10"/>
<text xml:space="preserve" text-anchor="middle" x="1347.85" y="-227.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">异步执行(现有服务扩展连接)</text>
</g>
<!-- a43539db1c804657b3d885ca10362e1f -->
<g id="node1" class="node">
<title>a43539db1c804657b3d885ca10362e1f</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/client/users.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="28" y="-1042.4"/>
<text xml:space="preserve" text-anchor="middle" x="78.4" y="-930.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">业务团队 / 模型团队</text>
<text xml:space="preserve" text-anchor="middle" x="78.4" y="-914.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">管理员</text>
</g>
<!-- 899fff0a9a094701b6f22f9956bcdb3a -->
<g id="node2" class="node">
<title>899fff0a9a094701b6f22f9956bcdb3a</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/network/nginx.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="235.05" y="-1042.4"/>
<text xml:space="preserve" text-anchor="middle" x="285.45" y="-930.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Nginx Gateway</text>
<text xml:space="preserve" text-anchor="middle" x="285.45" y="-914.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">唯一入口</text>
</g>
<!-- a43539db1c804657b3d885ca10362e1f&#45;&gt;899fff0a9a094701b6f22f9956bcdb3a -->
<g id="edge1" class="edge">
<title>a43539db1c804657b3d885ca10362e1f&#45;&gt;899fff0a9a094701b6f22f9956bcdb3a</title>
<path fill="none" stroke="#2f6fe0" stroke-width="1.8" d="M128.78,-992C156.98,-992 192.6,-992 222.65,-992"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" stroke-width="1.8" points="222.42,-995.5 232.42,-992 222.42,-988.5 222.42,-995.5"/>
<text xml:space="preserve" text-anchor="middle" x="181.93" y="-993.15" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">HTTPS</text>
</g>
<!-- 89f32bf7b7fc46a28737c75e205d9b60 -->
<g id="node3" class="node">
<title>89f32bf7b7fc46a28737c75e205d9b60</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/react.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="554.575" y="-1042.4"/>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-930.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">React SPA</text>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-914.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型平台 + 运维平台</text>
</g>
<!-- 899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;89f32bf7b7fc46a28737c75e205d9b60 -->
<g id="edge2" class="edge">
<title>899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;89f32bf7b7fc46a28737c75e205d9b60</title>
<path fill="none" stroke="#2f6fe0" d="M335.71,-992C391.68,-992 483.05,-992 543.34,-992"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="543.08,-995.5 553.08,-992 543.08,-988.5 543.08,-995.5"/>
<text xml:space="preserve" text-anchor="middle" x="405.48" y="-998.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">静态资源</text>
</g>
<!-- 9296664724924c399cb8cfb5068084fc -->
<g id="node4" class="node">
<title>9296664724924c399cb8cfb5068084fc</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/fastapi.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="554.575" y="-796.4"/>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-684.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型平台模块</text>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-668.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">现有功能 + 模型源数据写入</text>
</g>
<!-- 899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;9296664724924c399cb8cfb5068084fc -->
<g id="edge3" class="edge">
<title>899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;9296664724924c399cb8cfb5068084fc</title>
<path fill="none" stroke="#2f6fe0" d="M335.71,-953.83C392.38,-909.93 485.33,-837.92 545.58,-791.24"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="547.64,-794.08 553.4,-785.18 543.35,-788.54 547.64,-794.08"/>
<text xml:space="preserve" text-anchor="middle" x="405.48" y="-932.42" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">模型平台 API</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37 -->
<g id="node5" class="node">
<title>9aabb7a0ed52461f9dcc185116311d37</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/fastapi.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="554.575" y="-585.4"/>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-473.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">运维模块</text>
<text xml:space="preserve" text-anchor="middle" x="604.98" y="-457.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">查询 / 判级 / 复核 / 报告 / 流程</text>
</g>
<!-- 899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;9aabb7a0ed52461f9dcc185116311d37 -->
<g id="edge4" class="edge">
<title>899fff0a9a094701b6f22f9956bcdb3a&#45;&gt;9aabb7a0ed52461f9dcc185116311d37</title>
<path fill="none" stroke="#2f6fe0" stroke-width="1.8" d="M312.06,-909.26C338.5,-833.45 385.46,-721.17 453.1,-641 478.29,-611.15 513.74,-585.67 543.95,-567.18"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" stroke-width="1.8" points="545.68,-570.22 552.47,-562.09 542.09,-564.21 545.68,-570.22"/>
<text xml:space="preserve" text-anchor="middle" x="405.48" y="-781.42" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">运维 API</text>
</g>
<!-- 69e41aadb5c1457698ca525cb60f84f4 -->
<g id="node6" class="node">
<title>69e41aadb5c1457698ca525cb60f84f4</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/database/mysql.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="971.825" y="-1044.4"/>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-932.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">MySQL · model_platform</text>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-916.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">用户 / 角色 / 工作空间 / 调度</text>
</g>
<!-- 9296664724924c399cb8cfb5068084fc&#45;&gt;69e41aadb5c1457698ca525cb60f84f4 -->
<g id="edge5" class="edge">
<title>9296664724924c399cb8cfb5068084fc&#45;&gt;69e41aadb5c1457698ca525cb60f84f4</title>
<path fill="none" stroke="#3f9ad0" d="M655.24,-775.42C732.04,-821.29 880,-909.66 962.32,-958.82"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="960.34,-961.72 970.72,-963.84 963.93,-955.71 960.34,-961.72"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-913.1" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">现有平台读写</text>
</g>
<!-- aa5be898225c4a13839eb1bf2df335f2 -->
<g id="node8" class="node">
<title>aa5be898225c4a13839eb1bf2df335f2</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/database/mysql.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="971.825" y="-585.4"/>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-473.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">7 张模型源表</text>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-457.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型方受控直写</text>
</g>
<!-- 9296664724924c399cb8cfb5068084fc&#45;&gt;aa5be898225c4a13839eb1bf2df335f2 -->
<g id="edge8" class="edge">
<title>9296664724924c399cb8cfb5068084fc&#45;&gt;aa5be898225c4a13839eb1bf2df335f2</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2.2" d="M655.23,-751.69C716.49,-755.88 820.71,-753.16 883.85,-697 894.25,-687.75 885.8,-679 892.85,-667 911.01,-636.1 937.89,-606.95 962.37,-583.94"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2.2" points="964.52,-586.71 969.51,-577.37 959.78,-581.56 964.52,-586.71"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-769.32" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">受控 SQL 直写</text>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-750.57" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">writing → published</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37&#45;&gt;69e41aadb5c1457698ca525cb60f84f4 -->
<g id="edge6" class="edge">
<title>9aabb7a0ed52461f9dcc185116311d37&#45;&gt;69e41aadb5c1457698ca525cb60f84f4</title>
<path fill="none" stroke="#8b6fc4" stroke-width="1.8" stroke-dasharray="5,2" d="M655.06,-566.29C686.29,-585.38 728.02,-609.41 766.85,-627 816.94,-649.69 851.01,-623.15 883.85,-667.25 898.51,-686.94 881.94,-866.01 892.85,-888 907.76,-918.04 935.52,-942.62 961.42,-960.52"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" stroke-width="1.8" points="959.45,-963.41 969.71,-966.03 963.32,-957.58 959.45,-963.41"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-673.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">指定表 / 字段只读</text>
</g>
<!-- 16645ab788e94fef923a94e81cca369c -->
<g id="node7" class="node">
<title>16645ab788e94fef923a94e81cca369c</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/generic/storage/storage.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="971.825" y="-833.4"/>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-721.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">对象存储 + storage_objects</text>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-705.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">文件本体与平台资源</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37&#45;&gt;16645ab788e94fef923a94e81cca369c -->
<g id="edge7" class="edge">
<title>9aabb7a0ed52461f9dcc185116311d37&#45;&gt;16645ab788e94fef923a94e81cca369c</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M655.3,-540C726.81,-548.33 854.02,-567.55 883.85,-601.25 895.67,-614.6 884.21,-665.4 892.85,-681 908.88,-709.94 936.82,-733.63 962.54,-750.87"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="960.33,-753.6 970.63,-756.1 964.14,-747.73 960.33,-753.6"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-607.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">复用文件服务</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37&#45;&gt;aa5be898225c4a13839eb1bf2df335f2 -->
<g id="edge9" class="edge">
<title>9aabb7a0ed52461f9dcc185116311d37&#45;&gt;aa5be898225c4a13839eb1bf2df335f2</title>
<path fill="none" stroke="#3f9ad0" stroke-dasharray="5,2" d="M655.25,-533.17C687.09,-532.08 729.41,-530.8 766.85,-530.25 818.84,-529.48 831.86,-529.35 883.85,-530.25 908.97,-530.68 936.66,-531.56 960.56,-532.45"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="960.32,-535.94 970.45,-532.83 960.59,-528.95 960.32,-535.94"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-536.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">只读已发布修订</text>
</g>
<!-- c96300e2e41e48dd97521fa7cb378d72 -->
<g id="node9" class="node">
<title>c96300e2e41e48dd97521fa7cb378d72</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/database/mysql.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="971.825" y="-374.4"/>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-262.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">15 张运维业务表</text>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-246.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">判级 / 处理 / 报告 / 流程</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37&#45;&gt;c96300e2e41e48dd97521fa7cb378d72 -->
<g id="edge10" class="edge">
<title>9aabb7a0ed52461f9dcc185116311d37&#45;&gt;c96300e2e41e48dd97521fa7cb378d72</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2.2" d="M655.24,-509.97C731.41,-471.26 877.6,-396.98 960.3,-354.96"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2.2" points="961.73,-358.16 969.06,-350.51 958.55,-351.92 961.73,-358.16"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-454.8" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">业务读写</text>
</g>
<!-- bcaf3960a54f41b9bc84a892ea5cb7bb -->
<g id="node10" class="node">
<title>bcaf3960a54f41b9bc84a892ea5cb7bb</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="971.825" y="-163.4"/>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-51.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">ops_outbox_events</text>
<text xml:space="preserve" text-anchor="middle" x="1022.23" y="-35.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">运维库独立事件流</text>
</g>
<!-- 9aabb7a0ed52461f9dcc185116311d37&#45;&gt;bcaf3960a54f41b9bc84a892ea5cb7bb -->
<g id="edge11" class="edge">
<title>9aabb7a0ed52461f9dcc185116311d37&#45;&gt;bcaf3960a54f41b9bc84a892ea5cb7bb</title>
<path fill="none" stroke="#e0902f" stroke-dasharray="5,2" d="M655.17,-475.89C710.51,-410.6 804.26,-303.53 892.85,-219 914.91,-197.95 940.59,-176.25 963.16,-158.01"/>
<polygon fill="#e0902f" stroke="#e0902f" points="965.18,-160.87 970.79,-151.88 960.8,-155.41 965.18,-160.87"/>
<text xml:space="preserve" text-anchor="middle" x="825.35" y="-351.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">同事务写事件</text>
</g>
<!-- 2b9a60d3180746c5a0ebb4649ddfd854 -->
<g id="node11" class="node">
<title>2b9a60d3180746c5a0ebb4649ddfd854</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/predefined-process.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1296.95" y="-163.4"/>
<text xml:space="preserve" text-anchor="middle" x="1347.35" y="-51.25" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Schedule Executor</text>
<text xml:space="preserve" text-anchor="middle" x="1347.35" y="-35.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">增加运维库 Outbox 轮询</text>
</g>
<!-- bcaf3960a54f41b9bc84a892ea5cb7bb&#45;&gt;2b9a60d3180746c5a0ebb4649ddfd854 -->
<g id="edge12" class="edge">
<title>bcaf3960a54f41b9bc84a892ea5cb7bb&#45;&gt;2b9a60d3180746c5a0ebb4649ddfd854</title>
<path fill="none" stroke="#e0902f" stroke-width="1.8" stroke-dasharray="5,2" d="M1072.3,-113C1129.08,-113 1222.67,-113 1284.41,-113"/>
<polygon fill="#e0902f" stroke="#e0902f" stroke-width="1.8" points="1284.35,-116.5 1294.35,-113 1284.35,-109.5 1284.35,-116.5"/>
<text xml:space="preserve" text-anchor="middle" x="1192.73" y="-119.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">轮询 / 重试</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,55 @@
title: "A 卡模型运维模块 · 推荐系统架构 V0.2\n同一工程 / 双数据库 / 平台库只读 / 运维库自有"
direction: LR
formats: [png, svg]
theme:
font: "PingFang SC"
title_size: 25
node_size: 11
edge_size: 9.5
splines: spline
nodesep: 0.62
ranksep: 0.86
dpi: 180
pad: 0.55
size: "21,11!"
clusters:
- name: "入口层(现有)"
style: { bg: "#F5F7FA", pen: "#AEB7C4", margin: 20 }
nodes:
- { id: users, label: "业务团队 / 模型团队\n管理员", icon: users }
- { id: nginx, label: "Nginx Gateway\n唯一入口", icon: nginx }
- { id: react, label: "React SPA\n模型平台 + 运维平台", icon: react }
- name: "统一 FastAPI 工程(现有服务内新增模块)"
style: { bg: "#EEF5FD", pen: "#7FA8D8", margin: 22 }
nodes:
- { id: model_api, label: "模型平台模块\n现有功能 + 模型源数据写入", icon: fastapi }
- { id: ops_api, label: "运维模块\n查询 / 判级 / 复核 / 报告 / 流程", icon: fastapi }
- name: "model_platform(现有库,不改表)"
style: { bg: "#F5F6F8", pen: "#AEB7C4", dashed: true, margin: 22 }
nodes:
- { id: platform_db, label: "MySQL · model_platform\n用户 / 角色 / 工作空间 / 调度", icon: mysql }
- { id: storage, label: "对象存储 + storage_objects\n文件本体与平台资源", icon: storage }
- name: "model_operations(新建独立库)"
style: { bg: "#F1F8F5", pen: "#78A88D", margin: 22 }
nodes:
- { id: source_tables, label: "7 张模型源表\n模型方受控直写", icon: mysql }
- { id: ops_tables, label: "15 张运维业务表\n判级 / 处理 / 报告 / 流程", icon: mysql }
- { id: ops_outbox, label: "ops_outbox_events\n运维库独立事件流", icon: flowdb }
- name: "异步执行(现有服务扩展连接)"
style: { bg: "#FAF6EE", pen: "#C6A96D", margin: 20 }
nodes:
- { id: scheduler, label: "Schedule Executor\n增加运维库 Outbox 轮询", icon: process }
edges:
- { from: users, to: nginx, label: "HTTPS", type: flow, width: 1.8 }
- { from: nginx, to: react, label: "静态资源", type: flow }
- { from: nginx, to: model_api, label: "模型平台 API", type: flow }
- { from: nginx, to: ops_api, label: "运维 API", type: flow, width: 1.8 }
- { from: model_api, to: platform_db, label: "现有平台读写", type: write }
- { from: ops_api, to: platform_db, label: "指定表 / 字段只读", type: dep, width: 1.8 }
- { from: ops_api, to: storage, label: "复用文件服务", type: dep }
- { from: model_api, to: source_tables, label: "受控 SQL 直写\nwriting → published", type: write, width: 2.2 }
- { from: ops_api, to: source_tables, label: "只读已发布修订", type: write, style: dashed }
- { from: ops_api, to: ops_tables, label: "业务读写", type: write, width: 2.2 }
- { from: ops_api, to: ops_outbox, label: "同事务写事件", type: async }
- { from: ops_outbox, to: scheduler, label: "轮询 / 重试", type: async, width: 1.8 }
+19
View File
@@ -0,0 +1,19 @@
# 架构设计变更记录
## **当前最新版本:V0.2**
### V0.2 · 2026-08-31
- 解析甲方提供的 `model_platform` 18 张表 DDL,确认现有库只有身份、工作空间、文件、脚本版本和调度基础能力,没有运维业务表。
- 架构由“同库分表”调整为“同一工程、双数据库”:平台库指定表/字段只读,新建独立 `model_operations`
- 运维库设计为 24 张表和 1 个当前监控结果视图;模型方受控直写 7 张源表,运维模块独占判级、复核、报告与流程数据。
- 增加双数据库连接、跨库逻辑标识、独立 Outbox/Inbox 和 Schedule Executor 第二连接的边界说明。
- 系统架构图与数据域关系图同步升级至 V0.2。
### V0.1 · 2026-08-31
- 明确采用同一工程、同一 MySQL,并由模型平台受控直写 7 张源表。
- 将原始监控结果与运维判级/处理状态拆表,避免双方同时修改同一业务行。
- 给出 22 张逻辑表、批次发布与修订协议、数据库账号权限和首条 API 纵向链路。
- 明确不新增 RBAC、Redis或微服务,复用现有角色、对象存储、Outbox 与 Schedule Executor。
- 新增系统架构图、数据域关系图及仓库内可复现的 YAML 渲染脚本。
+148
View File
@@ -0,0 +1,148 @@
"""Render the versioned A-card architecture YAML files to PNG and SVG.
Setup once:
python3 -m pip install diagrams pyyaml
Usage:
python3 docs/architecture/render_diagrams.py
python3 docs/architecture/render_diagrams.py path/to/one.yaml
"""
from __future__ import annotations
import argparse
import importlib
from pathlib import Path
from typing import Any
import yaml
from diagrams import Cluster, Diagram, Edge
ICONS = {
"users": "diagrams.onprem.client:Users",
"nginx": "diagrams.onprem.network:Nginx",
"server": "diagrams.onprem.compute:Server",
"storage": "diagrams.generic.storage:Storage",
"process": "diagrams.programming.flowchart:PredefinedProcess",
"flowdb": "diagrams.programming.flowchart:Database",
"fastapi": "diagrams.programming.framework:Fastapi",
"react": "diagrams.programming.framework:React",
"mysql": "diagrams.onprem.database:MySQL",
}
EDGE_TYPES = {
"flow": {"color": "#2f6fe0"},
"write": {"color": "#3f9ad0"},
"async": {"color": "#e0902f", "style": "dashed"},
"obs": {"color": "#4f9d78", "style": "dashed"},
"dep": {"color": "#8b6fc4", "style": "dashed"},
"plain": {"color": "#9aa2ad"},
}
def resolve_icon(name: str) -> Any:
module_path, class_name = ICONS[name].split(":")
return getattr(importlib.import_module(module_path), class_name)
def cluster_attr(style: dict[str, Any], font: str) -> dict[str, str]:
return {
"bgcolor": style.get("bg", "#F5F6F8"),
"pencolor": style.get("pen", "#B8C0CC"),
"style": "rounded,dashed" if style.get("dashed") else "rounded",
"fontname": font,
"fontsize": str(style.get("fontsize", 15)),
"margin": str(style.get("margin", 18)),
}
def build_nodes(definitions: list[dict], registry: dict[str, Any]) -> None:
for definition in definitions:
label = str(definition.get("label", definition["id"])).replace("\\n", "\n")
registry[definition["id"]] = resolve_icon(definition.get("icon", "server"))(label)
def build_clusters(
definitions: list[dict], registry: dict[str, Any], font: str
) -> None:
for definition in definitions:
with Cluster(
definition["name"],
graph_attr=cluster_attr(definition.get("style", {}), font),
):
build_nodes(definition.get("nodes", []), registry)
build_clusters(definition.get("clusters", []), registry, font)
def draw_edges(definitions: list[dict], registry: dict[str, Any]) -> None:
for definition in definitions:
attributes = dict(
EDGE_TYPES.get(definition.get("type", "plain"), EDGE_TYPES["plain"])
)
for key, output_key in (
("label", "label"),
("style", "style"),
("width", "penwidth"),
("color", "color"),
("dir", "dir"),
):
if key in definition:
value = definition[key]
attributes[output_key] = (
str(value).replace("\\n", "\n") if key == "label" else str(value)
)
if definition.get("free"):
attributes["constraint"] = "false"
registry[definition["from"]] >> Edge(**attributes) >> registry[definition["to"]]
def render(path: Path) -> None:
spec = yaml.safe_load(path.read_text(encoding="utf-8"))
theme = spec.get("theme", {})
font = theme.get("font", "PingFang SC")
graph_attr = {
"fontname": font,
"fontsize": str(theme.get("title_size", 28)),
"bgcolor": "white",
"pad": str(theme.get("pad", 0.7)),
"splines": theme.get("splines", "ortho"),
"nodesep": str(theme.get("nodesep", 0.75)),
"ranksep": str(theme.get("ranksep", 1.0)),
"compound": "true",
"newrank": "true",
"label": str(spec.get("title", "")).replace("\\n", "\n"),
"labelloc": "t",
}
for optional in ("dpi", "size"):
if theme.get(optional):
graph_attr[optional] = str(theme[optional])
registry: dict[str, Any] = {}
with Diagram(
str(spec.get("title", path.stem)).replace("\\n", "\n"),
filename=str(path.with_suffix("")),
show=False,
direction=spec.get("direction", "TB"),
graph_attr=graph_attr,
node_attr={"fontname": font, "fontsize": str(theme.get("node_size", 12))},
edge_attr={"fontname": font, "fontsize": str(theme.get("edge_size", 10))},
outformat=spec.get("formats", ["png", "svg"]),
):
build_nodes(spec.get("nodes", []), registry)
build_clusters(spec.get("clusters", []), registry, font)
draw_edges(spec.get("edges", []), registry)
print(f"rendered {path.name}: {len(registry)} nodes")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="*", type=Path)
args = parser.parse_args()
paths = args.paths or sorted(Path(__file__).parent.glob("*.yaml"))
for path in paths:
render(path.resolve())
if __name__ == "__main__":
main()
@@ -0,0 +1,291 @@
# A 卡模型运维模块后端架构与数据库设计 V0.1
> 状态:设计评审稿,尚未创建 ORM、迁移或接口实现
> 日期:2026-08-31
> 依据:当前 `develop` 工程结构、运维前端 V1.9 页面与现有三条 API 契约
## 1. 结论
采用“**同一工程、同一 MySQL、分表归属、发布后可见**”的方式:
- 模型平台直接写入 7 张受控源表,不通过运维平台复制一份数据。
- 运维模块只读取状态为 `published` 的监控批次,未发布或失败批次对页面不可见。
- 判级、复核、报告、流程、文档、Prompt 和配置由运维模块独立维护,模型平台不得写入。
- 已发布监控数据不原地覆盖;勘误时新增修订批次,保留历史版本和审计链。
- 首期不新增微服务、Redis或消息中间件;复用现有 Nginx、React、FastAPI、MySQL、对象存储和 MySQL Outbox。
这能满足“模型方直接写业务表”的效率要求,同时避免模型任务误覆盖业务处理结果。
## 2. 模块边界
### 2.1 模型平台负责
- 银行、模型实例、模型版本及生命周期数据的写入。
- 月度监控原始结果、特征指标和分箱分布数据的计算与写入。
- 写入批次的完整性校验、计数、校验和及发布。
- 已发布数据发生错误时,以新修订批次更正,不修改旧记录。
### 2.2 运维模块负责
- 按已发布原始结果和已发布规则版本计算异常等级、监控结果等级和命中原因。
- 模型团队初审、业务团队终审、超时默认不处理及全程留痕。
- 监控/诊断报告生成、编辑、发送、汇总和历史修订。
- 七阶段开发评审流程、材料、知识库、规则、Prompt、报告模板和按银行配置。
- 工作台、模型大类、细分银行、监控明细和详情等查询 API。
### 2.3 明确不做
- 运维模块不重复建设账号、角色、权限表;只复用登录模块返回的用户、角色和工作空间。
- 模型平台不写判级、处理、报告、流程或配置表。
- “手工同步”不再复制数据;其含义调整为“校验最新发布批次并重建派生判级”。
- 报告汇总、知识库和工作台不单独建冗余汇总表,首期由业务表查询生成。
## 3. 总体架构
现有部署保持不变。运维能力作为 FastAPI 后端内的新 bounded context 接入:
1. 浏览器继续经 Nginx 访问 React SPA 和同源 `/api/v1/*`
2. 模型平台模块或调度任务直接写 `ops_*` 源表。
3. 批次发布后,运维服务计算并保存规则判级快照。
4. 运维 API 联查源数据、判级和处理结果,返回前端 DTO。
5. 报告生成、提醒等后台动作复用现有 MySQL Outbox 和 Schedule Executor。
6. 报告文件、流程材料继续复用 `storage_objects` 与现有对象存储抽象。
配套图:
- `A卡模型运维模块-系统架构-V0.1.png/.svg`
- `A卡模型运维模块-数据域关系-V0.1.png/.svg`
修改 YAML 后运行 `python3 docs/architecture/render_diagrams.py` 可重新生成两种格式;脚本依赖 `diagrams``pyyaml` 和本机 Graphviz。
## 4. 数据设计约定
- 表名前缀统一为 `ops_`,与现有模型平台表隔离。
- 主键统一使用 ULID `CHAR(26)`;日期时间统一 `DATETIME(3)`
- 月份在库内用 `DATE` 且固定为当月 1 日,API 序列化为 `YYYY-MM`
- KS、PSI、IV、CSI、坏账率和降幅在数据库中统一存 **01 比率**,使用 `DECIMAL(12,8)`;API 转换为 0~100 的百分数。
- 沿用当前仓库约定:不创建物理外键,使用 `fk_*` 逻辑索引并在服务层校验完整性。
- 可修改业务状态表使用 `state_version INTEGER` 做乐观锁。
- 需要删除的配置或流程数据采用 `is_deleted``deleted_at` 软删除;监控历史不允许删除。
- 所有根业务表包含 `workspace_id`,避免未来多个项目空间数据串用。
## 5. 表清单与归属
### 5.1 模型平台可写源表(7 张)
| 表 | 用途 | 核心唯一性 |
|---|---|---|
| `ops_banks` | 银行主数据及无极银行标记 | `(workspace_id, bank_code)` |
| `ops_model_instances` | 银行下的模型实例、通用模型关系、当前版本 | `(workspace_id, model_id)` |
| `ops_model_versions` | 版本、开发指标和生命周期日期 | `(model_instance_id, version_label)` |
| `ops_monitor_batches` | 月度写入批次、修订号、发布状态和校验和 | `(workspace_id, source_system, monitor_month, revision_no)` |
| `ops_monitor_results` | 模型单月原始排序性、KS、PSI和样本量 | `(batch_id, model_instance_id)` |
| `ops_monitor_feature_metrics` | 单月特征级 IV、CSI及贡献变化 | `(monitor_result_id, feature_code)` |
| `ops_monitor_distributions` | 评分分箱或特征分箱的基准期/当期分布 | `(monitor_result_id, dimension_type, feature_code, bin_order)` |
`ops_model_categories` 为运维模块维护的固定字典,模型平台只有读取权限。首期种子为 `std / bai / big / afd` 四类。
### 5.2 运维模块独占表(15 张)
| 数据域 | 表 |
|---|---|
| 字典 | `ops_model_categories` |
| 判级与处理 | `ops_rule_versions``ops_rule_items``ops_monitor_evaluations``ops_monitor_reviews` |
| 报告治理 | `ops_reports``ops_report_revisions``ops_report_template_versions``ops_prompt_versions``ops_prompt_regression_runs``ops_bank_report_configs` |
| 流程与材料 | `ops_workflows``ops_workflow_stages``ops_documents` |
| 使用统计 | `ops_usage_events` |
不新增通知任务表;通知和后台报告任务复用现有 `outbox_events`。若后续需要展示逐次送达结果,再增通知投递日志表。
## 6. 核心表字段初稿
### 6.1 模型与版本
`ops_model_instances`
- `model_instance_id``workspace_id``bank_id``category_id`
- `model_id``model_name``model_status`
- `current_version_id`
- `is_common_model``common_source_model_id``common_model_name`
- `source_updated_at``created_at``updated_at`
`ops_model_versions`
- `model_version_id``model_instance_id``version_label``version_status`
- `developer_user_ref``developer_display_name`
- `development_date``iteration_start_date``last_iteration_date``iteration_reason`
- `escort_start_date``escort_end_date``online_date``offline_date`
- `development_ks``development_psi``max_lift`
- `scoring_logic_storage_object_id``source_updated_at`
模型实例只保存当前版本指针,所有生命周期和开发指标按版本留存。
### 6.2 监控批次和原始结果
`ops_monitor_batches`
- `batch_id``workspace_id``source_system``source_batch_no`
- `monitor_month``revision_no`
- `batch_status``writing / published / failed / superseded`
- `expected_model_count``written_model_count``feature_row_count``distribution_row_count`
- `checksum_sha256``generated_at``published_at``failed_reason`
`ops_monitor_results`
- `monitor_result_id``batch_id``model_instance_id``model_version_id``monitor_month`
- `ranking_result``matched / unmatched / not_applicable`
- `ks_value``psi_value`
- `sample_count``good_count``bad_count`
- `source_result_json`:仅保存尚未结构化且确有追溯价值的源字段
- `calculated_at``created_at`
源表不保存 A/B/C、异常等级、处理建议或报告状态,避免两个模块同时改一行。
### 6.3 判级快照
`ops_monitor_evaluations`
- `evaluation_id``monitor_result_id``rule_version_id``rule_item_id`
- `ks_mom_drop_rate``secondary_level2_hits_6m`
- `abnormal_level``normal / level1 / level2 / level3`
- `monitor_grade``A / B / C`
- `reason_code``reason_text_snapshot``action_snapshot`
- `is_current``evaluated_at`
每次规则发布或源数据修订都新增判级记录;旧快照不覆盖。页面默认读取 `is_current=1` 的记录,历史报告仍绑定生成时的判级快照。
### 6.4 两段处理
`ops_monitor_reviews`
- `review_id``monitor_result_id``evaluation_id`
- `review_stage``model_initial / business_final`
- `review_status``pending / handled / auto_closed`
- `decision``no_action / tune_or_rebuild`
- `handling_note`(手工处理必填)
- `due_at``handled_by``handled_at``auto_closed_at`
- `state_version``created_at``updated_at`
唯一约束为 `(monitor_result_id, review_stage)`。模型团队阶段完成后才创建或激活业务团队阶段;超时任务通过 Outbox 执行默认“不处理”。
### 6.5 报告
`ops_reports` 保存报告主状态与绑定快照,`ops_report_revisions` 保存每次正文修改:
- 报告绑定 `monitor_result_id + evaluation_id + template_version_id + prompt_version_id`
- `ops_report_revisions` 使用 `(report_id, revision_no)` 唯一约束,正文和结构化快照均不可变。
- 监控报告页默认只查询最新监控月份;报告汇总页查询全部历史。数据库不删除旧月份报告。
- PDF/Excel 等文件通过 `storage_object_id` 指向现有 `storage_objects`
### 6.6 流程、材料与知识库
- `ops_workflows`:银行、模型大类、模型实例、独立开发/复用通用模型、当前阶段、状态和计划日期。
- `ops_workflow_stages`:1~7 阶段实例、负责人角色、开始/截止/完成时间、确认状态和停滞天数。
- `ops_documents`:同时支持 `storage_object_id``external_url`,可关联流程、阶段、模型和版本。
- 知识库不是新表,而是对 `ops_documents` 按银行、模型、版本、环节和通用模型标记查询。
## 7. 发布协议
模型平台每次写入必须执行以下协议:
1. 创建 `ops_monitor_batches`,状态为 `writing`,携带幂等的 `source_batch_no`
2. 幂等写入银行、模型实例和版本;插入本批次结果、特征指标和分布。
3. 校验模型数、明细行数、必填字段、指标范围和 SHA-256 校验和。
4. 在一个短事务内将批次改为 `published`;同月份上一修订改为 `superseded`
5. 写入 `monitor_batch.published` Outbox 事件,触发运维模块生成判级快照。
6. 运维查询只允许关联 `published` 批次,并按最高修订号取当前结果。
失败批次保留为 `failed` 供排查。任何已发布结果、特征和分布均不得执行 `UPDATE` 或物理删除。
## 8. 数据库权限
建议生产环境至少分三类账号:
| 账号 | 权限 |
|---|---|
| `schema_migrator` | 仅部署时使用,可对全部 `ops_*` 执行 DDL |
| `model_writer` | 读取字典;对 7 张源表执行限定的 `SELECT/INSERT/UPDATE`,无 `DELETE`,不能写运维独占表 |
| `operations_app` | 读取全部源表;读写运维独占表;不能修改已发布源结果 |
若模型任务与运维 API 暂时运行在同一 FastAPI 进程,代码层仍按两个 repository 隔离;生产批处理脚本优先使用独立 `MODEL_WRITER_DATABASE_URL`,将权限边界落实到数据库。
## 9. API 设计
### 9.1 首条真实纵向链路
保持前端已写好的路径,不重新发明契约:
| 接口 | 主要数据来源 |
|---|---|
| `GET /api/v1/operations/models` | 模型实例 + 当前版本 + 最新已发布结果 + 当前判级 + 最近处理 |
| `GET /api/v1/operations/models/{model_id}` | 单模型完整元数据和最新状态 |
| `GET /api/v1/operations/models/{model_id}/monitor-results?month=YYYY-MM` | 指定月最新已发布修订 + 当前判级 |
所有接口返回现有 `{ data, meta? }` 包装。切换 `VITE_OPERATIONS_API_MODE=api` 前,前端 `operationsApi.ts` 需要统一附加当前 `workspace_id`,用于选择并校验数据空间。
### 9.2 后续资源接口
- 查询:`/model-categories/overview``/banks/overview``/monitor-results``/monitor-results/{id}/features``/monitor-results/{id}/distributions`
- 处理:`/monitor-results/{id}/reviews/model-initial``/reviews/business-final`
- 报告:`/reports``/reports/{id}``/reports/{id}/revisions``/reports/{id}/send`
- 流程:`/workflows``/workflows/{id}``/workflows/{id}/stages/{stage}``/documents`
- 治理:`/rule-versions``/prompt-versions``/report-template-versions``/bank-report-configs`
列表统一支持分页、排序和与 V1.9 筛选框一致的查询参数;Excel/PDF 导出走独立端点,不让普通列表接口返回超大数据集。
## 10. 登录与角色
- 不增加运维 RBAC 表,也不接管现有权限管理模块。
- API 复用登录 Cookie、`Users``Roles` 和当前工作空间,不创建新的角色或权限数据。
- 新增轻量 `operations_context`:校验用户已登录且可访问所选工作空间;业务角色取与 `/api/v1/auth/me`、前端 `AuthUser.role_code` 相同的登录角色来源,避免前后端角色口径不一致。
- 角色映射约定:`admin` → 管理员;`developer/model_team` → 模型团队;`business/business_team/biz` → 业务团队。
- 前端按角色决定页面呈现,后端仍必须按角色校验写操作,不能把前端隐藏按钮当安全边界。
- 角色代码最终值由登录/权限模块给出,运维模块只维护一处映射常量。
## 11. 代码落位建议
沿用现有 router / schema / service 分层,每个文件不超过 500 行:
```text
common/src/common/db/models/operations/
reference.py models.py monitoring.py governance.py
reviews.py reports.py workflows.py usage.py
backend/src/backend/api/operations/
__init__.py _deps.py models.py monitoring.py reviews.py
reports.py workflows.py governance.py
backend/src/backend/services/operations/
queries.py evaluation.py publishing.py reports.py workflows.py
backend/src/backend/schemas/operations/
common.py models.py monitoring.py reports.py workflows.py governance.py
```
`backend.main` 只注册一个 `operations_router`;业务规则不写进路由文件。
## 12. 实施顺序
### P0:首条真实链路
1. 建模型/版本/批次/结果/特征/分布及规则判级 11 张核心表。
2. 导入一组 2026-07 样例数据并走完整 `writing → published` 协议。
3. 实现三条既有 API,前端追加 `workspace_id` 后切到 API 模式。
4. 验证列表、模型详情、单月监控详情数值一致。
### P1:业务闭环
实现两段复核、报告及修订、七阶段流程、材料和知识库。
### P2:治理与运营
实现规则版本、Prompt 回归、模板版本、使用统计、提醒和导出优化。
## 13. 开发前需共同确认的 5 项
以下均给出推荐默认值;未得到反对意见时可按推荐值实施:
1. **模型 ID 唯一性**:推荐 `model_id` 在工作空间内全局唯一,不只在银行内唯一。
2. **指标单位**:推荐数据库存 0~1 比率,API 输出百分数;禁止两种单位混写。
3. **批次粒度**:推荐一个来源系统每月一个全量批次,勘误整体升修订号。
4. **工作空间范围**:推荐全部根表带 `workspace_id`,即使首期只有一个 A 卡项目空间。
5. **模型开发材料**:推荐运维表同时支持对象存储文件和模型平台链接,避免重复上传。
这 5 项确认后即可开始 ORM、Alembic 迁移和首条真实接口实现。
Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: A 卡模型运维模块 · 数据域关系 V0.1
源数据与运维状态分离,历史结果不可覆盖 Pages: 1 -->
<svg width="2684pt" height="2160pt"
viewBox="0.00 0.00 2684.00 2160.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1.86609 1.86609) rotate(0) translate(36 1121.5)">
<title>A 卡模型运维模块 · 数据域关系 V0.1
源数据与运维状态分离,历史结果不可覆盖</title>
<polygon fill="white" stroke="none" points="-36,36 -36,-1121.5 1402.05,-1121.5 1402.05,36 -36,36"/>
<text xml:space="preserve" text-anchor="middle" x="683.03" y="-1058.7" font-family="PingFang SC" font-size="24.00" fill="#2d3436">A 卡模型运维模块 · 数据域关系 V0.1</text>
<text xml:space="preserve" text-anchor="middle" x="683.03" y="-1024.95" font-family="PingFang SC" font-size="24.00" fill="#2d3436">源数据与运维状态分离,历史结果不可覆盖</text>
<g id="clust1" class="cluster">
<title>cluster_模型平台直写源数据(7 张)</title>
<path fill="#eef5fd" stroke="#6f9ed4" d="M20,-329C20,-329 1104,-329 1104,-329 1110,-329 1116,-335 1116,-341 1116,-341 1116,-750 1116,-750 1116,-756 1110,-762 1104,-762 1104,-762 20,-762 20,-762 14,-762 8,-756 8,-750 8,-750 8,-341 8,-341 8,-335 14,-329 20,-329"/>
<text xml:space="preserve" text-anchor="middle" x="112.75" y="-743.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">模型平台直写源数据(7 张)</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_运维判级与处理</title>
<path fill="#f1f8f5" stroke="#74a087" d="M20,-8C20,-8 1346.05,-8 1346.05,-8 1352.05,-8 1358.05,-14 1358.05,-20 1358.05,-20 1358.05,-228 1358.05,-228 1358.05,-234 1352.05,-240 1346.05,-240 1346.05,-240 20,-240 20,-240 14,-240 8,-234 8,-228 8,-228 8,-20 8,-20 8,-14 14,-8 20,-8"/>
<text xml:space="preserve" text-anchor="middle" x="68.5" y="-221.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">运维判级与处理</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_报告、流程与运营</title>
<path fill="#faf6ee" stroke="#c5a467" d="M20,-770C20,-770 1346.05,-770 1346.05,-770 1352.05,-770 1358.05,-776 1358.05,-782 1358.05,-782 1358.05,-990 1358.05,-990 1358.05,-996 1352.05,-1002 1346.05,-1002 1346.05,-1002 20,-1002 20,-1002 14,-1002 8,-996 8,-990 8,-990 8,-782 8,-782 8,-776 14,-770 20,-770"/>
<text xml:space="preserve" text-anchor="middle" x="76" y="-983.75" font-family="PingFang SC" font-size="15.00" fill="#2d3436">报告、流程与运营</text>
</g>
<!-- 0bc523623a544090a0d886de1f78a625 -->
<g id="node1" class="node">
<title>0bc523623a544090a0d886de1f78a625</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-480.4"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-367.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_banks</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-352.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">银行</text>
</g>
<!-- 0e290213c8ea44bcb0ee9ee437a7c80b -->
<g id="node2" class="node">
<title>0e290213c8ea44bcb0ee9ee437a7c80b</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="205.8" y="-480.4"/>
<text xml:space="preserve" text-anchor="middle" x="256.2" y="-367.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_instances</text>
<text xml:space="preserve" text-anchor="middle" x="256.2" y="-352.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型实例</text>
</g>
<!-- 0bc523623a544090a0d886de1f78a625&#45;&gt;0e290213c8ea44bcb0ee9ee437a7c80b -->
<g id="edge1" class="edge">
<title>0bc523623a544090a0d886de1f78a625&#45;&gt;0e290213c8ea44bcb0ee9ee437a7c80b</title>
<path fill="none" stroke="#9aa2ad" d="M126.46,-430C147.55,-430 172.42,-430 194.65,-430"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="194.48,-433.5 204.48,-430 194.48,-426.5 194.48,-433.5"/>
<text xml:space="preserve" text-anchor="middle" x="166.3" y="-431.15" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 6cdbdf2ce8de45c694548663936ffb4e -->
<g id="node3" class="node">
<title>6cdbdf2ce8de45c694548663936ffb4e</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="477.85" y="-480.4"/>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-367.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_versions</text>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-352.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型版本</text>
</g>
<!-- 0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;6cdbdf2ce8de45c694548663936ffb4e -->
<g id="edge3" class="edge">
<title>0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;6cdbdf2ce8de45c694548663936ffb4e</title>
<path fill="none" stroke="#9aa2ad" d="M306.42,-430C351.49,-430 418.33,-430 466.67,-430"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="466.4,-433.5 476.4,-430 466.4,-426.5 466.4,-433.5"/>
<text xml:space="preserve" text-anchor="middle" x="392.23" y="-431.15" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 5297915299eb45eea32a0915742e54cd -->
<g id="node14" class="node">
<title>5297915299eb45eea32a0915742e54cd</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="477.85" y="-921.4"/>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-808.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_workflows + stages</text>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-793.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">七阶段流程</text>
</g>
<!-- 0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;5297915299eb45eea32a0915742e54cd -->
<g id="edge13" class="edge">
<title>0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;5297915299eb45eea32a0915742e54cd</title>
<path fill="none" stroke="#2f6fe0" d="M275.3,-512.65C287.84,-559.72 307.42,-618.59 335.6,-666 371.35,-726.15 427.3,-783.32 469.53,-821.93"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="467.17,-824.52 476.94,-828.63 471.87,-819.32 467.17,-824.52"/>
<text xml:space="preserve" text-anchor="middle" x="392.23" y="-805.77" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">开发 / 复用通用模型</text>
</g>
<!-- 87e82968badc47e9aa3f20c61d45d927 -->
<g id="node15" class="node">
<title>87e82968badc47e9aa3f20c61d45d927</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="736.4" y="-921.4"/>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-808.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_documents</text>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-793.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">材料 / 知识库索引</text>
</g>
<!-- 0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;87e82968badc47e9aa3f20c61d45d927 -->
<g id="edge15" class="edge">
<title>0e290213c8ea44bcb0ee9ee437a7c80b&#45;&gt;87e82968badc47e9aa3f20c61d45d927</title>
<path fill="none" stroke="#9aa2ad" d="M287.47,-512.74C299.21,-533.43 314.93,-553.1 335.6,-565 379.22,-590.11 405.53,-590.62 448.85,-565 471.88,-551.38 459.95,-532.13 477.85,-512.25 524.86,-460.03 540.6,-443.02 607.65,-422 649.95,-408.74 674.78,-391.98 707.4,-422 764.51,-474.54 715.77,-695.19 736.4,-770 737.11,-772.58 737.89,-775.17 738.72,-777.77"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="735.35,-778.73 741.94,-787.03 741.96,-776.43 735.35,-778.73"/>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-518.65" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">按模型 / 版本检索</text>
</g>
<!-- 941939a7ca7c4dd68aa48a7d95fba6ea -->
<g id="node5" class="node">
<title>941939a7ca7c4dd68aa48a7d95fba6ea</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="736.4" y="-480.4"/>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-367.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_results</text>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-352.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">单月原始指标</text>
</g>
<!-- 6cdbdf2ce8de45c694548663936ffb4e&#45;&gt;941939a7ca7c4dd68aa48a7d95fba6ea -->
<g id="edge5" class="edge">
<title>6cdbdf2ce8de45c694548663936ffb4e&#45;&gt;941939a7ca7c4dd68aa48a7d95fba6ea</title>
<path fill="none" stroke="#9aa2ad" d="M578.41,-430C620.31,-430 680.67,-430 725.48,-430"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="725.29,-433.5 735.29,-430 725.29,-426.5 725.29,-433.5"/>
<text xml:space="preserve" text-anchor="middle" x="657.53" y="-436.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">版本快照</text>
</g>
<!-- b4378b7b44d1441c990e5629da97e3f8 -->
<g id="node4" class="node">
<title>b4378b7b44d1441c990e5629da97e3f8</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="477.85" y="-681.4"/>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-568.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_batches</text>
<text xml:space="preserve" text-anchor="middle" x="528.25" y="-553.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">发布批次 / 修订</text>
</g>
<!-- b4378b7b44d1441c990e5629da97e3f8&#45;&gt;941939a7ca7c4dd68aa48a7d95fba6ea -->
<g id="edge4" class="edge">
<title>b4378b7b44d1441c990e5629da97e3f8&#45;&gt;941939a7ca7c4dd68aa48a7d95fba6ea</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2" d="M578.46,-605.98C615.83,-585.75 667.44,-555.09 707.4,-521 714.38,-515.04 721.28,-508.45 727.91,-501.61"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2" points="730.41,-504.05 734.71,-494.37 725.31,-499.26 730.41,-504.05"/>
<text xml:space="preserve" text-anchor="middle" x="657.53" y="-594.91" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">published 后可见</text>
</g>
<!-- 6d81dd3447fe491e8c37a88de15bba21 -->
<g id="node6" class="node">
<title>6d81dd3447fe491e8c37a88de15bba21</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="997.2" y="-480.4"/>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-367.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_feature_metrics</text>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-352.22" font-family="PingFang SC" font-size="10.50" fill="#2d3436">IV / CSI / 贡献</text>
</g>
<!-- 941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;6d81dd3447fe491e8c37a88de15bba21 -->
<g id="edge6" class="edge">
<title>941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;6d81dd3447fe491e8c37a88de15bba21</title>
<path fill="none" stroke="#9aa2ad" d="M837.09,-430C879.45,-430 940.7,-430 986.04,-430"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="985.98,-433.5 995.98,-430 985.98,-426.5 985.98,-433.5"/>
<text xml:space="preserve" text-anchor="middle" x="917.2" y="-431.15" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 8cf5f1d27bd44ef6a43acba265f9c5f4 -->
<g id="node7" class="node">
<title>8cf5f1d27bd44ef6a43acba265f9c5f4</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="997.2" y="-681.4"/>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-568.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_distributions</text>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-553.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">评分 / 特征分箱</text>
</g>
<!-- 941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;8cf5f1d27bd44ef6a43acba265f9c5f4 -->
<g id="edge7" class="edge">
<title>941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;8cf5f1d27bd44ef6a43acba265f9c5f4</title>
<path fill="none" stroke="#9aa2ad" d="M837.09,-468.28C880.26,-501.81 943.05,-550.57 988.62,-585.97"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="986.26,-588.56 996.3,-591.93 990.55,-583.04 986.26,-588.56"/>
<text xml:space="preserve" text-anchor="middle" x="917.2" y="-567.14" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 1a651d34b0c045b88085bf40cb9c52e1 -->
<g id="node10" class="node">
<title>1a651d34b0c045b88085bf40cb9c52e1</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="997.2" y="-159.4"/>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-46.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_evaluations</text>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-31.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">判级快照</text>
</g>
<!-- 941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;1a651d34b0c045b88085bf40cb9c52e1 -->
<g id="edge9" class="edge">
<title>941939a7ca7c4dd68aa48a7d95fba6ea&#45;&gt;1a651d34b0c045b88085bf40cb9c52e1</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2" d="M837.09,-368.87C880.46,-315.07 943.63,-236.72 989.26,-180.13"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2" points="991.82,-182.52 995.38,-172.53 986.37,-178.12 991.82,-182.52"/>
<text xml:space="preserve" text-anchor="middle" x="917.2" y="-332.57" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">原始指标 → 判级</text>
</g>
<!-- 13501c09d5a347388b6bf1551ddd64dd -->
<g id="node8" class="node">
<title>13501c09d5a347388b6bf1551ddd64dd</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-159.4"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-46.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_model_categories</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-31.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">固定大类字典</text>
</g>
<!-- 13501c09d5a347388b6bf1551ddd64dd&#45;&gt;0e290213c8ea44bcb0ee9ee437a7c80b -->
<g id="edge2" class="edge">
<title>13501c09d5a347388b6bf1551ddd64dd&#45;&gt;0e290213c8ea44bcb0ee9ee437a7c80b</title>
<path fill="none" stroke="#9aa2ad" d="M122.42,-191.74C145.43,-233.87 174.13,-286.15 202.82,-337.65"/>
<polygon fill="#9aa2ad" stroke="#9aa2ad" points="199.7,-339.24 207.63,-346.27 205.81,-335.83 199.7,-339.24"/>
<text xml:space="preserve" text-anchor="middle" x="166.3" y="-286.52" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">1:N</text>
</g>
<!-- 6253de4668fb4387968b2c3037eb1130 -->
<g id="node9" class="node">
<title>6253de4668fb4387968b2c3037eb1130</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="736.4" y="-159.4"/>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-46.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_rule_versions + items</text>
<text xml:space="preserve" text-anchor="middle" x="786.8" y="-31.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">规则版本与矩阵</text>
</g>
<!-- 6253de4668fb4387968b2c3037eb1130&#45;&gt;1a651d34b0c045b88085bf40cb9c52e1 -->
<g id="edge8" class="edge">
<title>6253de4668fb4387968b2c3037eb1130&#45;&gt;1a651d34b0c045b88085bf40cb9c52e1</title>
<path fill="none" stroke="#8b6fc4" stroke-width="1.8" stroke-dasharray="5,2" d="M837.09,-109C879.09,-109 939.66,-109 984.88,-109"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" stroke-width="1.8" points="984.77,-112.5 994.77,-109 984.77,-105.5 984.77,-112.5"/>
<text xml:space="preserve" text-anchor="middle" x="917.2" y="-115.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">按已发布规则计算</text>
</g>
<!-- 79d9b23ca5254f4cacb53546dfb6824e -->
<g id="node11" class="node">
<title>79d9b23ca5254f4cacb53546dfb6824e</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1239.25" y="-159.4"/>
<text xml:space="preserve" text-anchor="middle" x="1289.65" y="-46.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_monitor_reviews</text>
<text xml:space="preserve" text-anchor="middle" x="1289.65" y="-31.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模型初审 → 业务终审</text>
</g>
<!-- 1a651d34b0c045b88085bf40cb9c52e1&#45;&gt;79d9b23ca5254f4cacb53546dfb6824e -->
<g id="edge10" class="edge">
<title>1a651d34b0c045b88085bf40cb9c52e1&#45;&gt;79d9b23ca5254f4cacb53546dfb6824e</title>
<path fill="none" stroke="#2f6fe0" d="M1097.76,-109C1135.54,-109 1187.92,-109 1228.26,-109"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="1228.05,-112.5 1238.05,-109 1228.05,-105.5 1228.05,-112.5"/>
<text xml:space="preserve" text-anchor="middle" x="1168.63" y="-115.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">触发两段处理</text>
</g>
<!-- 34aca64f2cd942a883883ca8ca28adce -->
<g id="node12" class="node">
<title>34aca64f2cd942a883883ca8ca28adce</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="1239.25" y="-921.4"/>
<text xml:space="preserve" text-anchor="middle" x="1289.65" y="-808.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_reports + revisions</text>
<text xml:space="preserve" text-anchor="middle" x="1289.65" y="-793.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">报告与不可变修订</text>
</g>
<!-- 1a651d34b0c045b88085bf40cb9c52e1&#45;&gt;34aca64f2cd942a883883ca8ca28adce -->
<g id="edge11" class="edge">
<title>1a651d34b0c045b88085bf40cb9c52e1&#45;&gt;34aca64f2cd942a883883ca8ca28adce</title>
<path fill="none" stroke="#2f6fe0" d="M1075,-191.79C1087.47,-230.95 1102.53,-278.37 1116,-321 1166.69,-481.47 1225.28,-668.42 1259.52,-777.84"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="1256.08,-778.56 1262.41,-787.06 1262.76,-776.47 1256.08,-778.56"/>
<text xml:space="preserve" text-anchor="middle" x="1168.63" y="-626.54" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">绑定判级快照</text>
</g>
<!-- 80b8a0a57f5041f0b9226cdc133358f6 -->
<g id="node13" class="node">
<title>80b8a0a57f5041f0b9226cdc133358f6</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="997.2" y="-921.4"/>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-808.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">模板 / Prompt / 回归</text>
<text xml:space="preserve" text-anchor="middle" x="1047.6" y="-793.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">按银行报告配置</text>
</g>
<!-- 80b8a0a57f5041f0b9226cdc133358f6&#45;&gt;34aca64f2cd942a883883ca8ca28adce -->
<g id="edge12" class="edge">
<title>80b8a0a57f5041f0b9226cdc133358f6&#45;&gt;34aca64f2cd942a883883ca8ca28adce</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M1097.76,-871C1135.54,-871 1187.92,-871 1228.26,-871"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="1228.05,-874.5 1238.05,-871 1228.05,-867.5 1228.05,-874.5"/>
<text xml:space="preserve" text-anchor="middle" x="1168.63" y="-877.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">模板 + Prompt</text>
</g>
<!-- 5297915299eb45eea32a0915742e54cd&#45;&gt;87e82968badc47e9aa3f20c61d45d927 -->
<g id="edge14" class="edge">
<title>5297915299eb45eea32a0915742e54cd&#45;&gt;87e82968badc47e9aa3f20c61d45d927</title>
<path fill="none" stroke="#3f9ad0" d="M578.41,-871C620.31,-871 680.67,-871 725.48,-871"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="725.29,-874.5 735.29,-871 725.29,-867.5 725.29,-874.5"/>
<text xml:space="preserve" text-anchor="middle" x="657.53" y="-877.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">阶段材料</text>
</g>
<!-- dd6af7440411402f8066d90862eac449 -->
<g id="node16" class="node">
<title>dd6af7440411402f8066d90862eac449</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="26" y="-921.4"/>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-808.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">ops_usage_events</text>
<text xml:space="preserve" text-anchor="middle" x="76.4" y="-793.23" font-family="PingFang SC" font-size="10.50" fill="#2d3436">登录 / 需求 / 报告行为</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,56 @@
title: "A 卡模型运维模块 · 数据域关系 V0.1\n源数据与运维状态分离,历史结果不可覆盖"
direction: LR
formats: [png, svg]
theme:
font: "PingFang SC"
title_size: 24
node_size: 10.5
edge_size: 8.8
splines: spline
nodesep: 0.48
ranksep: 0.8
dpi: 180
pad: 0.5
size: "20,12!"
clusters:
- name: "模型平台直写源数据(7 张)"
style: { bg: "#EEF5FD", pen: "#6F9ED4", margin: 18 }
nodes:
- { id: banks, label: "ops_banks\n银行", icon: flowdb }
- { id: models, label: "ops_model_instances\n模型实例", icon: flowdb }
- { id: versions, label: "ops_model_versions\n模型版本", icon: flowdb }
- { id: batches, label: "ops_monitor_batches\n发布批次 / 修订", icon: flowdb }
- { id: results, label: "ops_monitor_results\n单月原始指标", icon: flowdb }
- { id: features, label: "ops_monitor_feature_metrics\nIV / CSI / 贡献", icon: flowdb }
- { id: distributions, label: "ops_monitor_distributions\n评分 / 特征分箱", icon: flowdb }
- name: "运维判级与处理"
style: { bg: "#F1F8F5", pen: "#74A087", margin: 18 }
nodes:
- { id: categories, label: "ops_model_categories\n固定大类字典", icon: flowdb }
- { id: rules, label: "ops_rule_versions + items\n规则版本与矩阵", icon: flowdb }
- { id: evaluations, label: "ops_monitor_evaluations\n判级快照", icon: flowdb }
- { id: reviews, label: "ops_monitor_reviews\n模型初审 → 业务终审", icon: flowdb }
- name: "报告、流程与运营"
style: { bg: "#FAF6EE", pen: "#C5A467", margin: 18 }
nodes:
- { id: reports, label: "ops_reports + revisions\n报告与不可变修订", icon: flowdb }
- { id: governance, label: "模板 / Prompt / 回归\n按银行报告配置", icon: flowdb }
- { id: workflows, label: "ops_workflows + stages\n七阶段流程", icon: flowdb }
- { id: documents, label: "ops_documents\n材料 / 知识库索引", icon: flowdb }
- { id: usage, label: "ops_usage_events\n登录 / 需求 / 报告行为", icon: flowdb }
edges:
- { from: banks, to: models, label: "1:N", type: plain }
- { from: categories, to: models, label: "1:N", type: plain }
- { from: models, to: versions, label: "1:N", type: plain }
- { from: batches, to: results, label: "published 后可见", type: write, width: 2.0 }
- { from: versions, to: results, label: "版本快照", type: plain }
- { from: results, to: features, label: "1:N", type: plain }
- { from: results, to: distributions, label: "1:N", type: plain }
- { from: rules, to: evaluations, label: "按已发布规则计算", type: dep, width: 1.8 }
- { from: results, to: evaluations, label: "原始指标 → 判级", type: write, width: 2.0 }
- { from: evaluations, to: reviews, label: "触发两段处理", type: flow }
- { from: evaluations, to: reports, label: "绑定判级快照", type: flow }
- { from: governance, to: reports, label: "模板 + Prompt", type: dep }
- { from: models, to: workflows, label: "开发 / 复用通用模型", type: flow }
- { from: workflows, to: documents, label: "阶段材料", type: write }
- { from: models, to: documents, label: "按模型 / 版本检索", type: plain, free: true }
Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 15.1.0 (20260618.0150)
-->
<!-- Title: A 卡模型运维模块 · 推荐系统架构 V0.1
同一工程 / 同一 MySQL / 源表发布后可见 Pages: 1 -->
<svg width="1513pt" height="2160pt"
viewBox="0.00 0.00 1513.00 2160.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1.26146 1.26146) rotate(0) translate(39.6 1672.7)">
<title>A 卡模型运维模块 · 推荐系统架构 V0.1
同一工程 / 同一 MySQL / 源表发布后可见</title>
<polygon fill="white" stroke="none" points="-39.6,39.6 -39.6,-1672.7 1159.6,-1672.7 1159.6,39.6 -39.6,39.6"/>
<text xml:space="preserve" text-anchor="middle" x="560" y="-1605.35" font-family="PingFang SC" font-size="25.00" fill="#2d3436">A 卡模型运维模块 · 推荐系统架构 V0.1</text>
<text xml:space="preserve" text-anchor="middle" x="560" y="-1569.35" font-family="PingFang SC" font-size="25.00" fill="#2d3436">同一工程 / 同一 MySQL / 源表发布后可见</text>
<g id="clust1" class="cluster">
<title>cluster_入口层(现有)</title>
<path fill="#f5f7fa" stroke="#aeb7c4" d="M20,-793.55C20,-793.55 136,-793.55 136,-793.55 142,-793.55 148,-799.55 148,-805.55 148,-805.55 148,-1533.1 148,-1533.1 148,-1539.1 142,-1545.1 136,-1545.1 136,-1545.1 20,-1545.1 20,-1545.1 14,-1545.1 8,-1539.1 8,-1533.1 8,-1533.1 8,-805.55 8,-805.55 8,-799.55 14,-793.55 20,-793.55"/>
<text xml:space="preserve" text-anchor="middle" x="68.5" y="-1526.85" font-family="PingFang SC" font-size="15.00" fill="#2d3436">入口层(现有)</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_统一 FastAPI 工程(不新增服务)</title>
<path fill="#eef5fd" stroke="#7fa8d8" d="M168,-254.35C168,-254.35 436,-254.35 436,-254.35 442,-254.35 448,-260.35 448,-266.35 448,-266.35 448,-1018.9 448,-1018.9 448,-1024.9 442,-1030.9 436,-1030.9 436,-1030.9 168,-1030.9 168,-1030.9 162,-1030.9 156,-1024.9 156,-1018.9 156,-1018.9 156,-266.35 156,-266.35 156,-260.35 162,-254.35 168,-254.35"/>
<text xml:space="preserve" text-anchor="middle" x="278.38" y="-1012.65" font-family="PingFang SC" font-size="15.00" fill="#2d3436">统一 FastAPI 工程(不新增服务)</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_共享数据层(现有 MySQL</title>
<path fill="#f1f8f5" stroke="#78a88d" d="M468,-8C468,-8 736,-8 736,-8 742,-8 748,-14 748,-20 748,-20 748,-743.8 748,-743.8 748,-749.8 742,-755.8 736,-755.8 736,-755.8 468,-755.8 468,-755.8 462,-755.8 456,-749.8 456,-743.8 456,-743.8 456,-20 456,-20 456,-14 462,-8 468,-8"/>
<text xml:space="preserve" text-anchor="middle" x="559.62" y="-737.55" font-family="PingFang SC" font-size="15.00" fill="#2d3436">共享数据层(现有 MySQL</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_文件层(现有)</title>
<path fill="#faf6ee" stroke="#c6a96d" d="M984,-518.45C984,-518.45 1100,-518.45 1100,-518.45 1106,-518.45 1112,-524.45 1112,-530.45 1112,-530.45 1112,-741.8 1112,-741.8 1112,-747.8 1106,-753.8 1100,-753.8 1100,-753.8 984,-753.8 984,-753.8 978,-753.8 972,-747.8 972,-741.8 972,-741.8 972,-530.45 972,-530.45 972,-524.45 978,-518.45 984,-518.45"/>
<text xml:space="preserve" text-anchor="middle" x="1032.5" y="-735.55" font-family="PingFang SC" font-size="15.00" fill="#2d3436">文件层(现有)</text>
</g>
<!-- 8e923065d05147b68fd08369fc363143 -->
<g id="node1" class="node">
<title>8e923065d05147b68fd08369fc363143</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/client/users.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="27.6" y="-1462.95"/>
<text xml:space="preserve" text-anchor="middle" x="78" y="-1350.8" font-family="PingFang SC" font-size="11.00" fill="#2d3436">业务团队 / 模型团队</text>
<text xml:space="preserve" text-anchor="middle" x="78" y="-1335.05" font-family="PingFang SC" font-size="11.00" fill="#2d3436">管理员</text>
</g>
<!-- 7302e8c0cbac47f8b0461d1c3238c1b8 -->
<g id="node2" class="node">
<title>7302e8c0cbac47f8b0461d1c3238c1b8</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/network/nginx.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="27.6" y="-1221.85"/>
<text xml:space="preserve" text-anchor="middle" x="78" y="-1109.7" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Nginx Gateway</text>
<text xml:space="preserve" text-anchor="middle" x="78" y="-1093.95" font-family="PingFang SC" font-size="11.00" fill="#2d3436">唯一入口</text>
</g>
<!-- 8e923065d05147b68fd08369fc363143&#45;&gt;7302e8c0cbac47f8b0461d1c3238c1b8 -->
<g id="edge1" class="edge">
<title>8e923065d05147b68fd08369fc363143&#45;&gt;7302e8c0cbac47f8b0461d1c3238c1b8</title>
<path fill="none" stroke="#2f6fe0" stroke-width="1.8" d="M78,-1329.93C78,-1309.56 78,-1287.55 78,-1266.62"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" stroke-width="1.8" points="81.5,-1266.83 78,-1256.83 74.5,-1266.83 81.5,-1266.83"/>
<text xml:space="preserve" text-anchor="middle" x="100.12" y="-1286.4" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">HTTPS</text>
</g>
<!-- 4e2614e624784972b26a9cbc506918fd -->
<g id="node3" class="node">
<title>4e2614e624784972b26a9cbc506918fd</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/react.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="27.6" y="-946.75"/>
<text xml:space="preserve" text-anchor="middle" x="78" y="-834.6" font-family="PingFang SC" font-size="11.00" fill="#2d3436">React SPA</text>
<text xml:space="preserve" text-anchor="middle" x="78" y="-818.85" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型平台 + 运维平台</text>
</g>
<!-- 7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;4e2614e624784972b26a9cbc506918fd -->
<g id="edge2" class="edge">
<title>7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;4e2614e624784972b26a9cbc506918fd</title>
<path fill="none" stroke="#2f6fe0" d="M78,-1088.88C78,-1057.81 78,-1022.18 78,-990.14"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="81.5,-990.48 78,-980.48 74.5,-990.48 81.5,-990.48"/>
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-1045.3" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">静态资源</text>
</g>
<!-- 62f35beca1644317a41769c903fdcc3d -->
<g id="node4" class="node">
<title>62f35beca1644317a41769c903fdcc3d</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/fastapi.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="177.6" y="-946.75"/>
<text xml:space="preserve" text-anchor="middle" x="228" y="-834.6" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型平台模块</text>
<text xml:space="preserve" text-anchor="middle" x="228" y="-818.85" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型 / 版本 / 计算任务</text>
</g>
<!-- 7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;62f35beca1644317a41769c903fdcc3d -->
<g id="edge3" class="edge">
<title>7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;62f35beca1644317a41769c903fdcc3d</title>
<path fill="none" stroke="#2f6fe0" d="M126.22,-1088.92C137.17,-1069.98 148.63,-1049.81 159,-1030.9 166.37,-1017.46 173.99,-1003.15 181.36,-989.09"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" points="184.45,-990.73 185.97,-980.25 178.24,-987.49 184.45,-990.73"/>
<text xml:space="preserve" text-anchor="middle" x="191.3" y="-1045.3" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">模型平台 API</text>
</g>
<!-- 3cf7ac83f6764e16bfd1e5ea55c240e2 -->
<g id="node5" class="node">
<title>3cf7ac83f6764e16bfd1e5ea55c240e2</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/framework/fastapi.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="325.6" y="-946.75"/>
<text xml:space="preserve" text-anchor="middle" x="376" y="-834.6" font-family="PingFang SC" font-size="11.00" fill="#2d3436">运维模块</text>
<text xml:space="preserve" text-anchor="middle" x="376" y="-818.85" font-family="PingFang SC" font-size="11.00" fill="#2d3436">查询 / 判级 / 复核 / 报告 / 流程</text>
</g>
<!-- 7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;3cf7ac83f6764e16bfd1e5ea55c240e2 -->
<g id="edge4" class="edge">
<title>7302e8c0cbac47f8b0461d1c3238c1b8&#45;&gt;3cf7ac83f6764e16bfd1e5ea55c240e2</title>
<path fill="none" stroke="#2f6fe0" stroke-width="1.8" d="M128.03,-1148.5C177.1,-1125.01 251.66,-1083.88 302,-1030.9 313.61,-1018.68 324.01,-1004.3 333.1,-989.58"/>
<polygon fill="#2f6fe0" stroke="#2f6fe0" stroke-width="1.8" points="335.99,-991.55 338.09,-981.17 329.97,-987.98 335.99,-991.55"/>
<text xml:space="preserve" text-anchor="middle" x="317.89" y="-1045.3" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">运维 API</text>
</g>
<!-- d97ed9af3adb480abbe2376e20067521 -->
<g id="node6" class="node">
<title>d97ed9af3adb480abbe2376e20067521</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/compute/server.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="177.6" y="-671.65"/>
<text xml:space="preserve" text-anchor="middle" x="228" y="-559.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Jupyter Runtime</text>
<text xml:space="preserve" text-anchor="middle" x="228" y="-543.75" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型开发运行时</text>
</g>
<!-- 62f35beca1644317a41769c903fdcc3d&#45;&gt;d97ed9af3adb480abbe2376e20067521 -->
<g id="edge5" class="edge">
<title>62f35beca1644317a41769c903fdcc3d&#45;&gt;d97ed9af3adb480abbe2376e20067521</title>
<path fill="none" stroke="#8b6fc4" stroke-dasharray="5,2" d="M228,-813.78C228,-782.71 228,-747.08 228,-715.04"/>
<polygon fill="#8b6fc4" stroke="#8b6fc4" points="231.5,-715.38 228,-705.38 224.5,-715.38 231.5,-715.38"/>
<text xml:space="preserve" text-anchor="middle" x="259.88" y="-770.2" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">运行时控制</text>
</g>
<!-- 18c413154ae24c099f7a314c5bda70a5 -->
<g id="node8" class="node">
<title>18c413154ae24c099f7a314c5bda70a5</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/database/mysql.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="625.6" y="-163.2"/>
<text xml:space="preserve" text-anchor="middle" x="676" y="-51.05" font-family="PingFang SC" font-size="11.00" fill="#2d3436">7 张模型源表</text>
<text xml:space="preserve" text-anchor="middle" x="676" y="-35.3" font-family="PingFang SC" font-size="11.00" fill="#2d3436">模型方受控直写</text>
</g>
<!-- 62f35beca1644317a41769c903fdcc3d&#45;&gt;18c413154ae24c099f7a314c5bda70a5 -->
<g id="edge6" class="edge">
<title>62f35beca1644317a41769c903fdcc3d&#45;&gt;18c413154ae24c099f7a314c5bda70a5</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2.2" d="M275.95,-813.75C283.7,-805.77 292.38,-798.73 302,-793.55 325.31,-780.99 514.2,-788.5 540,-782.55 569.24,-775.8 584.1,-779.89 602,-755.8 721.98,-594.36 710.21,-344.17 692.37,-208.72"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2.2" points="695.85,-208.31 691.03,-198.88 688.91,-209.26 695.85,-208.31"/>
<text xml:space="preserve" text-anchor="middle" x="757.02" y="-495.1" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">受控 SQL 直写</text>
<text xml:space="preserve" text-anchor="middle" x="757.02" y="-476.35" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">writing → published</text>
</g>
<!-- 3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;18c413154ae24c099f7a314c5bda70a5 -->
<g id="edge8" class="edge">
<title>3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;18c413154ae24c099f7a314c5bda70a5</title>
<path fill="none" stroke="#3f9ad0" stroke-width="1.8" stroke-dasharray="5,2" d="M426.11,-893.21C512.07,-886.7 687.3,-860.55 780,-755.8 939.39,-575.7 1017.83,-432.17 893,-226.6 859.54,-171.5 789.44,-142.08 737.93,-127.26"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="1.8" points="739.13,-123.96 728.56,-124.69 737.28,-130.71 739.13,-123.96"/>
<text xml:space="preserve" text-anchor="middle" x="994.18" y="-487.22" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">只读已发布修订</text>
</g>
<!-- 7a2b21350c4c414098e07d740ff99dad -->
<g id="node9" class="node">
<title>7a2b21350c4c414098e07d740ff99dad</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/onprem/database/mysql.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="477.6" y="-163.2"/>
<text xml:space="preserve" text-anchor="middle" x="528" y="-51.05" font-family="PingFang SC" font-size="11.00" fill="#2d3436">15 张运维业务表</text>
<text xml:space="preserve" text-anchor="middle" x="528" y="-35.3" font-family="PingFang SC" font-size="11.00" fill="#2d3436">判级 / 处理 / 报告 / 流程</text>
</g>
<!-- 3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;7a2b21350c4c414098e07d740ff99dad -->
<g id="edge9" class="edge">
<title>3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;7a2b21350c4c414098e07d740ff99dad</title>
<path fill="none" stroke="#3f9ad0" stroke-width="2.2" d="M426.32,-890.57C494.21,-881.46 614.61,-855.46 684,-782.55 793.3,-667.71 766.17,-598.69 790,-441.95 804.38,-347.33 851.93,-299.58 790,-226.6 735.21,-162.03 676.34,-236.16 602,-195.6 597.27,-193.02 592.69,-190.02 588.28,-186.72"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" stroke-width="2.2" points="590.83,-184.29 580.88,-180.66 586.4,-189.71 590.83,-184.29"/>
<text xml:space="preserve" text-anchor="middle" x="811.22" y="-487.22" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">业务读写</text>
</g>
<!-- b33cd1fa7d3641028972a6872041f8b5 -->
<g id="node10" class="node">
<title>b33cd1fa7d3641028972a6872041f8b5</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/database.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="477.6" y="-671.65"/>
<text xml:space="preserve" text-anchor="middle" x="528" y="-559.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">outbox_events</text>
<text xml:space="preserve" text-anchor="middle" x="528" y="-543.75" font-family="PingFang SC" font-size="11.00" fill="#2d3436">现有异步可靠投递</text>
</g>
<!-- 3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;b33cd1fa7d3641028972a6872041f8b5 -->
<g id="edge10" class="edge">
<title>3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;b33cd1fa7d3641028972a6872041f8b5</title>
<path fill="none" stroke="#e0902f" stroke-dasharray="5,2" d="M425.54,-813.92C436.76,-794.96 448.47,-774.78 459,-755.8 466.44,-742.4 474.1,-728.11 481.48,-714.06"/>
<polygon fill="#e0902f" stroke="#e0902f" points="484.57,-715.7 486.1,-705.22 478.37,-712.46 484.57,-715.7"/>
<text xml:space="preserve" text-anchor="middle" x="490.82" y="-770.2" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">同事务写事件</text>
</g>
<!-- e2ab9cb488234c80803bcdfa37522afa -->
<g id="node11" class="node">
<title>e2ab9cb488234c80803bcdfa37522afa</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/generic/storage/storage.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="991.6" y="-671.65"/>
<text xml:space="preserve" text-anchor="middle" x="1042" y="-559.5" font-family="PingFang SC" font-size="11.00" fill="#2d3436">对象存储 + storage_objects</text>
<text xml:space="preserve" text-anchor="middle" x="1042" y="-543.75" font-family="PingFang SC" font-size="11.00" fill="#2d3436">报告 / 流程材料 / 评分逻辑</text>
</g>
<!-- 3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;e2ab9cb488234c80803bcdfa37522afa -->
<g id="edge13" class="edge">
<title>3cf7ac83f6764e16bfd1e5ea55c240e2&#45;&gt;e2ab9cb488234c80803bcdfa37522afa</title>
<path fill="none" stroke="#3f9ad0" d="M426.27,-891.09C551.14,-879.66 871.47,-844.86 956,-782.55 979.45,-765.26 997.17,-739.62 1010.24,-713.88"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="1013.33,-715.54 1014.54,-705.01 1007.03,-712.48 1013.33,-715.54"/>
<text xml:space="preserve" text-anchor="middle" x="1018.85" y="-770.2" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">材料与报告文件</text>
</g>
<!-- 60e5076279b545fe957a6ff711d7e045 -->
<g id="node7" class="node">
<title>60e5076279b545fe957a6ff711d7e045</title>
<image xlink:href="/private/tmp/a-card-diagrams-v01/lib/python3.14/site-packages/resources/programming/flowchart/predefined-process.png" width="100.8px" height="100.8px" preserveAspectRatio="xMinYMin meet" x="325.6" y="-409.55"/>
<text xml:space="preserve" text-anchor="middle" x="376" y="-297.4" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Schedule Executor</text>
<text xml:space="preserve" text-anchor="middle" x="376" y="-281.65" font-family="PingFang SC" font-size="11.00" fill="#2d3436">Outbox 轮询与后台任务</text>
</g>
<!-- 60e5076279b545fe957a6ff711d7e045&#45;&gt;18c413154ae24c099f7a314c5bda70a5 -->
<g id="edge7" class="edge">
<title>60e5076279b545fe957a6ff711d7e045&#45;&gt;18c413154ae24c099f7a314c5bda70a5</title>
<path fill="none" stroke="#3f9ad0" d="M422.01,-276.59C428.95,-268.34 436.63,-260.68 445,-254.35 455.43,-246.46 460.67,-249.7 473,-245.35 480.24,-242.79 595.67,-199.95 602,-195.6 607.36,-191.92 612.6,-187.78 617.66,-183.36"/>
<polygon fill="#3f9ad0" stroke="#3f9ad0" points="619.88,-186.07 624.87,-176.72 615.14,-180.92 619.88,-186.07"/>
<text xml:space="preserve" text-anchor="middle" x="567.9" y="-233" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">批处理结果直写</text>
</g>
<!-- 60e5076279b545fe957a6ff711d7e045&#45;&gt;7a2b21350c4c414098e07d740ff99dad -->
<g id="edge12" class="edge">
<title>60e5076279b545fe957a6ff711d7e045&#45;&gt;7a2b21350c4c414098e07d740ff99dad</title>
<path fill="none" stroke="#e0902f" stroke-dasharray="5,2" d="M335.54,-276.64C332.17,-259.47 332.86,-241.92 341.5,-226.6 368.2,-179.25 423.66,-149.52 467.2,-132.55"/>
<polygon fill="#e0902f" stroke="#e0902f" points="468.15,-135.93 476.28,-129.14 465.69,-129.37 468.15,-135.93"/>
<text xml:space="preserve" text-anchor="middle" x="403.75" y="-233" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">判级 / 报告 / 超时任务</text>
</g>
<!-- b33cd1fa7d3641028972a6872041f8b5&#45;&gt;60e5076279b545fe957a6ff711d7e045 -->
<g id="edge11" class="edge">
<title>b33cd1fa7d3641028972a6872041f8b5&#45;&gt;60e5076279b545fe957a6ff711d7e045</title>
<path fill="none" stroke="#e0902f" stroke-width="1.8" stroke-dasharray="5,2" d="M480.32,-538.67C464.34,-511.32 446.41,-480.64 429.98,-452.53"/>
<polygon fill="#e0902f" stroke="#e0902f" stroke-width="1.8" points="433.21,-451.11 425.14,-444.24 427.17,-454.64 433.21,-451.11"/>
<text xml:space="preserve" text-anchor="middle" x="492.23" y="-487.22" font-family="Sans-Serif" font-size="13.00" fill="#2d3436">轮询 / 重试</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,52 @@
title: "A 卡模型运维模块 · 推荐系统架构 V0.1\n同一工程 / 同一 MySQL / 源表发布后可见"
direction: TB
formats: [png, svg]
theme:
font: "PingFang SC"
title_size: 25
node_size: 11
edge_size: 9.5
splines: spline
nodesep: 0.65
ranksep: 0.85
dpi: 180
pad: 0.55
size: "18,12!"
clusters:
- name: "入口层(现有)"
style: { bg: "#F5F7FA", pen: "#AEB7C4", margin: 20 }
nodes:
- { id: users, label: "业务团队 / 模型团队\n管理员", icon: users }
- { id: nginx, label: "Nginx Gateway\n唯一入口", icon: nginx }
- { id: react, label: "React SPA\n模型平台 + 运维平台", icon: react }
- name: "统一 FastAPI 工程(不新增服务)"
style: { bg: "#EEF5FD", pen: "#7FA8D8", margin: 22 }
nodes:
- { id: model_api, label: "模型平台模块\n模型 / 版本 / 计算任务", icon: fastapi }
- { id: ops_api, label: "运维模块\n查询 / 判级 / 复核 / 报告 / 流程", icon: fastapi }
- { id: runtime, label: "Jupyter Runtime\n模型开发运行时", icon: server }
- { id: scheduler, label: "Schedule Executor\nOutbox 轮询与后台任务", icon: process }
- name: "共享数据层(现有 MySQL"
style: { bg: "#F1F8F5", pen: "#78A88D", margin: 22 }
nodes:
- { id: source_tables, label: "7 张模型源表\n模型方受控直写", icon: mysql }
- { id: ops_tables, label: "15 张运维业务表\n判级 / 处理 / 报告 / 流程", icon: mysql }
- { id: outbox, label: "outbox_events\n现有异步可靠投递", icon: flowdb }
- name: "文件层(现有)"
style: { bg: "#FAF6EE", pen: "#C6A96D", margin: 20 }
nodes:
- { id: storage, label: "对象存储 + storage_objects\n报告 / 流程材料 / 评分逻辑", icon: storage }
edges:
- { from: users, to: nginx, label: "HTTPS", type: flow, width: 1.8 }
- { from: nginx, to: react, label: "静态资源", type: flow }
- { from: nginx, to: model_api, label: "模型平台 API", type: flow }
- { from: nginx, to: ops_api, label: "运维 API", type: flow, width: 1.8 }
- { from: model_api, to: runtime, label: "运行时控制", type: dep }
- { from: model_api, to: source_tables, label: "受控 SQL 直写\nwriting → published", type: write, width: 2.2 }
- { from: scheduler, to: source_tables, label: "批处理结果直写", type: write }
- { from: ops_api, to: source_tables, label: "只读已发布修订", type: write, style: dashed, width: 1.8 }
- { from: ops_api, to: ops_tables, label: "业务读写", type: write, width: 2.2 }
- { from: ops_api, to: outbox, label: "同事务写事件", type: async }
- { from: outbox, to: scheduler, label: "轮询 / 重试", type: async, width: 1.8 }
- { from: scheduler, to: ops_tables, label: "判级 / 报告 / 超时任务", type: async }
- { from: ops_api, to: storage, label: "材料与报告文件", type: write }
@@ -0,0 +1,154 @@
# A 卡原始监控指标——模型方表评估清单 V0.1
> 日期:2026-09-01
> 评估对象:模型方 `model_deploy` 数据库及待补监控结构。
> 目的:确认模型方能够提供哪些原始监控数据、通过哪些表提供,以及我方是否需要保留标准化月度快照。
## 1. 结论先行
模型方现有 `model_monitor_aggr` 只能部分承载 KS、PSI、IV 等标量指标,不能完整支撑 A 卡运维平台的月度监控、规则回放、报告和特征分布展示。
建议模型方提供 4 类结构化监控表或完全等价的数据结构:
1. `model_monitor_batch`:月度批次、修订和发布状态。
2. `model_monitor_result`:模型单月排序性、KS、PSI和样本量。
3. `model_monitor_feature_metric`:特征级IV、CSI及变化。
4. `model_monitor_distribution`:评分分箱和特征分箱分布。
以上数据应由模型计算任务自动写入,不由模型团队手工执行 SQL。
## 2. 模型方现有相关表
| 现有表 | 当前用途 | 可复用内容 | 不足 | 我方建议 |
|---|---|---|---|---|
| `model_deploy` | 模型部署主表 | `deployId`、模型名称、编码、状态、当前版本 | 缺业务模型大类、银行关系、通用模型等 | 作为模型主标识使用,不复制主表 |
| `model_version` | 模型版本 | 版本ID、版本号、版本名称、创建人、创建时间 | 缺迭代、陪跑、上线/下线和开发指标 | 作为版本主标识;缺失字段另补 |
| `model_details` | 模型详情 | 模型说明、输入/输出Schema | 不是月度监控数据 | 仅模型详情页读取 |
| `model_monitor_aggr` | 通用聚合指标 | `deployId/version/metricName/dimensionValue/valueNum/gmtCreated`;注释允许 `ks/psi/iv` | 无月份、批次、修订、发布状态、排序性、样本量、CSI、分箱结构 | 可继续保存运行聚合;不建议单独承担业务监控全量数据 |
| `model_monitor_call_log` | 模型调用原始日志 | 调用成功、耗时、节点、错误 | 属于服务运行监控,不是模型效果监控 | 不作为 A 卡月度监控来源 |
| `model_monitor_aggr_cursor` | 聚合器游标 | 聚合进度 | 纯技术状态 | 我方无需读取 |
## 3. 建议模型方提供的 4 张表
### 3.1 `model_monitor_batch`——监控批次
一行代表一次月度监控数据发布。
建议字段:
- `id BIGINT`:批次主键。
- `source_batch_no VARCHAR(128)`:模型方幂等批次号。
- `monitor_month DATE`:监控月份,固定当月1日。
- `revision_no INT`:同月份修订号。
- `status VARCHAR(24)``writing/published/failed/superseded`
- `expected_model_count/written_model_count`:预期和实际模型数。
- `feature_row_count/distribution_row_count`:特征和分箱行数。
- `checksum VARCHAR(64)`:批次校验和。
- `generated_at/published_at DATETIME`:计算和发布时间。
- `failed_reason VARCHAR(2000)`:失败原因。
关键约束:同一监控月份和修订号唯一;我方只读取 `published` 的最高修订。
### 3.2 `model_monitor_result`——模型单月监控结果
一行代表一个模型版本在一个月份的核心监控结果。
建议字段:
- `id BIGINT`:结果主键。
- `batch_id BIGINT`:所属批次。
- `deploy_id BIGINT`:关联 `model_deploy.id`
- `version_id BIGINT`:关联 `model_version.id`
- `monitor_month DATE`:监控月份。
- `ranking_result VARCHAR(24)``matched/unmatched/not_applicable`
- `ks_value DECIMAL(12,8)`KS,建议统一存01比率。
- `psi_value DECIMAL(12,8)`PSI,建议统一存01比率。
- `sample_count/good_count/bad_count BIGINT`:样本量。
- `calculated_at DATETIME`:完成计算时间。
- `extra JSON`:仅用于尚未结构化且已约定的数据,不作为核心字段替代品。
关键约束:同一批次下一个 `deploy_id` 只能有一条结果。
### 3.3 `model_monitor_feature_metric`——特征级指标
一行代表一个监控结果下的一个特征。
建议字段:
- `id BIGINT`:主键。
- `monitor_result_id BIGINT`:关联单月监控结果。
- `feature_code/feature_name`:特征编码和名称。
- `iv_value/previous_iv_value`:当期与上期IV。
- `iv_drop_rate`IV降幅。
- `csi_value/previous_csi_value`:当期与上期CSI。
- `csi_rise_rate`CSI升幅。
- `ks_contribution_change`:对KS变化的贡献。
- `psi_contribution_change`:对PSI变化的贡献。
- `calculated_at DATETIME`:计算时间。
关键约束:同一监控结果下 `feature_code` 唯一。
### 3.4 `model_monitor_distribution`——评分及特征分箱
一行代表一个评分区间或一个特征分箱。
建议字段:
- `id BIGINT`:主键。
- `monitor_result_id BIGINT`:关联单月监控结果。
- `dimension_type VARCHAR(24)``score_band/feature_bin`
- `feature_code/feature_name`:评分分箱时可为空。
- `bin_order/bin_code/bin_label`:分箱顺序、编码和显示名称。
- `reference_period_label`:基准期说明。
- `reference_count/reference_share`:基准期数量和占比。
- `current_count/current_share`:当期数量和占比。
- `good_count/bad_count/bad_rate`:好坏客户数据。
- `psi_component/csi_component`:该分箱对PSI/CSI的贡献。
关键约束:`monitor_result_id + dimension_type + feature_code + bin_order` 唯一。
## 4. 四张表与前端页面关系
| 表 | 模型大类概览 | 监控概览 | 监控详情 | 监控/诊断报告 | 系统同步 |
|---|---|---|---|---|---|
| `model_monitor_batch` | 间接 | 间接 | 间接 | 绑定数据版本 | 展示批次和修订状态 |
| `model_monitor_result` | KS/PSI、趋势、同业均值 | 月度明细和等级输入 | 核心结论与趋势 | 报告核心指标 | 同步主数据 |
| `model_monitor_feature_metric` | 不直接使用 | 可选摘要 | IV/CSI前5及全指标 | 诊断报告特征分析 | 同步特征指标 |
| `model_monitor_distribution` | 不直接使用 | 不直接使用 | 排序性和特征分布 | 报告四张明细表 | 同步分箱数据 |
## 5. 我方可以计算、模型方无需提供的内容
取得上述原始数据后,我方可自行计算:
- 本行平均KS/PSI及同业平均值。
- 近6个月趋势。
- KS环比降幅。
- 近6个月二级异常次数。
- 异常等级及A/B/C监控结果等级。
- 命中原因、处理建议、阈值影响测算。
- 报告正文、待办、催办和缓存数据。
## 6. 必须与模型方确认的 10 项
1. 是否接受新增上述4张表,或提供完全等价的表/视图。
2. `deploy_id`和版本引用使用 `model_version.id` 还是 `version`整数。
3. KS、PSI、IV、CSI统一存01还是0100。
4. 排序性计算方法及枚举值。
5. PSI和CSI基准期规则,是否为滚动基准期。
6. 批次粒度是全月全量、按银行还是按模型。
7. 历史勘误是否新增修订,禁止原地覆盖已发布结果。
8. 特征编码是否跨版本稳定,特征名称变更如何留痕。
9. 首次需要回补多少个月历史数据及预计数据量。
10. 提供一套真实脱敏样例:一个模型、一个月份、完整特征和分箱数据。
## 7. 若模型方不愿新增4张表
最小折中方案:
- `model_monitor_aggr`继续承载KS、PSI、IV、CSI等标量指标。
- 必须固定 `metricName` 字典、单位、月份口径和 `dimensionValue` 规则。
- 仍需新增批次/发布结构以及评分和特征分箱明细结构。
- 不建议把所有数据都塞入 `extra JSON`,否则无法建立稳定索引、约束和验收口径。
该折中方案开发和长期维护成本更高,推荐优先采用4张结构化表。
@@ -0,0 +1,123 @@
# A 卡运维平台功能、页面与表关系 V0.1
> 日期:2026-09-01
> 依据:对外原型 V1.10、三库边界确认、`model_platform` 18 张表、`model_deploy` 16 张表及银行表待补结论。
> 状态:分析稿;用于共同确认 `model_operations` V0.2,暂不执行删表或改表。
## 1. 当前结论
最终系统由三个数据库组成:
- `model_platform`:模型开发平台,提供统一登录、角色、项目空间、脚本、调度和对象存储。
- `model_deploy`:模型部署平台,提供模型、版本、部署详情、运行节点和聚合监控指标;银行表及模型—银行关系由模型方补充。
- `model_operations`:我方运维业务库,承载判级、处理、报告、流程、文档、配置和操作统计。
已确认从我方库删除重复主数据表:
- `ops_banks`
- `ops_model_instances`
- `ops_model_versions`
删除后,我方目标表为 **21 张**。其中 16 张归属明确,5 张仍需共同决定是否保留为“标准化快照/读模型”。
## 2. 三库关联原则
- 不建立跨数据库物理外键。
- 我方内部主键继续使用 `CHAR(26)` ULID。
- 开发平台引用使用 `platform_user_id/platform_workspace_id CHAR(26)`
- 部署平台引用使用 `deploy_id/deploy_version_id/bank_id BIGINT`,最终类型以模型方银行表为准。
- 报告、判级和流程必须保存生成时快照,不能因为模型方后续修改名称或版本而改变历史。
- 前端不直接访问数据库,全部通过我方后端聚合三个库的数据。
## 3. 前端页面与功能、表关系
对外原型 V1.10 当前共有 **13 个前端页面**,页面与数据关系如下。
| 前端页面 | 主要功能 | 我方表(model_operations | 外部只读表 | 主要写入动作 |
|---|---|---|---|---|
| 我的工作台 | KPI、待办、关注、催办、最近动态 | `ops_monitor_evaluations``ops_monitor_reviews``ops_reports``ops_workflows``ops_workflow_stages``ops_usage_events` | `model_deploy`、银行表;用户显示名来自 `model_platform.users` | 页面本身只读;操作跳转到处理、报告或流程接口 |
| 平台使用统计 | 登录、需求、报告阅读和下载统计 | `ops_usage_events` | `model_platform.users/roles` | 系统自动记录行为事件 |
| 模型大类概览 | 大类卡片、银行筛选、本行/同业 KS/PSI、趋势、等级下钻 | `ops_model_categories``ops_monitor_results``ops_monitor_evaluations` | `model_deploy.model_deploy``model_version`、模型方银行及关系表 | 只读聚合 |
| 已上线模型详情 | 模型版本、开发时点指标、生命周期、评分逻辑、材料入口 | `ops_documents`;必要时读取标准化监控快照 | `model_deploy``model_version``model_details`、银行及关系表;文件来自 `model_platform.storage_objects` | 上传评分逻辑或材料时写 `ops_documents` |
| 模型监控概览 | 月度明细、多维筛选、异常等级和监控结果等级 | `ops_monitor_results``ops_monitor_evaluations``ops_monitor_reviews` | 模型、版本、银行名称来自 `model_deploy` | 只读;处理动作进入复核表 |
| 模型监控详情 | 排序性、KS/PSI、IV/CSI、分布、命中规则、两段处理 | `ops_monitor_results``ops_monitor_feature_metrics``ops_monitor_distributions``ops_monitor_evaluations``ops_monitor_reviews``ops_reports` | `model_deploy``model_version``model_monitor_aggr` | 模型团队初审、业务团队终审写 `ops_monitor_reviews` |
| 监控 / 诊断报告 | 自动生成、模型团队编辑、发送业务团队、导出 | `ops_reports``ops_report_revisions``ops_report_template_versions``ops_prompt_versions``ops_monitor_evaluations` | 模型、版本、银行信息来自 `model_deploy` | 系统建报告;模型团队新增修订并发送;写 Outbox |
| 报告汇总 | 历史报告查询、阅读状态、筛选和导出 | `ops_reports``ops_report_revisions` | 模型、版本、银行信息来自 `model_deploy` | 阅读/下载写 `ops_usage_events` |
| 全流程进度 | 需求发起、七阶段流转、预计日期、复用通用模型、催办 | `ops_workflows``ops_workflow_stages``ops_documents``ops_outbox_events` | 银行、模型、版本来自 `model_deploy`;用户来自 `model_platform` | 业务发起、模型推进、业务确认均写流程表 |
| 文档知识库 | 按银行、模型、版本、环节检索流程材料 | `ops_documents``ops_workflows``ops_workflow_stages` | 文件元数据来自 `model_platform.storage_objects`;模型信息来自 `model_deploy` | 上传/确认材料写 `ops_documents` |
| 监控等级规则 | 13 行规则矩阵、版本发布、回滚、阈值影响测算 | `ops_rule_versions``ops_rule_items``ops_monitor_evaluations``ops_monitor_results` | 模型与银行信息来自 `model_deploy` | 管理员发布规则;系统按新版本生成判级快照 |
| 报告 Prompt 管理 | Prompt 编辑、版本、发布和历史样本回归 | `ops_prompt_versions``ops_prompt_regression_runs``ops_report_template_versions` | 历史监控样本来自部署/运维数据 | 模型团队维护 Prompt;系统写回归结果 |
| 系统配置 | 报告频率、输出日期、模板版本、通知、手动重算 | `ops_bank_report_configs``ops_report_template_versions``ops_outbox_events` | 银行列表来自模型方银行表;角色权限来自 `model_platform` | 管理员修改配置并触发后台事件 |
## 4. 我方 21 张表逐表归属
### 4.1 数据接入与标准化(5 张,均需定案)
| 表 | 作用 | 写入方 | 主要页面 | 当前判断 |
|---|---|---|---|---|
| `ops_model_categories` | 标准A卡/白户A卡/大额A卡/反欺诈评分业务分类 | 我方管理员或种子 | 模型大类概览、规则、流程 | 暂留;若模型方补业务大类字段,可改为映射表 |
| `ops_monitor_batches` | 月度数据批次、修订号和发布状态 | 同步任务 | 监控、报告、系统配置 | 建议保留,模型方当前无批次/修订/发布结构 |
| `ops_monitor_results` | 月度排序性、KS、PSI及样本快照 | 同步任务 | 大类、监控、报告 | 建议保留为标准化月度快照,不由模型团队手工写 |
| `ops_monitor_feature_metrics` | 特征级 IV/CSI及变化 | 同步任务 | 监控详情、诊断报告 | 建议保留;`model_monitor_aggr` 只有通用 metricName,结构不足 |
| `ops_monitor_distributions` | 评分区间和特征分箱分布 | 同步任务 | 监控详情、诊断报告 | 建议保留;模型方当前 SQL 无对应结构 |
### 4.2 判级与处理(4 张,归属明确)
| 表 | 写入方 | 主要页面 |
|---|---|---|
| `ops_rule_versions` | 管理员 | 监控等级规则 |
| `ops_rule_items` | 管理员 | 监控等级规则 |
| `ops_monitor_evaluations` | 判级服务 | 工作台、概览、详情、报告 |
| `ops_monitor_reviews` | 模型团队初审、业务团队终审 | 工作台、监控详情 |
### 4.3 报告与 AI 治理(6 张,归属明确)
| 表 | 写入方 | 主要页面 |
|---|---|---|
| `ops_report_template_versions` | 管理员 | 报告、系统配置 |
| `ops_prompt_versions` | 模型团队/管理员 | Prompt 管理、报告 |
| `ops_prompt_regression_runs` | 回归任务 | Prompt 管理 |
| `ops_reports` | 报告生成服务、模型团队发送 | 报告、报告汇总、工作台 |
| `ops_report_revisions` | 报告生成服务、模型团队编辑 | 报告、报告汇总 |
| `ops_bank_report_configs` | 管理员 | 系统配置 |
### 4.4 流程、材料与使用统计(4 张,归属明确)
| 表 | 写入方 | 主要页面 |
|---|---|---|
| `ops_workflows` | 业务团队发起、流程服务维护 | 全流程进度、工作台 |
| `ops_workflow_stages` | 模型团队/业务团队操作,流程服务落库 | 全流程进度、工作台 |
| `ops_documents` | 模型团队/业务团队上传,文档服务落库 | 流程、知识库、模型详情 |
| `ops_usage_events` | 系统自动记录 | 平台使用统计、报告汇总 |
### 4.5 异步可靠性(2 张,归属明确)
| 表 | 写入方 | 主要用途 |
|---|---|---|
| `ops_outbox_events` | 与业务操作同事务写入 | 判级、报告、提醒、超时、流程通知 |
| `ops_consumer_inbox` | 后台消费者 | 防止任务重复执行 |
## 5. 关键写入责任
- 模型方只写 `model_deploy`,不直接写我方判级、报告和流程表。
- 我方同步任务从 `model_deploy` 读取数据,规范化后写入 4 张 `ops_monitor_*` 快照表。
- 业务团队和模型团队不直接执行 SQL;其页面操作通过后端服务写入复核、报告修订、流程阶段和文档表。
- 管理员通过页面维护规则、模板、Prompt 和按银行配置。
- 后台任务通过 Outbox/Inbox 处理判级、报告生成、催办和超时默认流转。
## 6. 需要共同定案的 5 项
1. **统一身份主源**:前端登录继续以 `model_platform.users/roles/workspaces` 为准,还是改用 `model_deploy.sys_user/sys_project_space`。建议继续使用现有 `model_platform` 登录。
2. **模型大类来源**:模型方是否在 `model_deploy` 增加标准A/白户A/大额A/反欺诈业务分类;若不增加,我方保留 `ops_model_categories` 与部署模型的映射。
3. **监控快照保留**:是否接受我方把 `model_monitor_aggr` 转换为稳定的月度结果、特征和分布快照。建议保留,避免历史报告随源数据变化。
4. **银行关系结构**:模型方补充一对多还是多对多关系。通用模型存在多银行复用,建议 `bank + model_deploy_bank` 两张表。
5. **缺失监控字段**:排序性、CSI、特征分箱、基准期/当期分布、样本量和批次修订由模型方新增结构还是通过固定 `extra JSON` 提供。建议明确字段表,不依赖无约束 JSON。
## 7. 建议的下一步
先不要再次执行 V0.1 完整建表脚本。按本文件共同确认上述 5 项后:
1. 输出 `model_operations` V0.2 正式表清单。
2. 生成 V0.1 → V0.2 的非破坏性迁移 SQL。
3. 给模型方一份只读表、字段和样例数据需求清单。
4. 再生成 ORM 与首条“模型列表—详情—单月监控结果”真实接口。
@@ -0,0 +1,46 @@
# A 卡运维数据库 DBA 授权申请 V0.1
> 目标:现有 `model_platform` 保持不变,新建 `model_operations`。以下为权限范围说明,不包含真实账号名和口令。
## 1. 运维后端
- 现有 `DATABASE_URL`:沿用平台后端连接,用于认证、工作空间和存储;运维代码只查询必要字段。
- 新增 `OPERATIONS_DATABASE_URL`:连接 `model_operations`,读写运维业务表。
- 不申请对 `model_platform` 新增、修改或删除表结构。
建议平台库最小读取范围:
| 表 | 字段/用途 |
|---|---|
| `users` | `user_id/display_name/status/platform_role_id/is_deleted`,明确排除 `password_hash` |
| `roles` | `role_id/role_code/role_name/role_scope/is_deleted` |
| `workspaces` | `workspace_id/workspace_code/workspace_name/status/is_deleted` |
| `workspace_members` | `workspace_id/user_id/role_id/member_status/is_deleted` |
| `storage_objects` | 文件元数据;优先通过现有存储服务访问 |
| `versions/scripts` | 可选关联开发制品,不作为业务模型主数据 |
| `schedules/schedule_runs` | 可选关联模型计算任务,只读运行状态 |
## 2. 模型数据写入方
建议为模型任务使用独立 `MODEL_WRITER_DATABASE_URL`
- `SELECT``ops_model_categories` 和 7 张模型源表。
- `INSERT/UPDATE``ops_banks``ops_model_instances``ops_model_versions``ops_monitor_batches``ops_monitor_results``ops_monitor_feature_metrics``ops_monitor_distributions`
- `INSERT``ops_outbox_events`,事件类型限定为 `monitor_batch.published`
- 不授予 `DELETE`,不授予 15 张运维业务表写权限。
已发布批次后的源结果不可修改,该限制还需由写入服务校验;MySQL 表级授权本身无法按行状态限制 `UPDATE`
## 3. 架构迁移账号
- 仅在发布窗口使用,可对 `model_operations` 执行 DDL 和种子数据脚本。
- 不作为应用运行账号保存到服务配置。
- 不授予对 `model_platform` 的 DDL 权限。
## 4. 审核项
- MySQL 版本需为 8.0,字符集 `utf8mb4`,排序规则 `utf8mb4_0900_ai_ci`
- 确认新库备份、恢复、容量告警和慢查询策略。
- 确认两套连接池上限,避免同一 FastAPI 进程挤满数据库连接。
- 确认 Schedule Executor 何时增加 `model_operations` Outbox 轮询连接。
+26
View File
@@ -0,0 +1,26 @@
# 数据库设计变更记录
## **当前最新版本:V0.1**
### 功能页面表关系 V0.1 · 2026-09-01
- 按三库边界梳理对外原型 V1.10 的 13 个页面、21 张我方目标表及外部只读依赖。
- 明确 16 张运维业务表归属,标出 5 张数据接入/标准化表仍需共同定案。
- 记录页面级读写关系、人工/系统写入责任以及五项 P0 架构决策。
- 当前仅为分析稿,不执行删表、迁移或正式数据写入。
### 原始监控指标模型方表评估清单 V0.1 · 2026-09-01
- 核查 `model_deploy` 现有6张相关表,确认 `model_monitor_aggr` 只能部分提供KS/PSI/IV标量指标。
- 建议模型方新增或提供等价的监控批次、单月结果、特征指标、分箱分布4类结构化表。
- 列出每张表的必要字段、唯一约束、前端用途及10项评估问题。
- 明确本行/同业均值、趋势、判级和报告等衍生结果由我方计算,无需模型方提供。
### V0.1 · 2026-08-31
- 根据现有 `model_platform` 18 张表结构,确认仅复用身份、工作空间、对象存储、脚本版本和调度数据。
- 新增独立 `model_operations` 库,共 24 张表和 1 个当前监控结果视图。
- 模型平台受控写入 7 张源表;判级、复核、报告、流程等 15 张业务表归运维模块独占。
- 新库自带 Outbox/Inbox,不写现有平台事件表;跨库标识只做逻辑引用,不建物理外键。
- SQL 全部采用非破坏式建库建表,并按模块拆分为单文件不超过 500 行。
- 新增 `model_operations-完整建表-V0.1.sql`,只合并新库 24 张表,可在 DBeaver/MySQL 客户端中一次性复制执行;不包含建库、视图或种子数据。
+158
View File
@@ -0,0 +1,158 @@
"""Build the single-file model_operations schema from versioned modules."""
from __future__ import annotations
from pathlib import Path
BASE = Path(__file__).parent
SOURCE_DIR = BASE / "model_operations-V0.1"
OUTPUT = BASE / "model_operations-完整建表-V0.1.sql"
SOURCES = [
"10_reference_and_models.sql",
"20_monitoring_source.sql",
"30_governance_and_reviews.sql",
"40_reports_and_prompts.sql",
"50_workflows_documents_usage.sql",
]
def statements(text: str) -> list[str]:
"""Split SQL safely enough for quotes/backticks and strip line comments."""
output: list[str] = []
current: list[str] = []
quote: str | None = None
line_comment = False
index = 0
while index < len(text):
char = text[index]
next_char = text[index + 1] if index + 1 < len(text) else ""
if line_comment:
if char == "\n":
line_comment = False
current.append(" ")
index += 1
continue
if quote is None and char == "-" and next_char == "-":
line_comment = True
index += 2
continue
if quote:
current.append(char)
if char == quote:
if quote == "'" and next_char == "'":
current.append(next_char)
index += 2
continue
quote = None
index += 1
continue
if char in ("'", "`"):
quote = char
current.append(char)
elif char == ";":
statement = " ".join("".join(current).split())
if statement:
output.append(statement + ";")
current = []
else:
current.append(char)
index += 1
tail = " ".join("".join(current).split())
if tail:
output.append(tail + ";")
return output
def format_create_table(statement: str) -> str:
"""Format one CREATE TABLE compactly while keeping DBeaver-friendly lines."""
open_at = statement.find("(")
if open_at < 0:
return statement
quote: str | None = None
depth = 0
close_at = -1
for index in range(open_at, len(statement)):
char = statement[index]
next_char = statement[index + 1] if index + 1 < len(statement) else ""
if quote:
if char == quote:
if quote == "'" and next_char == "'":
continue
quote = None
continue
if char in ("'", "`"):
quote = char
elif char == "(":
depth += 1
elif char == ")":
depth -= 1
if depth == 0:
close_at = index
break
if close_at < 0:
return statement
body = statement[open_at + 1 : close_at]
parts: list[str] = []
current: list[str] = []
quote = None
depth = 0
for index, char in enumerate(body):
next_char = body[index + 1] if index + 1 < len(body) else ""
if quote:
current.append(char)
if char == quote:
if quote == "'" and next_char == "'":
continue
quote = None
continue
if char in ("'", "`"):
quote = char
current.append(char)
elif char == "(":
depth += 1
current.append(char)
elif char == ")":
depth -= 1
current.append(char)
elif char == "," and depth == 0:
parts.append(" ".join("".join(current).split()))
current = []
else:
current.append(char)
tail_part = " ".join("".join(current).split())
if tail_part:
parts.append(tail_part)
lines = [statement[:open_at].rstrip() + " ("]
for index in range(0, len(parts), 2):
chunk = ", ".join(parts[index : index + 2])
if index + 2 < len(parts):
chunk += ","
lines.append(" " + chunk)
lines.append(") " + statement[close_at + 1 :].strip())
return "\n".join(lines)
def main() -> None:
bundled: list[str] = [
"-- A 卡模型智能运维平台 · model_operations 完整建表 V0.1",
"-- 内容:仅包含新库的 24 张表,不含建库、视图、种子数据或 model_platform 现有表。",
"-- 执行前:请在 DBeaver 中选中 model_operations 作为当前活动数据库。",
"-- DBeaver:请使用“执行 SQL 脚本”(Alt/Option + X),不要选中全文后按 Ctrl/Cmd + Enter。",
"-- 如使用“执行 SQL 语句”,每次只能选中一条 CREATE TABLE 单独执行。",
"",
]
for source_name in SOURCES:
source_statements = statements((SOURCE_DIR / source_name).read_text(encoding="utf-8"))
bundled.append(f"-- ===== {source_name} =====")
for statement in source_statements:
if not statement.lower().startswith("create table "):
continue
bundled.append(format_create_table(statement))
bundled.append("")
OUTPUT.write_text("\n".join(bundled).rstrip() + "\n", encoding="utf-8")
print(f"built {OUTPUT.name}: 24 tables")
if __name__ == "__main__":
main()
@@ -0,0 +1,9 @@
-- A 卡模型智能运维平台独立数据库 V0.1
-- 安全特性:只创建不存在的数据库,不删除、不覆盖现有 model_platform。
CREATE DATABASE IF NOT EXISTS model_operations
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
USE model_operations;
@@ -0,0 +1,127 @@
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_model_categories (
category_code VARCHAR(32) NOT NULL COMMENT 'std/bai/big/afd',
category_name VARCHAR(100) NOT NULL,
sort_order SMALLINT NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/inactive',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (category_code),
KEY idx_ops_categories_status (status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='固定模型大类字典,由运维模块维护';
CREATE TABLE IF NOT EXISTS ops_banks (
bank_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
bank_code VARCHAR(64) NOT NULL,
bank_name VARCHAR(150) NOT NULL,
is_wuji_bank TINYINT(1) NOT NULL DEFAULT 0,
bank_status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/inactive',
source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform',
source_bank_ref VARCHAR(128) NULL,
source_updated_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (bank_id),
UNIQUE KEY uk_ops_banks_code (workspace_id, bank_code),
UNIQUE KEY uk_ops_banks_source (
workspace_id, source_system, source_bank_ref
),
KEY idx_ops_banks_workspace_status (
workspace_id, bank_status, is_deleted
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='银行主数据;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_model_instances (
model_instance_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks',
category_code VARCHAR(32) NOT NULL COMMENT '逻辑引用 ops_model_categories',
model_id VARCHAR(128) NOT NULL COMMENT '工作空间内全局唯一业务模型ID',
model_name VARCHAR(200) NOT NULL,
model_status VARCHAR(24) NOT NULL DEFAULT 'normal'
COMMENT 'normal/escort/escort_finished/offline',
current_version_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_versions',
is_common_model TINYINT(1) NOT NULL DEFAULT 0,
common_source_model_id CHAR(26) NULL
COMMENT '复用时逻辑引用通用模型实例',
common_model_name VARCHAR(200) NULL,
source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform',
source_model_ref VARCHAR(128) NULL,
source_updated_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (model_instance_id),
UNIQUE KEY uk_ops_models_business_id (workspace_id, model_id),
UNIQUE KEY uk_ops_models_source (
workspace_id, source_system, source_model_ref
),
KEY fk_ops_models_bank (bank_id),
KEY fk_ops_models_current_version (current_version_id),
KEY fk_ops_models_common_source (common_source_model_id),
KEY idx_ops_models_filters (
workspace_id, category_code, model_status, is_deleted
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='业务模型实例;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_model_versions (
model_version_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
model_instance_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_instances',
version_label VARCHAR(64) NOT NULL COMMENT '例如 v2.3',
version_status VARCHAR(24) NOT NULL DEFAULT 'active'
COMMENT 'draft/escort/active/offline',
platform_versions_id CHAR(26) NULL
COMMENT '可选逻辑引用 model_platform.versions',
developer_user_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
developer_display_name VARCHAR(100) NULL COMMENT '历史展示快照',
development_date DATE NULL,
iteration_start_date DATE NULL,
last_iteration_date DATE NULL,
iteration_reason VARCHAR(1000) NULL,
escort_start_date DATE NULL,
escort_end_date DATE NULL,
online_date DATE NULL,
offline_date DATE NULL,
development_ks DECIMAL(12,8) NULL COMMENT '0-1比率',
development_psi DECIMAL(12,8) NULL COMMENT '0-1比率',
max_lift DECIMAL(12,8) NULL,
scoring_logic_storage_object_id CHAR(26) NULL
COMMENT '逻辑引用 model_platform.storage_objects',
source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform',
source_version_ref VARCHAR(128) NULL,
source_updated_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (model_version_id),
UNIQUE KEY uk_ops_model_versions_label (
model_instance_id, version_label
),
UNIQUE KEY uk_ops_model_versions_source (
workspace_id, source_system, source_version_ref
),
KEY fk_ops_model_versions_model (model_instance_id),
KEY fk_ops_model_versions_platform_version (platform_versions_id),
KEY fk_ops_model_versions_developer (developer_user_id),
KEY idx_ops_model_versions_lifecycle (
workspace_id, version_status, online_date, offline_date
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='模型版本与生命周期;模型平台可受控直写';
@@ -0,0 +1,160 @@
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_monitor_batches (
batch_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform',
source_batch_no VARCHAR(128) NOT NULL COMMENT '来源方幂等批次号',
monitor_month DATE NOT NULL COMMENT '固定存当月1日',
revision_no INT NOT NULL DEFAULT 1,
supersedes_batch_id CHAR(26) NULL COMMENT '逻辑引用上一修订批次',
batch_status VARCHAR(24) NOT NULL DEFAULT 'writing'
COMMENT 'writing/published/failed/superseded',
expected_model_count INT NOT NULL DEFAULT 0,
written_model_count INT NOT NULL DEFAULT 0,
feature_row_count BIGINT NOT NULL DEFAULT 0,
distribution_row_count BIGINT NOT NULL DEFAULT 0,
checksum_sha256 CHAR(64) NULL,
generated_at DATETIME(3) NULL,
published_at DATETIME(3) NULL,
published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
failed_reason VARCHAR(2000) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (batch_id),
UNIQUE KEY uk_ops_monitor_batches_source_no (
workspace_id, source_system, source_batch_no
),
UNIQUE KEY uk_ops_monitor_batches_revision (
workspace_id, source_system, monitor_month, revision_no
),
KEY fk_ops_monitor_batches_supersedes (supersedes_batch_id),
KEY idx_ops_monitor_batches_publish (
workspace_id, batch_status, monitor_month, revision_no
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='月度监控写入批次与发布门闩;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_monitor_results (
monitor_result_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
batch_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_batches',
model_instance_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_instances',
model_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_versions',
monitor_month DATE NOT NULL COMMENT '固定存当月1日',
source_result_ref VARCHAR(128) NULL,
ranking_result VARCHAR(24) NULL
COMMENT 'matched/unmatched/not_applicable',
ks_value DECIMAL(12,8) NULL COMMENT '0-1比率',
psi_value DECIMAL(12,8) NULL COMMENT '0-1比率',
sample_count BIGINT NULL,
good_count BIGINT NULL,
bad_count BIGINT NULL,
source_result_json JSON NULL COMMENT '尚未结构化的可追溯源字段',
source_calculated_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (monitor_result_id),
UNIQUE KEY uk_ops_monitor_results_batch_model (
batch_id, model_instance_id
),
KEY fk_ops_monitor_results_batch (batch_id),
KEY fk_ops_monitor_results_model (model_instance_id),
KEY fk_ops_monitor_results_version (model_version_id),
KEY idx_ops_monitor_results_month (
workspace_id, monitor_month, model_instance_id
),
CONSTRAINT chk_ops_monitor_results_ks
CHECK (ks_value IS NULL OR (ks_value >= 0 AND ks_value <= 1)),
CONSTRAINT chk_ops_monitor_results_psi
CHECK (psi_value IS NULL OR psi_value >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='单模型单月原始监控结果;发布后不可更新';
CREATE TABLE IF NOT EXISTS ops_monitor_feature_metrics (
feature_metric_id CHAR(26) NOT NULL,
monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results',
feature_code VARCHAR(128) NOT NULL,
feature_name VARCHAR(200) NOT NULL,
iv_value DECIMAL(12,8) NULL,
previous_iv_value DECIMAL(12,8) NULL,
iv_drop_rate DECIMAL(12,8) NULL COMMENT '0-1比率',
csi_value DECIMAL(12,8) NULL,
previous_csi_value DECIMAL(12,8) NULL,
csi_rise_rate DECIMAL(12,8) NULL COMMENT '0-1比率',
ks_contribution_change DECIMAL(12,8) NULL,
psi_contribution_change DECIMAL(12,8) NULL,
source_metric_json JSON NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (feature_metric_id),
UNIQUE KEY uk_ops_feature_metrics_result_feature (
monitor_result_id, feature_code
),
KEY fk_ops_feature_metrics_result (monitor_result_id),
KEY idx_ops_feature_metrics_iv_drop (monitor_result_id, iv_drop_rate),
KEY idx_ops_feature_metrics_csi_rise (monitor_result_id, csi_rise_rate)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='单月特征级 IV/CSI 与贡献变化;发布后不可更新';
CREATE TABLE IF NOT EXISTS ops_monitor_distributions (
distribution_id CHAR(26) NOT NULL,
monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results',
dimension_type VARCHAR(24) NOT NULL COMMENT 'score_band/feature_bin',
feature_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '评分分箱时为空串',
feature_name VARCHAR(200) NULL,
bin_order INT NOT NULL,
bin_code VARCHAR(128) NOT NULL,
bin_label VARCHAR(255) NOT NULL,
reference_period_label VARCHAR(64) NULL,
reference_count BIGINT NULL,
reference_share DECIMAL(12,8) NULL COMMENT '0-1比率',
current_count BIGINT NULL,
current_share DECIMAL(12,8) NULL COMMENT '0-1比率',
good_count BIGINT NULL,
bad_count BIGINT NULL,
bad_rate DECIMAL(12,8) NULL COMMENT '0-1比率',
psi_component DECIMAL(12,8) NULL,
csi_component DECIMAL(12,8) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (distribution_id),
UNIQUE KEY uk_ops_distributions_bin (
monitor_result_id, dimension_type, feature_code, bin_order
),
KEY fk_ops_distributions_result (monitor_result_id),
KEY idx_ops_distributions_feature (
monitor_result_id, feature_code, bin_order
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='排序性评分分箱及特征分布;发布后不可更新';
CREATE OR REPLACE VIEW v_ops_current_monitor_results AS
SELECT result_row.*
FROM ops_monitor_results AS result_row
JOIN ops_monitor_batches AS batch_row
ON batch_row.batch_id = result_row.batch_id
JOIN (
SELECT
workspace_id,
source_system,
monitor_month,
MAX(revision_no) AS revision_no
FROM ops_monitor_batches
WHERE batch_status = 'published' AND is_deleted = 0
GROUP BY workspace_id, source_system, monitor_month
) AS latest
ON latest.workspace_id = batch_row.workspace_id
AND latest.source_system = batch_row.source_system
AND latest.monitor_month = batch_row.monitor_month
AND latest.revision_no = batch_row.revision_no
WHERE result_row.is_deleted = 0
AND batch_row.batch_status = 'published'
AND batch_row.is_deleted = 0;
@@ -0,0 +1,177 @@
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_rule_versions (
rule_version_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
category_code VARCHAR(32) NOT NULL DEFAULT '*'
COMMENT '* 表示全部模型大类',
version_label VARCHAR(64) NOT NULL,
rule_status VARCHAR(24) NOT NULL DEFAULT 'draft'
COMMENT 'draft/published/retired',
threshold_json JSON NOT NULL COMMENT 'KS/PSI/环比切点快照',
supersedes_rule_version_id CHAR(26) NULL,
change_note VARCHAR(1000) NULL,
created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
published_at DATETIME(3) NULL,
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (rule_version_id),
UNIQUE KEY uk_ops_rule_versions_label (
workspace_id, category_code, version_label
),
KEY fk_ops_rule_versions_supersedes (supersedes_rule_version_id),
KEY idx_ops_rule_versions_status (
workspace_id, category_code, rule_status, published_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='监控判级规则版本';
CREATE TABLE IF NOT EXISTS ops_rule_items (
rule_item_id CHAR(26) NOT NULL,
rule_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_versions',
sort_order SMALLINT NOT NULL,
ranking_result VARCHAR(24) NOT NULL COMMENT 'matched/unmatched',
ks_band_code VARCHAR(32) NOT NULL,
psi_band_code VARCHAR(32) NOT NULL,
ks_drop_band_code VARCHAR(32) NOT NULL,
conditions_json JSON NULL COMMENT '用于跨档或扩展条件',
abnormal_level VARCHAR(16) NOT NULL
COMMENT 'normal/level1/level2/level3',
monitor_grade CHAR(1) NOT NULL COMMENT 'A/B/C',
secondary_upgrade_threshold SMALLINT NULL
COMMENT '近6月二级异常累计升级阈值',
reason_code VARCHAR(64) NOT NULL,
reason_template VARCHAR(1000) NOT NULL,
action_text VARCHAR(1000) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (rule_item_id),
UNIQUE KEY uk_ops_rule_items_order (rule_version_id, sort_order),
UNIQUE KEY uk_ops_rule_items_reason (rule_version_id, reason_code),
KEY fk_ops_rule_items_version (rule_version_id),
CONSTRAINT chk_ops_rule_items_grade
CHECK (monitor_grade IN ('A', 'B', 'C'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='规则版本下的判级矩阵行';
CREATE TABLE IF NOT EXISTS ops_monitor_evaluations (
evaluation_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results',
rule_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_versions',
rule_item_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_items',
ks_mom_drop_rate DECIMAL(12,8) NULL COMMENT '0-1比率',
secondary_level2_hits_6m SMALLINT NOT NULL DEFAULT 0,
abnormal_level VARCHAR(16) NOT NULL
COMMENT 'normal/level1/level2/level3',
monitor_grade CHAR(1) NOT NULL COMMENT 'A/B/C',
reason_code VARCHAR(64) NOT NULL,
reason_text_snapshot VARCHAR(2000) NOT NULL,
action_snapshot VARCHAR(2000) NOT NULL,
is_current TINYINT(1) NOT NULL DEFAULT 1,
evaluated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
current_monitor_result_id CHAR(26)
GENERATED ALWAYS AS (
CASE
WHEN is_current = 1 AND is_deleted = 0 THEN monitor_result_id
ELSE NULL
END
) VIRTUAL,
PRIMARY KEY (evaluation_id),
UNIQUE KEY uk_ops_evaluations_result_rule (
monitor_result_id, rule_version_id
),
UNIQUE KEY uk_ops_evaluations_current (current_monitor_result_id),
KEY fk_ops_evaluations_result (monitor_result_id),
KEY fk_ops_evaluations_rule_version (rule_version_id),
KEY fk_ops_evaluations_rule_item (rule_item_id),
KEY idx_ops_evaluations_grade (
workspace_id, monitor_grade, abnormal_level, evaluated_at
),
CONSTRAINT chk_ops_evaluations_grade
CHECK (monitor_grade IN ('A', 'B', 'C'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='按规则版本生成的不可变监控判级快照';
CREATE TABLE IF NOT EXISTS ops_monitor_reviews (
review_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results',
evaluation_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_evaluations',
review_stage VARCHAR(24) NOT NULL
COMMENT 'model_initial/business_final',
review_status VARCHAR(24) NOT NULL DEFAULT 'pending'
COMMENT 'pending/handled/auto_closed',
decision_code VARCHAR(32) NULL COMMENT 'no_action/tune_or_rebuild',
handling_note VARCHAR(2000) NULL COMMENT '手工处理时必填,由服务层校验',
due_at DATETIME(3) NULL,
handled_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
handled_at DATETIME(3) NULL,
auto_closed_at DATETIME(3) NULL,
state_version INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (review_id),
UNIQUE KEY uk_ops_reviews_result_stage (
monitor_result_id, review_stage
),
KEY fk_ops_reviews_evaluation (evaluation_id),
KEY fk_ops_reviews_handler (handled_by),
KEY idx_ops_reviews_pending (
workspace_id, review_status, review_stage, due_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='模型团队初审与业务团队终审';
CREATE TABLE IF NOT EXISTS ops_outbox_events (
event_id CHAR(26) NOT NULL,
aggregate_type VARCHAR(64) NOT NULL,
aggregate_id VARCHAR(128) NOT NULL,
event_type VARCHAR(128) NOT NULL,
schema_version SMALLINT NOT NULL DEFAULT 1,
payload_json JSON NOT NULL,
event_status VARCHAR(16) NOT NULL DEFAULT 'pending'
COMMENT 'pending/published/failed',
available_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
retry_count INT NOT NULL DEFAULT 0,
trace_id VARCHAR(64) NULL,
idempotency_key VARCHAR(128) NULL,
published_at DATETIME(3) NULL,
last_error VARCHAR(2000) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (event_id),
UNIQUE KEY uk_ops_outbox_idempotency (idempotency_key),
KEY idx_ops_outbox_pending (event_status, available_at, created_at),
KEY idx_ops_outbox_aggregate (aggregate_type, aggregate_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='运维库事务 Outbox;由运维异步执行器轮询';
CREATE TABLE IF NOT EXISTS ops_consumer_inbox (
consumer_name VARCHAR(128) NOT NULL,
event_id CHAR(26) NOT NULL,
process_status VARCHAR(16) NOT NULL DEFAULT 'processing'
COMMENT 'processing/succeeded/failed',
message_id VARCHAR(128) NULL,
processed_at DATETIME(3) NULL,
error_message VARCHAR(2000) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (consumer_name, event_id),
KEY idx_ops_inbox_status (
consumer_name, process_status, created_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='运维异步消费者幂等 Inbox';
@@ -0,0 +1,172 @@
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_report_template_versions (
template_version_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
report_type VARCHAR(24) NOT NULL COMMENT 'monitor/diagnostic',
version_label VARCHAR(64) NOT NULL,
template_status VARCHAR(24) NOT NULL DEFAULT 'draft'
COMMENT 'draft/published/retired',
schema_json JSON NULL COMMENT '报告结构与字段定义',
template_storage_object_id CHAR(26) NULL
COMMENT '逻辑引用 model_platform.storage_objects',
change_note VARCHAR(1000) NULL,
created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
published_at DATETIME(3) NULL,
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (template_version_id),
UNIQUE KEY uk_ops_report_templates_version (
workspace_id, report_type, version_label
),
KEY idx_ops_report_templates_status (
workspace_id, report_type, template_status, published_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='监控/诊断报告模板版本';
CREATE TABLE IF NOT EXISTS ops_prompt_versions (
prompt_version_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
prompt_key VARCHAR(64) NOT NULL COMMENT 'monitor_A/diagnostic_BC 等',
version_label VARCHAR(64) NOT NULL,
prompt_name VARCHAR(200) NOT NULL,
prompt_text LONGTEXT NOT NULL,
prompt_status VARCHAR(24) NOT NULL DEFAULT 'draft'
COMMENT 'draft/review/published/retired',
change_note VARCHAR(1000) NULL,
created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
published_at DATETIME(3) NULL,
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (prompt_version_id),
UNIQUE KEY uk_ops_prompt_versions_label (
workspace_id, prompt_key, version_label
),
KEY idx_ops_prompt_versions_status (
workspace_id, prompt_key, prompt_status, published_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='报告 Prompt 版本';
CREATE TABLE IF NOT EXISTS ops_prompt_regression_runs (
regression_run_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
prompt_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_prompt_versions',
run_no INT NOT NULL,
run_status VARCHAR(24) NOT NULL DEFAULT 'queued'
COMMENT 'queued/running/passed/failed/cancelled',
sample_set_version VARCHAR(64) NOT NULL,
sample_count INT NOT NULL DEFAULT 0,
passed_count INT NOT NULL DEFAULT 0,
failed_count INT NOT NULL DEFAULT 0,
details_json JSON NULL,
started_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
started_at DATETIME(3) NULL,
finished_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (regression_run_id),
UNIQUE KEY uk_ops_prompt_regression_no (prompt_version_id, run_no),
KEY fk_ops_prompt_regression_prompt (prompt_version_id),
KEY idx_ops_prompt_regression_status (
workspace_id, run_status, created_at
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='Prompt 回归测试批次与结果摘要';
CREATE TABLE IF NOT EXISTS ops_reports (
report_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
report_no VARCHAR(64) NOT NULL,
monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results',
evaluation_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_evaluations',
report_month DATE NOT NULL COMMENT '固定存当月1日',
report_type VARCHAR(24) NOT NULL COMMENT 'monitor/diagnostic',
report_status VARCHAR(32) NOT NULL DEFAULT 'pending_model_read'
COMMENT 'pending_model_read/editing/sent_business',
template_version_id CHAR(26) NOT NULL,
prompt_version_id CHAR(26) NULL,
current_revision_id CHAR(26) NULL COMMENT '逻辑引用 ops_report_revisions',
output_date DATE NULL,
generated_at DATETIME(3) NULL,
sent_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
sent_at DATETIME(3) NULL,
business_first_read_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
business_first_read_at DATETIME(3) NULL,
state_version INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (report_id),
UNIQUE KEY uk_ops_reports_no (workspace_id, report_no),
UNIQUE KEY uk_ops_reports_result_type (monitor_result_id, report_type),
KEY fk_ops_reports_evaluation (evaluation_id),
KEY fk_ops_reports_template (template_version_id),
KEY fk_ops_reports_prompt (prompt_version_id),
KEY idx_ops_reports_summary (
workspace_id, report_month, report_type, report_status
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='监控报告与诊断报告主记录';
CREATE TABLE IF NOT EXISTS ops_report_revisions (
report_revision_id CHAR(26) NOT NULL,
report_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_reports',
revision_no INT NOT NULL,
revision_status VARCHAR(24) NOT NULL DEFAULT 'draft'
COMMENT 'draft/saved/sent',
body_json JSON NULL,
body_text LONGTEXT NULL,
source_snapshot_json JSON NOT NULL
COMMENT '生成时指标、规则、模板和Prompt快照',
pdf_storage_object_id CHAR(26) NULL
COMMENT '逻辑引用 model_platform.storage_objects',
excel_storage_object_id CHAR(26) NULL
COMMENT '逻辑引用 model_platform.storage_objects',
edited_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
edited_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (report_revision_id),
UNIQUE KEY uk_ops_report_revisions_no (report_id, revision_no),
KEY fk_ops_report_revisions_report (report_id),
KEY fk_ops_report_revisions_editor (edited_by)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='报告不可变正文修订';
CREATE TABLE IF NOT EXISTS ops_bank_report_configs (
bank_report_config_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks',
report_frequency VARCHAR(24) NOT NULL DEFAULT 'monthly'
COMMENT 'monthly/quarterly/semiannual/annual',
output_day TINYINT UNSIGNED NOT NULL DEFAULT 15,
output_time TIME NOT NULL DEFAULT '06:00:00',
reminder_days SMALLINT UNSIGNED NOT NULL DEFAULT 5,
enabled TINYINT(1) NOT NULL DEFAULT 1,
state_version INT NOT NULL DEFAULT 0,
updated_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (bank_report_config_id),
UNIQUE KEY uk_ops_bank_report_configs_bank (workspace_id, bank_id),
KEY idx_ops_bank_report_configs_due (
workspace_id, enabled, report_frequency, output_day
),
CONSTRAINT chk_ops_bank_report_output_day
CHECK (output_day BETWEEN 1 AND 28)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='按银行设置报告频率、输出日期与提醒时间';
@@ -0,0 +1,142 @@
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_workflows (
workflow_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
request_no VARCHAR(64) NOT NULL,
workflow_title VARCHAR(255) NOT NULL,
bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks',
category_code VARCHAR(32) NOT NULL COMMENT '逻辑引用 ops_model_categories',
model_instance_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_instances',
request_type VARCHAR(32) NOT NULL COMMENT 'new/iterate/rebuild',
development_source VARCHAR(32) NOT NULL DEFAULT 'independent'
COMMENT 'independent/common_reuse',
common_source_model_id CHAR(26) NULL COMMENT '逻辑引用通用模型实例',
current_stage SMALLINT NOT NULL DEFAULT 1,
workflow_status VARCHAR(24) NOT NULL DEFAULT 'active'
COMMENT 'active/completed/cancelled',
initiated_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
initiated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
expected_feedback_at DATETIME(3) NULL,
planned_test_date DATE NULL,
planned_online_date DATE NULL,
completed_at DATETIME(3) NULL,
cancelled_at DATETIME(3) NULL,
state_version INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (workflow_id),
UNIQUE KEY uk_ops_workflows_request_no (workspace_id, request_no),
KEY fk_ops_workflows_bank (bank_id),
KEY fk_ops_workflows_model (model_instance_id),
KEY fk_ops_workflows_common_source (common_source_model_id),
KEY idx_ops_workflows_filters (
workspace_id, workflow_status, category_code, current_stage, initiated_at
),
CONSTRAINT chk_ops_workflows_stage
CHECK (current_stage BETWEEN 1 AND 8)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='模型新增、迭代或通用模型复用的全流程实例';
CREATE TABLE IF NOT EXISTS ops_workflow_stages (
workflow_stage_id CHAR(26) NOT NULL,
workflow_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_workflows',
stage_no SMALLINT NOT NULL,
stage_name_snapshot VARCHAR(100) NOT NULL,
stage_status VARCHAR(24) NOT NULL DEFAULT 'pending'
COMMENT 'pending/active/waiting_confirmation/completed/skipped',
owner_role_code VARCHAR(64) NOT NULL COMMENT '登录角色代码快照',
planned_at DATETIME(3) NULL,
due_at DATETIME(3) NULL,
started_at DATETIME(3) NULL,
completed_at DATETIME(3) NULL,
completed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
confirmation_required TINYINT(1) NOT NULL DEFAULT 0,
confirmation_status VARCHAR(24) NULL
COMMENT 'pending/confirmed/rejected/not_required',
confirmed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
confirmed_at DATETIME(3) NULL,
stage_note VARCHAR(2000) NULL,
state_version INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (workflow_stage_id),
UNIQUE KEY uk_ops_workflow_stages_no (workflow_id, stage_no),
KEY fk_ops_workflow_stages_workflow (workflow_id),
KEY idx_ops_workflow_stages_due (stage_status, due_at),
CONSTRAINT chk_ops_workflow_stages_no
CHECK (stage_no BETWEEN 1 AND 7)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='七阶段流程节点实例与确认留痕';
CREATE TABLE IF NOT EXISTS ops_documents (
document_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
workflow_id CHAR(26) NULL COMMENT '逻辑引用 ops_workflows',
workflow_stage_id CHAR(26) NULL COMMENT '逻辑引用 ops_workflow_stages',
model_instance_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_instances',
model_version_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_versions',
document_type VARCHAR(64) NOT NULL,
document_name VARCHAR(255) NOT NULL,
document_version VARCHAR(64) NULL,
source_type VARCHAR(16) NOT NULL COMMENT 'storage/link',
storage_object_id CHAR(26) NULL
COMMENT '逻辑引用 model_platform.storage_objects',
external_url VARCHAR(1500) NULL,
uploaded_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
confirmation_required TINYINT(1) NOT NULL DEFAULT 0,
confirmation_status VARCHAR(24) NULL,
confirmed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users',
confirmed_at DATETIME(3) NULL,
metadata_json JSON NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (document_id),
KEY fk_ops_documents_workflow (workflow_id),
KEY fk_ops_documents_stage (workflow_stage_id),
KEY fk_ops_documents_model (model_instance_id),
KEY fk_ops_documents_version (model_version_id),
KEY fk_ops_documents_storage (storage_object_id),
KEY idx_ops_documents_knowledge (
workspace_id, document_type, model_instance_id, uploaded_at
),
CONSTRAINT chk_ops_documents_source
CHECK (
(source_type = 'storage' AND storage_object_id IS NOT NULL)
OR (source_type = 'link' AND external_url IS NOT NULL)
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='流程材料和文档知识库索引;文件本体复用平台对象存储';
CREATE TABLE IF NOT EXISTS ops_usage_events (
usage_event_id CHAR(26) NOT NULL,
workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces',
user_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users',
role_code_snapshot VARCHAR(64) NULL,
event_type VARCHAR(64) NOT NULL
COMMENT 'login/request_submit/report_read/report_download',
target_type VARCHAR(64) NULL,
target_id VARCHAR(128) NULL,
event_metadata JSON NULL,
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
is_deleted TINYINT(1) NOT NULL DEFAULT 0,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (usage_event_id),
KEY idx_ops_usage_events_stats (
workspace_id, occurred_at, event_type, user_id
),
KEY idx_ops_usage_events_target (target_type, target_id, occurred_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='运维平台登录、需求和报告行为事件';
@@ -0,0 +1,21 @@
USE model_operations;
INSERT INTO ops_model_categories (
category_code,
category_name,
sort_order,
status,
is_deleted,
deleted_at
) VALUES
('std', '标准A卡', 10, 'active', 0, NULL),
('bai', '白户A卡', 20, 'active', 0, NULL),
('big', '大额A卡', 30, 'active', 0, NULL),
('afd', '反欺诈评分', 40, 'active', 0, NULL)
ON DUPLICATE KEY UPDATE
category_name = VALUES(category_name),
sort_order = VALUES(sort_order),
status = VALUES(status),
is_deleted = 0,
deleted_at = NULL;
@@ -0,0 +1,55 @@
# model_operations 数据库 V0.1
本目录只提供建库建表脚本,不会修改现有 `model_platform` 库。当前为设计稿,执行生产数据库前仍需 DBA 审核账号、备份和变更窗口。
如需一次性复制执行 24 张新表,直接使用同级文件 `model_operations-完整建表-V0.1.sql`;该文件不包含建库、视图或种子数据。
先将 `model_operations` 设为当前活动数据库,再使用 DBeaver 的“执行 SQL 脚本”(Alt/Option + X)。合并文件不再包含 `USE`Ctrl/Cmd + Enter 仍只适合单条 `CREATE TABLE`
## 执行顺序
```bash
mysql -h <host> -P <port> -u <schema-admin> -p < 00_database.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 10_reference_and_models.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 20_monitoring_source.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 30_governance_and_reviews.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 40_reports_and_prompts.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 50_workflows_documents_usage.sql
mysql -h <host> -P <port> -u <schema-admin> -p model_operations < 60_seed.sql
```
脚本使用 `CREATE DATABASE/TABLE IF NOT EXISTS`,不包含 `DROP``TRUNCATE` 或业务数据删除。索引全部内联在建表语句中,避免 MySQL 8 不支持 `CREATE INDEX IF NOT EXISTS` 的问题。
执行前可运行 `python3 validate_schema.py`,检查 24 张表、1 个视图、文件行数和破坏性语句。
## 两库边界
### model_platform:只读依赖
| 表 | 运维模块用途 | 允许读取的关键字段 |
|---|---|---|
| `users` | 当前用户、处理人和上传人显示 | `user_id/display_name/status/platform_role_id`;禁止读取 `password_hash` |
| `roles` | 登录角色代码和名称 | `role_id/role_code/role_name/role_scope` |
| `workspaces` | 运维数据空间 | `workspace_id/workspace_code/workspace_name/status` |
| `workspace_members` | 校验用户能否访问工作空间 | `workspace_id/user_id/role_id/member_status` |
| `storage_objects` | 报告、评分逻辑和流程材料元数据 | 通过现有存储服务访问,运维表只保存 `storage_object_id` |
| `versions/scripts` | 可选关联模型开发产物 | 仅引用稳定版本和脚本标识,不把它当业务模型版本 |
| `schedules/schedule_runs` | 可选追踪监控计算任务 | 只读任务和运行状态,不直接改调度数据 |
`permissions``role_permissions``upload_sessions`、平台 `outbox_events` 等表不由运维模块直接读写;权限仍由现有登录/权限模块负责。
### model_operations:运维模块自有
- 24 张表:7 张模型源表、15 张运维业务表、2 张独立 Outbox/Inbox 技术表。
- 所有 `workspace_id/user_id/storage_object_id/platform_versions_id` 均为跨库逻辑引用,不建立跨库物理外键。
- 模型平台写入 7 张源表,并可向 `ops_outbox_events` 插入 `monitor_batch.published` 事件;不得写判级、复核、报告和流程表。
- 运维应用拥有本库业务读写权限,但不得更新已发布的原始监控结果。
## 应用连接
后端需要两套连接配置:
- `DATABASE_URL`:现有 `model_platform`,沿用认证、工作空间、存储和调度能力。
- `OPERATIONS_DATABASE_URL`:新建 `model_operations`,承载全部运维业务数据。
首条真实接口只需要后端双连接;异步判级、报告和提醒接入时,再让 Schedule Executor 增加运维库 Outbox 轮询连接。
@@ -0,0 +1,70 @@
"""Static safety and inventory checks for the model_operations V0.1 DDL."""
from __future__ import annotations
import re
from pathlib import Path
BASE = Path(__file__).parent
EXPECTED_TABLES = {
"ops_model_categories",
"ops_banks",
"ops_model_instances",
"ops_model_versions",
"ops_monitor_batches",
"ops_monitor_results",
"ops_monitor_feature_metrics",
"ops_monitor_distributions",
"ops_rule_versions",
"ops_rule_items",
"ops_monitor_evaluations",
"ops_monitor_reviews",
"ops_outbox_events",
"ops_consumer_inbox",
"ops_report_template_versions",
"ops_prompt_versions",
"ops_prompt_regression_runs",
"ops_reports",
"ops_report_revisions",
"ops_bank_report_configs",
"ops_workflows",
"ops_workflow_stages",
"ops_documents",
"ops_usage_events",
}
def main() -> None:
sql_files = sorted(BASE.glob("*.sql"))
combined = "\n".join(path.read_text(encoding="utf-8") for path in sql_files)
tables = {
match.lower()
for match in re.findall(
r"CREATE\s+TABLE\s+IF\s+NOT\s+EXISTS\s+([a-zA-Z0-9_]+)",
combined,
flags=re.IGNORECASE,
)
}
missing = EXPECTED_TABLES - tables
extra = tables - EXPECTED_TABLES
assert not missing, f"missing tables: {sorted(missing)}"
assert not extra, f"unexpected tables: {sorted(extra)}"
assert len(re.findall(r"CREATE\s+OR\s+REPLACE\s+VIEW", combined, re.I)) == 1
assert not re.search(r"\b(DROP|TRUNCATE|DELETE\s+FROM)\b", combined, re.I)
assert not re.search(
r"\b(?:FROM|JOIN|UPDATE|INTO|REFERENCES|TABLE)\s+model_platform\.",
combined,
re.I,
), "cross-database SQL object reference found"
for path in sql_files:
line_count = len(path.read_text(encoding="utf-8").splitlines())
assert line_count <= 500, f"{path.name} exceeds 500 lines"
print(
f"validated {len(sql_files)} SQL files: "
f"{len(tables)} tables, 1 view, no destructive statements"
)
if __name__ == "__main__":
main()
@@ -0,0 +1,40 @@
-- A 卡模型智能运维平台 · model_operations 完整建表 V0.1
-- 内容:仅包含新库的 24 张表,不含建库、视图、种子数据或 model_platform 现有表。
-- 执行前请确认当前账号具备 model_operations 建表权限。
-- DBeaver:请使用“执行 SQL 脚本”(Alt/Option + X),不要选中全文后按 Ctrl/Cmd + Enter。
-- 如使用“执行 SQL 语句”,每次只能选中一条 USE 或 CREATE TABLE 单独执行。
-- ===== 10_reference_and_models.sql =====
USE model_operations;
CREATE TABLE IF NOT EXISTS ops_model_categories ( category_code VARCHAR(32) NOT NULL COMMENT 'std/bai/big/afd', category_name VARCHAR(100) NOT NULL, sort_order SMALLINT NOT NULL DEFAULT 0, status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/inactive', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (category_code), KEY idx_ops_categories_status (status, sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='固定模型大类字典,由运维模块维护';
CREATE TABLE IF NOT EXISTS ops_banks ( bank_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', bank_code VARCHAR(64) NOT NULL, bank_name VARCHAR(150) NOT NULL, is_wuji_bank TINYINT(1) NOT NULL DEFAULT 0, bank_status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/inactive', source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform', source_bank_ref VARCHAR(128) NULL, source_updated_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (bank_id), UNIQUE KEY uk_ops_banks_code (workspace_id, bank_code), UNIQUE KEY uk_ops_banks_source ( workspace_id, source_system, source_bank_ref ), KEY idx_ops_banks_workspace_status ( workspace_id, bank_status, is_deleted ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='银行主数据;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_model_instances ( model_instance_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks', category_code VARCHAR(32) NOT NULL COMMENT '逻辑引用 ops_model_categories', model_id VARCHAR(128) NOT NULL COMMENT '工作空间内全局唯一业务模型ID', model_name VARCHAR(200) NOT NULL, model_status VARCHAR(24) NOT NULL DEFAULT 'normal' COMMENT 'normal/escort/escort_finished/offline', current_version_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_versions', is_common_model TINYINT(1) NOT NULL DEFAULT 0, common_source_model_id CHAR(26) NULL COMMENT '复用时逻辑引用通用模型实例', common_model_name VARCHAR(200) NULL, source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform', source_model_ref VARCHAR(128) NULL, source_updated_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (model_instance_id), UNIQUE KEY uk_ops_models_business_id (workspace_id, model_id), UNIQUE KEY uk_ops_models_source ( workspace_id, source_system, source_model_ref ), KEY fk_ops_models_bank (bank_id), KEY fk_ops_models_current_version (current_version_id), KEY fk_ops_models_common_source (common_source_model_id), KEY idx_ops_models_filters ( workspace_id, category_code, model_status, is_deleted ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='业务模型实例;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_model_versions ( model_version_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', model_instance_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_instances', version_label VARCHAR(64) NOT NULL COMMENT '例如 v2.3', version_status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT 'draft/escort/active/offline', platform_versions_id CHAR(26) NULL COMMENT '可选逻辑引用 model_platform.versions', developer_user_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', developer_display_name VARCHAR(100) NULL COMMENT '历史展示快照', development_date DATE NULL, iteration_start_date DATE NULL, last_iteration_date DATE NULL, iteration_reason VARCHAR(1000) NULL, escort_start_date DATE NULL, escort_end_date DATE NULL, online_date DATE NULL, offline_date DATE NULL, development_ks DECIMAL(12,8) NULL COMMENT '0-1比率', development_psi DECIMAL(12,8) NULL COMMENT '0-1比率', max_lift DECIMAL(12,8) NULL, scoring_logic_storage_object_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.storage_objects', source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform', source_version_ref VARCHAR(128) NULL, source_updated_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (model_version_id), UNIQUE KEY uk_ops_model_versions_label ( model_instance_id, version_label ), UNIQUE KEY uk_ops_model_versions_source ( workspace_id, source_system, source_version_ref ), KEY fk_ops_model_versions_model (model_instance_id), KEY fk_ops_model_versions_platform_version (platform_versions_id), KEY fk_ops_model_versions_developer (developer_user_id), KEY idx_ops_model_versions_lifecycle ( workspace_id, version_status, online_date, offline_date ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='模型版本与生命周期;模型平台可受控直写';
-- ===== 20_monitoring_source.sql =====
CREATE TABLE IF NOT EXISTS ops_monitor_batches ( batch_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', source_system VARCHAR(64) NOT NULL DEFAULT 'model_platform', source_batch_no VARCHAR(128) NOT NULL COMMENT '来源方幂等批次号', monitor_month DATE NOT NULL COMMENT '固定存当月1日', revision_no INT NOT NULL DEFAULT 1, supersedes_batch_id CHAR(26) NULL COMMENT '逻辑引用上一修订批次', batch_status VARCHAR(24) NOT NULL DEFAULT 'writing' COMMENT 'writing/published/failed/superseded', expected_model_count INT NOT NULL DEFAULT 0, written_model_count INT NOT NULL DEFAULT 0, feature_row_count BIGINT NOT NULL DEFAULT 0, distribution_row_count BIGINT NOT NULL DEFAULT 0, checksum_sha256 CHAR(64) NULL, generated_at DATETIME(3) NULL, published_at DATETIME(3) NULL, published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', failed_reason VARCHAR(2000) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (batch_id), UNIQUE KEY uk_ops_monitor_batches_source_no ( workspace_id, source_system, source_batch_no ), UNIQUE KEY uk_ops_monitor_batches_revision ( workspace_id, source_system, monitor_month, revision_no ), KEY fk_ops_monitor_batches_supersedes (supersedes_batch_id), KEY idx_ops_monitor_batches_publish ( workspace_id, batch_status, monitor_month, revision_no ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='月度监控写入批次与发布门闩;模型平台可受控直写';
CREATE TABLE IF NOT EXISTS ops_monitor_results ( monitor_result_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', batch_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_batches', model_instance_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_instances', model_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_model_versions', monitor_month DATE NOT NULL COMMENT '固定存当月1日', source_result_ref VARCHAR(128) NULL, ranking_result VARCHAR(24) NULL COMMENT 'matched/unmatched/not_applicable', ks_value DECIMAL(12,8) NULL COMMENT '0-1比率', psi_value DECIMAL(12,8) NULL COMMENT '0-1比率', sample_count BIGINT NULL, good_count BIGINT NULL, bad_count BIGINT NULL, source_result_json JSON NULL COMMENT '尚未结构化的可追溯源字段', source_calculated_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (monitor_result_id), UNIQUE KEY uk_ops_monitor_results_batch_model ( batch_id, model_instance_id ), KEY fk_ops_monitor_results_batch (batch_id), KEY fk_ops_monitor_results_model (model_instance_id), KEY fk_ops_monitor_results_version (model_version_id), KEY idx_ops_monitor_results_month ( workspace_id, monitor_month, model_instance_id ), CONSTRAINT chk_ops_monitor_results_ks CHECK (ks_value IS NULL OR (ks_value >= 0 AND ks_value <= 1)), CONSTRAINT chk_ops_monitor_results_psi CHECK (psi_value IS NULL OR psi_value >= 0) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='单模型单月原始监控结果;发布后不可更新';
CREATE TABLE IF NOT EXISTS ops_monitor_feature_metrics ( feature_metric_id CHAR(26) NOT NULL, monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results', feature_code VARCHAR(128) NOT NULL, feature_name VARCHAR(200) NOT NULL, iv_value DECIMAL(12,8) NULL, previous_iv_value DECIMAL(12,8) NULL, iv_drop_rate DECIMAL(12,8) NULL COMMENT '0-1比率', csi_value DECIMAL(12,8) NULL, previous_csi_value DECIMAL(12,8) NULL, csi_rise_rate DECIMAL(12,8) NULL COMMENT '0-1比率', ks_contribution_change DECIMAL(12,8) NULL, psi_contribution_change DECIMAL(12,8) NULL, source_metric_json JSON NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (feature_metric_id), UNIQUE KEY uk_ops_feature_metrics_result_feature ( monitor_result_id, feature_code ), KEY fk_ops_feature_metrics_result (monitor_result_id), KEY idx_ops_feature_metrics_iv_drop (monitor_result_id, iv_drop_rate), KEY idx_ops_feature_metrics_csi_rise (monitor_result_id, csi_rise_rate) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='单月特征级 IV/CSI 与贡献变化;发布后不可更新';
CREATE TABLE IF NOT EXISTS ops_monitor_distributions ( distribution_id CHAR(26) NOT NULL, monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results', dimension_type VARCHAR(24) NOT NULL COMMENT 'score_band/feature_bin', feature_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '评分分箱时为空串', feature_name VARCHAR(200) NULL, bin_order INT NOT NULL, bin_code VARCHAR(128) NOT NULL, bin_label VARCHAR(255) NOT NULL, reference_period_label VARCHAR(64) NULL, reference_count BIGINT NULL, reference_share DECIMAL(12,8) NULL COMMENT '0-1比率', current_count BIGINT NULL, current_share DECIMAL(12,8) NULL COMMENT '0-1比率', good_count BIGINT NULL, bad_count BIGINT NULL, bad_rate DECIMAL(12,8) NULL COMMENT '0-1比率', psi_component DECIMAL(12,8) NULL, csi_component DECIMAL(12,8) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (distribution_id), UNIQUE KEY uk_ops_distributions_bin ( monitor_result_id, dimension_type, feature_code, bin_order ), KEY fk_ops_distributions_result (monitor_result_id), KEY idx_ops_distributions_feature ( monitor_result_id, feature_code, bin_order ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='排序性评分分箱及特征分布;发布后不可更新';
-- ===== 30_governance_and_reviews.sql =====
CREATE TABLE IF NOT EXISTS ops_rule_versions ( rule_version_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', category_code VARCHAR(32) NOT NULL DEFAULT '*' COMMENT '* 表示全部模型大类', version_label VARCHAR(64) NOT NULL, rule_status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/retired', threshold_json JSON NOT NULL COMMENT 'KS/PSI/环比切点快照', supersedes_rule_version_id CHAR(26) NULL, change_note VARCHAR(1000) NULL, created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', published_at DATETIME(3) NULL, is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (rule_version_id), UNIQUE KEY uk_ops_rule_versions_label ( workspace_id, category_code, version_label ), KEY fk_ops_rule_versions_supersedes (supersedes_rule_version_id), KEY idx_ops_rule_versions_status ( workspace_id, category_code, rule_status, published_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='监控判级规则版本';
CREATE TABLE IF NOT EXISTS ops_rule_items ( rule_item_id CHAR(26) NOT NULL, rule_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_versions', sort_order SMALLINT NOT NULL, ranking_result VARCHAR(24) NOT NULL COMMENT 'matched/unmatched', ks_band_code VARCHAR(32) NOT NULL, psi_band_code VARCHAR(32) NOT NULL, ks_drop_band_code VARCHAR(32) NOT NULL, conditions_json JSON NULL COMMENT '用于跨档或扩展条件', abnormal_level VARCHAR(16) NOT NULL COMMENT 'normal/level1/level2/level3', monitor_grade CHAR(1) NOT NULL COMMENT 'A/B/C', secondary_upgrade_threshold SMALLINT NULL COMMENT '近6月二级异常累计升级阈值', reason_code VARCHAR(64) NOT NULL, reason_template VARCHAR(1000) NOT NULL, action_text VARCHAR(1000) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (rule_item_id), UNIQUE KEY uk_ops_rule_items_order (rule_version_id, sort_order), UNIQUE KEY uk_ops_rule_items_reason (rule_version_id, reason_code), KEY fk_ops_rule_items_version (rule_version_id), CONSTRAINT chk_ops_rule_items_grade CHECK (monitor_grade IN ('A', 'B', 'C')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='规则版本下的判级矩阵行';
CREATE TABLE IF NOT EXISTS ops_monitor_evaluations ( evaluation_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results', rule_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_versions', rule_item_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_rule_items', ks_mom_drop_rate DECIMAL(12,8) NULL COMMENT '0-1比率', secondary_level2_hits_6m SMALLINT NOT NULL DEFAULT 0, abnormal_level VARCHAR(16) NOT NULL COMMENT 'normal/level1/level2/level3', monitor_grade CHAR(1) NOT NULL COMMENT 'A/B/C', reason_code VARCHAR(64) NOT NULL, reason_text_snapshot VARCHAR(2000) NOT NULL, action_snapshot VARCHAR(2000) NOT NULL, is_current TINYINT(1) NOT NULL DEFAULT 1, evaluated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, current_monitor_result_id CHAR(26) GENERATED ALWAYS AS ( CASE WHEN is_current = 1 AND is_deleted = 0 THEN monitor_result_id ELSE NULL END ) VIRTUAL, PRIMARY KEY (evaluation_id), UNIQUE KEY uk_ops_evaluations_result_rule ( monitor_result_id, rule_version_id ), UNIQUE KEY uk_ops_evaluations_current (current_monitor_result_id), KEY fk_ops_evaluations_result (monitor_result_id), KEY fk_ops_evaluations_rule_version (rule_version_id), KEY fk_ops_evaluations_rule_item (rule_item_id), KEY idx_ops_evaluations_grade ( workspace_id, monitor_grade, abnormal_level, evaluated_at ), CONSTRAINT chk_ops_evaluations_grade CHECK (monitor_grade IN ('A', 'B', 'C')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='按规则版本生成的不可变监控判级快照';
CREATE TABLE IF NOT EXISTS ops_monitor_reviews ( review_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results', evaluation_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_evaluations', review_stage VARCHAR(24) NOT NULL COMMENT 'model_initial/business_final', review_status VARCHAR(24) NOT NULL DEFAULT 'pending' COMMENT 'pending/handled/auto_closed', decision_code VARCHAR(32) NULL COMMENT 'no_action/tune_or_rebuild', handling_note VARCHAR(2000) NULL COMMENT '手工处理时必填,由服务层校验', due_at DATETIME(3) NULL, handled_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', handled_at DATETIME(3) NULL, auto_closed_at DATETIME(3) NULL, state_version INT NOT NULL DEFAULT 0, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (review_id), UNIQUE KEY uk_ops_reviews_result_stage ( monitor_result_id, review_stage ), KEY fk_ops_reviews_evaluation (evaluation_id), KEY fk_ops_reviews_handler (handled_by), KEY idx_ops_reviews_pending ( workspace_id, review_status, review_stage, due_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='模型团队初审与业务团队终审';
CREATE TABLE IF NOT EXISTS ops_outbox_events ( event_id CHAR(26) NOT NULL, aggregate_type VARCHAR(64) NOT NULL, aggregate_id VARCHAR(128) NOT NULL, event_type VARCHAR(128) NOT NULL, schema_version SMALLINT NOT NULL DEFAULT 1, payload_json JSON NOT NULL, event_status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/published/failed', available_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), retry_count INT NOT NULL DEFAULT 0, trace_id VARCHAR(64) NULL, idempotency_key VARCHAR(128) NULL, published_at DATETIME(3) NULL, last_error VARCHAR(2000) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (event_id), UNIQUE KEY uk_ops_outbox_idempotency (idempotency_key), KEY idx_ops_outbox_pending (event_status, available_at, created_at), KEY idx_ops_outbox_aggregate (aggregate_type, aggregate_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='运维库事务 Outbox;由运维异步执行器轮询';
CREATE TABLE IF NOT EXISTS ops_consumer_inbox ( consumer_name VARCHAR(128) NOT NULL, event_id CHAR(26) NOT NULL, process_status VARCHAR(16) NOT NULL DEFAULT 'processing' COMMENT 'processing/succeeded/failed', message_id VARCHAR(128) NULL, processed_at DATETIME(3) NULL, error_message VARCHAR(2000) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (consumer_name, event_id), KEY idx_ops_inbox_status ( consumer_name, process_status, created_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='运维异步消费者幂等 Inbox';
-- ===== 40_reports_and_prompts.sql =====
CREATE TABLE IF NOT EXISTS ops_report_template_versions ( template_version_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', report_type VARCHAR(24) NOT NULL COMMENT 'monitor/diagnostic', version_label VARCHAR(64) NOT NULL, template_status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/retired', schema_json JSON NULL COMMENT '报告结构与字段定义', template_storage_object_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.storage_objects', change_note VARCHAR(1000) NULL, created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', published_at DATETIME(3) NULL, is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (template_version_id), UNIQUE KEY uk_ops_report_templates_version ( workspace_id, report_type, version_label ), KEY idx_ops_report_templates_status ( workspace_id, report_type, template_status, published_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='监控/诊断报告模板版本';
CREATE TABLE IF NOT EXISTS ops_prompt_versions ( prompt_version_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', prompt_key VARCHAR(64) NOT NULL COMMENT 'monitor_A/diagnostic_BC 等', version_label VARCHAR(64) NOT NULL, prompt_name VARCHAR(200) NOT NULL, prompt_text LONGTEXT NOT NULL, prompt_status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/review/published/retired', change_note VARCHAR(1000) NULL, created_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), published_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', published_at DATETIME(3) NULL, is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (prompt_version_id), UNIQUE KEY uk_ops_prompt_versions_label ( workspace_id, prompt_key, version_label ), KEY idx_ops_prompt_versions_status ( workspace_id, prompt_key, prompt_status, published_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='报告 Prompt 版本';
CREATE TABLE IF NOT EXISTS ops_prompt_regression_runs ( regression_run_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', prompt_version_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_prompt_versions', run_no INT NOT NULL, run_status VARCHAR(24) NOT NULL DEFAULT 'queued' COMMENT 'queued/running/passed/failed/cancelled', sample_set_version VARCHAR(64) NOT NULL, sample_count INT NOT NULL DEFAULT 0, passed_count INT NOT NULL DEFAULT 0, failed_count INT NOT NULL DEFAULT 0, details_json JSON NULL, started_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', started_at DATETIME(3) NULL, finished_at DATETIME(3) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (regression_run_id), UNIQUE KEY uk_ops_prompt_regression_no (prompt_version_id, run_no), KEY fk_ops_prompt_regression_prompt (prompt_version_id), KEY idx_ops_prompt_regression_status ( workspace_id, run_status, created_at ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Prompt 回归测试批次与结果摘要';
CREATE TABLE IF NOT EXISTS ops_reports ( report_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', report_no VARCHAR(64) NOT NULL, monitor_result_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_results', evaluation_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_monitor_evaluations', report_month DATE NOT NULL COMMENT '固定存当月1日', report_type VARCHAR(24) NOT NULL COMMENT 'monitor/diagnostic', report_status VARCHAR(32) NOT NULL DEFAULT 'pending_model_read' COMMENT 'pending_model_read/editing/sent_business', template_version_id CHAR(26) NOT NULL, prompt_version_id CHAR(26) NULL, current_revision_id CHAR(26) NULL COMMENT '逻辑引用 ops_report_revisions', output_date DATE NULL, generated_at DATETIME(3) NULL, sent_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', sent_at DATETIME(3) NULL, business_first_read_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', business_first_read_at DATETIME(3) NULL, state_version INT NOT NULL DEFAULT 0, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (report_id), UNIQUE KEY uk_ops_reports_no (workspace_id, report_no), UNIQUE KEY uk_ops_reports_result_type (monitor_result_id, report_type), KEY fk_ops_reports_evaluation (evaluation_id), KEY fk_ops_reports_template (template_version_id), KEY fk_ops_reports_prompt (prompt_version_id), KEY idx_ops_reports_summary ( workspace_id, report_month, report_type, report_status ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='监控报告与诊断报告主记录';
CREATE TABLE IF NOT EXISTS ops_report_revisions ( report_revision_id CHAR(26) NOT NULL, report_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_reports', revision_no INT NOT NULL, revision_status VARCHAR(24) NOT NULL DEFAULT 'draft' COMMENT 'draft/saved/sent', body_json JSON NULL, body_text LONGTEXT NULL, source_snapshot_json JSON NOT NULL COMMENT '生成时指标、规则、模板和Prompt快照', pdf_storage_object_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.storage_objects', excel_storage_object_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.storage_objects', edited_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', edited_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (report_revision_id), UNIQUE KEY uk_ops_report_revisions_no (report_id, revision_no), KEY fk_ops_report_revisions_report (report_id), KEY fk_ops_report_revisions_editor (edited_by) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='报告不可变正文修订';
CREATE TABLE IF NOT EXISTS ops_bank_report_configs ( bank_report_config_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks', report_frequency VARCHAR(24) NOT NULL DEFAULT 'monthly' COMMENT 'monthly/quarterly/semiannual/annual', output_day TINYINT UNSIGNED NOT NULL DEFAULT 15, output_time TIME NOT NULL DEFAULT '06:00:00', reminder_days SMALLINT UNSIGNED NOT NULL DEFAULT 5, enabled TINYINT(1) NOT NULL DEFAULT 1, state_version INT NOT NULL DEFAULT 0, updated_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (bank_report_config_id), UNIQUE KEY uk_ops_bank_report_configs_bank (workspace_id, bank_id), KEY idx_ops_bank_report_configs_due ( workspace_id, enabled, report_frequency, output_day ), CONSTRAINT chk_ops_bank_report_output_day CHECK (output_day BETWEEN 1 AND 28) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='按银行设置报告频率、输出日期与提醒时间';
-- ===== 50_workflows_documents_usage.sql =====
CREATE TABLE IF NOT EXISTS ops_workflows ( workflow_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', request_no VARCHAR(64) NOT NULL, workflow_title VARCHAR(255) NOT NULL, bank_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_banks', category_code VARCHAR(32) NOT NULL COMMENT '逻辑引用 ops_model_categories', model_instance_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_instances', request_type VARCHAR(32) NOT NULL COMMENT 'new/iterate/rebuild', development_source VARCHAR(32) NOT NULL DEFAULT 'independent' COMMENT 'independent/common_reuse', common_source_model_id CHAR(26) NULL COMMENT '逻辑引用通用模型实例', current_stage SMALLINT NOT NULL DEFAULT 1, workflow_status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT 'active/completed/cancelled', initiated_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', initiated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), expected_feedback_at DATETIME(3) NULL, planned_test_date DATE NULL, planned_online_date DATE NULL, completed_at DATETIME(3) NULL, cancelled_at DATETIME(3) NULL, state_version INT NOT NULL DEFAULT 0, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (workflow_id), UNIQUE KEY uk_ops_workflows_request_no (workspace_id, request_no), KEY fk_ops_workflows_bank (bank_id), KEY fk_ops_workflows_model (model_instance_id), KEY fk_ops_workflows_common_source (common_source_model_id), KEY idx_ops_workflows_filters ( workspace_id, workflow_status, category_code, current_stage, initiated_at ), CONSTRAINT chk_ops_workflows_stage CHECK (current_stage BETWEEN 1 AND 8) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='模型新增、迭代或通用模型复用的全流程实例';
CREATE TABLE IF NOT EXISTS ops_workflow_stages ( workflow_stage_id CHAR(26) NOT NULL, workflow_id CHAR(26) NOT NULL COMMENT '逻辑引用 ops_workflows', stage_no SMALLINT NOT NULL, stage_name_snapshot VARCHAR(100) NOT NULL, stage_status VARCHAR(24) NOT NULL DEFAULT 'pending' COMMENT 'pending/active/waiting_confirmation/completed/skipped', owner_role_code VARCHAR(64) NOT NULL COMMENT '登录角色代码快照', planned_at DATETIME(3) NULL, due_at DATETIME(3) NULL, started_at DATETIME(3) NULL, completed_at DATETIME(3) NULL, completed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', confirmation_required TINYINT(1) NOT NULL DEFAULT 0, confirmation_status VARCHAR(24) NULL COMMENT 'pending/confirmed/rejected/not_required', confirmed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', confirmed_at DATETIME(3) NULL, stage_note VARCHAR(2000) NULL, state_version INT NOT NULL DEFAULT 0, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (workflow_stage_id), UNIQUE KEY uk_ops_workflow_stages_no (workflow_id, stage_no), KEY fk_ops_workflow_stages_workflow (workflow_id), KEY idx_ops_workflow_stages_due (stage_status, due_at), CONSTRAINT chk_ops_workflow_stages_no CHECK (stage_no BETWEEN 1 AND 7) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='七阶段流程节点实例与确认留痕';
CREATE TABLE IF NOT EXISTS ops_documents ( document_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', workflow_id CHAR(26) NULL COMMENT '逻辑引用 ops_workflows', workflow_stage_id CHAR(26) NULL COMMENT '逻辑引用 ops_workflow_stages', model_instance_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_instances', model_version_id CHAR(26) NULL COMMENT '逻辑引用 ops_model_versions', document_type VARCHAR(64) NOT NULL, document_name VARCHAR(255) NOT NULL, document_version VARCHAR(64) NULL, source_type VARCHAR(16) NOT NULL COMMENT 'storage/link', storage_object_id CHAR(26) NULL COMMENT '逻辑引用 model_platform.storage_objects', external_url VARCHAR(1500) NULL, uploaded_by CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), confirmation_required TINYINT(1) NOT NULL DEFAULT 0, confirmation_status VARCHAR(24) NULL, confirmed_by CHAR(26) NULL COMMENT '逻辑引用 model_platform.users', confirmed_at DATETIME(3) NULL, metadata_json JSON NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (document_id), KEY fk_ops_documents_workflow (workflow_id), KEY fk_ops_documents_stage (workflow_stage_id), KEY fk_ops_documents_model (model_instance_id), KEY fk_ops_documents_version (model_version_id), KEY fk_ops_documents_storage (storage_object_id), KEY idx_ops_documents_knowledge ( workspace_id, document_type, model_instance_id, uploaded_at ), CONSTRAINT chk_ops_documents_source CHECK ( (source_type = 'storage' AND storage_object_id IS NOT NULL) OR (source_type = 'link' AND external_url IS NOT NULL) ) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='流程材料和文档知识库索引;文件本体复用平台对象存储';
CREATE TABLE IF NOT EXISTS ops_usage_events ( usage_event_id CHAR(26) NOT NULL, workspace_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.workspaces', user_id CHAR(26) NOT NULL COMMENT '逻辑引用 model_platform.users', role_code_snapshot VARCHAR(64) NULL, event_type VARCHAR(64) NOT NULL COMMENT 'login/request_submit/report_read/report_download', target_type VARCHAR(64) NULL, target_id VARCHAR(128) NULL, event_metadata JSON NULL, occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), is_deleted TINYINT(1) NOT NULL DEFAULT 0, deleted_at DATETIME(3) NULL, PRIMARY KEY (usage_event_id), KEY idx_ops_usage_events_stats ( workspace_id, occurred_at, event_type, user_id ), KEY idx_ops_usage_events_target (target_type, target_id, occurred_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='运维平台登录、需求和报告行为事件';
@@ -0,0 +1,544 @@
create table model_platform.consumer_inbox
(
consumer_name varchar(128) not null,
event_id char(26) not null,
process_status varchar(16) default 'processing' not null comment 'processing/succeeded/failed',
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
message_id varchar(128) null comment 'Inbox message ID',
processed_at datetime(3) null,
error_message varchar(2000) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
primary key (consumer_name, event_id)
)
comment '消费者幂等 Inbox,防止 Stream 重投导致重复执行';
create index idx_consumer_inbox_status
on model_platform.consumer_inbox (consumer_name, process_status, created_at);
create table model_platform.data_resources
(
resource_id char(26) not null
primary key,
workspace_id char(26) not null,
storage_object_id char(26) not null,
owner_user_id char(26) not null,
resource_name varchar(255) not null,
visibility varchar(16) default 'private' not null,
status varchar(16) default 'active' not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
description varchar(1000) null,
schema_json json null comment '字段结构、行数等可选元数据',
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null
)
comment '数据资源';
create index idx_data_resources_owner
on model_platform.data_resources (owner_user_id, status);
create index idx_data_resources_workspace
on model_platform.data_resources (workspace_id, visibility, status);
create table model_platform.outbox_events
(
event_id char(26) not null
primary key,
aggregate_type varchar(64) not null,
aggregate_id varchar(128) not null,
event_type varchar(128) not null,
schema_version smallint default 1 not null,
payload_json json not null,
event_status varchar(16) default 'pending' not null comment 'pending/published/failed',
available_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
retry_count int default 0 not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
trace_id varchar(64) null,
idempotency_key varchar(128) null,
published_at datetime(3) null,
last_error varchar(2000) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null
)
comment '事务 Outbox;提交后发布到内部事件总线';
create index idx_outbox_aggregate
on model_platform.outbox_events (aggregate_type, aggregate_id, created_at);
create index idx_outbox_idempotency
on model_platform.outbox_events (idempotency_key);
create index idx_outbox_pending
on model_platform.outbox_events (event_status, available_at, created_at);
create table model_platform.permissions
(
permission_id char(26) not null
primary key,
permission_code varchar(128) not null,
permission_name varchar(100) not null,
module_code varchar(64) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
description varchar(500) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_permissions_code
unique (permission_code)
)
comment '权限点';
create index idx_permissions_module
on model_platform.permissions (module_code);
create table model_platform.role_permissions
(
role_id char(26) not null,
permission_id char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
primary key (role_id, permission_id)
)
comment '角色权限';
create index fk_role_permissions_permission
on model_platform.role_permissions (permission_id);
create table model_platform.roles
(
role_id char(26) not null
primary key,
role_code varchar(64) not null,
role_name varchar(100) not null,
role_scope varchar(16) not null comment 'platform/workspace',
is_builtin tinyint(1) default 0 not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
description varchar(500) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_roles_code
unique (role_code)
)
comment '角色';
create table model_platform.schedule_edges
(
edge_id char(26) not null
primary key,
schedule_id char(26) not null,
source_node_id char(26) not null,
target_node_id char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
condition_expr varchar(1000) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_schedule_edges_pair
unique (schedule_id, source_node_id, target_node_id)
)
comment 'DAG 有向边';
create index fk_schedule_edges_source
on model_platform.schedule_edges (source_node_id);
create index idx_schedule_edges_target
on model_platform.schedule_edges (target_node_id);
create table model_platform.schedule_node_runs
(
node_run_id char(26) not null
primary key,
run_id char(26) not null,
node_id char(26) not null,
versions_id char(26) not null,
attempt_no int default 1 not null,
node_status varchar(24) default 'queued' not null comment 'queued/running/succeeded/failed/skipped/cancelled/timed_out',
state_version int default 0 not null comment '乐观锁版本',
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
started_at datetime(3) null,
finished_at datetime(3) null,
duration_ms bigint null,
exit_code int null,
message varchar(2000) null,
metrics_json json null,
logs_object_id char(26) null,
result_object_id char(26) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_node_runs_attempt
unique (run_id, node_id, attempt_no)
)
comment '调度节点运行与重试';
create index fk_node_runs_logs
on model_platform.schedule_node_runs (logs_object_id);
create index fk_node_runs_node
on model_platform.schedule_node_runs (node_id);
create index fk_node_runs_result
on model_platform.schedule_node_runs (result_object_id);
create index idx_node_runs_status
on model_platform.schedule_node_runs (run_id, node_status);
create index idx_node_runs_version
on model_platform.schedule_node_runs (versions_id);
create table model_platform.schedule_nodes
(
node_id char(26) not null
primary key,
schedule_id char(26) not null,
node_key varchar(64) not null comment '画布内稳定标识',
node_name varchar(255) not null,
versions_id char(26) not null,
python_version varchar(8) default '3.12' not null comment '节点执行 Python 版本(3.8/3.10/3.12',
timeout_seconds int default 600 not null,
retry_count int default 0 not null,
retry_interval_sec int default 5 not null,
position_x decimal(10, 2) default (0.00) not null,
position_y decimal(10, 2) default (0.00) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
arguments_json json null,
env_refs_json json null comment '只存密钥引用,不存明文密钥',
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_schedule_nodes_key
unique (schedule_id, node_key)
)
comment 'DAG 节点,必须引用稳定版本';
create index idx_schedule_nodes_version
on model_platform.schedule_nodes (versions_id);
create table model_platform.schedule_runs
(
run_id char(26) not null
primary key,
schedule_id char(26) not null,
workspace_id char(26) not null,
workflow_version int not null,
trigger_type varchar(16) not null comment 'manual/cron/api/retry',
idempotency_key varchar(128) not null,
run_status varchar(24) default 'queued' not null comment 'queued/running/succeeded/failed/cancelled/timed_out',
state_version int default 0 not null comment '乐观锁版本',
schedule_snapshot json not null comment '执行时 DAG 快照',
queued_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
triggered_by char(26) null,
started_at datetime(3) null,
finished_at datetime(3) null,
duration_ms bigint null,
error_code varchar(64) null,
error_message text null,
logs_object_id char(26) null,
result_object_id char(26) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_schedule_runs_idempotency
unique (idempotency_key)
)
comment '调度运行';
create index fk_schedule_runs_logs
on model_platform.schedule_runs (logs_object_id);
create index fk_schedule_runs_result
on model_platform.schedule_runs (result_object_id);
create index fk_schedule_runs_user
on model_platform.schedule_runs (triggered_by);
create index idx_schedule_runs_schedule
on model_platform.schedule_runs (schedule_id, created_at);
create index idx_schedule_runs_status
on model_platform.schedule_runs (run_status, queued_at);
create index idx_schedule_runs_workspace_status
on model_platform.schedule_runs (workspace_id, run_status, queued_at);
create table model_platform.schedules
(
schedule_id char(26) not null
primary key,
workspace_id char(26) not null,
schedule_name varchar(255) not null,
trigger_type varchar(16) default 'cron' not null comment 'manual/cron/api',
timezone varchar(64) default 'Asia/Shanghai' not null,
enabled tinyint(1) default 0 not null,
workflow_version int default 1 not null,
max_concurrency int default 1 not null,
failure_policy varchar(24) default 'stop' not null comment 'stop/continue',
created_by char(26) not null,
updated_by char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
description varchar(1000) null,
cron_expression varchar(128) null,
last_run_at datetime(3) null,
next_run_at datetime(3) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null
)
comment '调度方案';
create index fk_schedules_created_by
on model_platform.schedules (created_by);
create index fk_schedules_updated_by
on model_platform.schedules (updated_by);
create index idx_schedules_due
on model_platform.schedules (enabled, next_run_at);
create index idx_schedules_workspace
on model_platform.schedules (workspace_id, enabled, updated_at);
create table model_platform.scripts
(
script_id char(26) not null
primary key,
workspace_id char(26) not null,
current_object_id char(26) not null comment '当前工作副本',
owner_user_id char(26) not null,
script_name varchar(255) not null,
script_type varchar(16) not null comment 'python/notebook',
visibility varchar(16) default 'private' not null,
status varchar(16) default 'active' not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
is_deleted tinyint(1) default 0 not null,
is_locked tinyint(1) default 1 not null,
deleted_at datetime(3) null
)
comment '可执行 Python/Notebook 脚本';
create index idx_scripts_owner
on model_platform.scripts (owner_user_id, status);
create index idx_scripts_workspace
on model_platform.scripts (workspace_id, script_type, visibility, status);
create table model_platform.storage_objects
(
storage_object_id char(26) not null
primary key,
workspace_id char(26) not null,
object_type varchar(16) not null comment 'file/directory',
usage_type varchar(32) not null comment 'working_copy/public_script/data_resource/version_artifact/snapshot/run_log/run_result',
storage_backend varchar(16) not null comment 's3',
storage_uri varchar(1500) not null,
file_name varchar(255) not null,
size_bytes bigint default 0 not null,
visibility varchar(16) default 'private' not null comment 'private/workspace/public',
is_immutable tinyint(1) default 0 not null,
object_status varchar(24) default 'available' not null comment 'uploading/available/deleting/deleted/failed',
created_by char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
owner_user_id char(26) null,
relative_path varchar(1024) null comment 'Workspace 相对路径',
path_hash binary(32) null comment 'SHA-256(relative_path),由应用写入',
bucket_name varchar(128) null,
object_key varchar(1024) null,
object_key_hash binary(32) null comment 'SHA-256(object_key),由应用写入',
object_key_hash_active binary(32) as ((case
when (`object_status` = _utf8mb4'available') then `object_key_hash`
else NULL end)) comment 'VIRTUAL generated column used by uk_storage_bucket_key_active',
file_extension varchar(32) null,
mime_type varchar(255) null,
content_hash char(64) null comment 'SHA-256 hex',
object_etag varchar(255) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
trash_key varchar(1100) null comment 'Path inside the trash bucket where the soft-deleted bytes live. Format: ''{source_bucket}/{object_key}'' so a restore is a same-key copy back to the source bucket. NULL while the row is still available.',
constraint uk_storage_bucket_key_active
unique (storage_backend, bucket_name, object_key_hash_active)
)
comment 'Workspace 文件和 RustFS 对象的统一元数据;目录树走 materialized path (relative_path),不要 join 邻接表列——已删除。';
create index fk_storage_created_by
on model_platform.storage_objects (created_by);
create index idx_storage_content_hash
on model_platform.storage_objects (content_hash);
create index idx_storage_owner
on model_platform.storage_objects (owner_user_id, object_status);
create index idx_storage_workspace_path
on model_platform.storage_objects (workspace_id, storage_backend, path_hash);
create index idx_storage_workspace_relative_path
on model_platform.storage_objects (workspace_id, relative_path(255));
create index idx_storage_workspace_usage
on model_platform.storage_objects (workspace_id, usage_type, object_status);
create table model_platform.upload_sessions
(
upload_id char(26) not null
primary key,
workspace_id char(26) not null,
user_id char(26) not null,
idempotency_key varchar(128) not null,
bucket_name varchar(128) not null,
object_key varchar(1024) not null,
object_key_hash binary(32) not null,
upload_status varchar(24) default 'created' not null comment 'created/uploading/completed/expired/aborted/failed',
expires_at datetime(3) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
multipart_upload_id varchar(255) null,
expected_size_bytes bigint null,
expected_hash char(64) null,
content_type varchar(255) null,
storage_object_id char(26) null,
completed_at datetime(3) null,
file_name varchar(255) default '' not null,
usage_type varchar(32) default 'working_copy' not null comment 'data_resource/version_artifact/snapshot/run_log/run_result/working_copy/public_script',
visibility varchar(16) default 'private' not null comment 'private/workspace/public',
is_immutable tinyint(1) default 0 not null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_upload_sessions_idempotency
unique (idempotency_key)
)
comment 'RustFS 预签名上传会话;URL 本身不持久化';
create index fk_upload_sessions_storage_object
on model_platform.upload_sessions (storage_object_id);
create index fk_upload_sessions_user
on model_platform.upload_sessions (user_id);
create index idx_upload_sessions_expiry
on model_platform.upload_sessions (upload_status, expires_at);
create index idx_upload_sessions_object_key
on model_platform.upload_sessions (bucket_name, object_key_hash);
create index idx_upload_sessions_workspace
on model_platform.upload_sessions (workspace_id, user_id, created_at);
create table model_platform.users
(
user_id char(26) not null
primary key,
username varchar(64) not null,
display_name varchar(100) not null,
password_hash varchar(255) not null,
status varchar(16) default 'active' not null comment 'active/disabled/locked',
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
email varchar(255) null,
platform_role_id char(26) null,
avatar_uri varchar(1000) null,
last_login_at datetime(3) null,
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_users_email
unique (email),
constraint uk_users_username
unique (username)
)
comment '平台用户';
create index fk_users_platform_role
on model_platform.users (platform_role_id);
create index idx_users_status
on model_platform.users (status);
create table model_platform.versions
(
versions_id char(26) not null comment '稳定版本唯一 ID'
primary key,
workspace_id char(26) not null,
script_id char(26) not null,
source_object_id char(26) not null comment '发布时的源对象',
artifact_object_id char(26) not null comment 'S3 不可变版本制品',
version_no int not null,
version_label varchar(32) not null comment '例如 v1.0',
source_path varchar(1024) not null comment '发布时路径快照',
artifact_path varchar(1500) not null,
content_hash char(64) not null,
file_size_bytes bigint default 0 not null,
visibility varchar(16) default 'private' not null,
created_by char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
release_note varchar(1000) null,
schedule_hidden_at datetime(3) null comment '从调度稳定版本列表移除的时间;不影响版本和运行历史',
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_versions_artifact
unique (artifact_object_id),
constraint uk_versions_script_hash
unique (script_id, content_hash),
constraint uk_versions_script_no
unique (script_id, version_no)
)
comment '不可变稳定版本;调度节点必须引用 versions_id';
create index fk_versions_source_object
on model_platform.versions (source_object_id);
create index idx_versions_creator
on model_platform.versions (created_by, created_at);
create index idx_versions_workspace_created
on model_platform.versions (workspace_id, created_at);
create table model_platform.workspace_members
(
workspace_id char(26) not null,
user_id char(26) not null,
role_id char(26) not null,
member_status varchar(16) default 'active' not null,
joined_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
primary key (workspace_id, user_id)
)
comment 'Workspace 成员与角色';
create index idx_workspace_members_role
on model_platform.workspace_members (role_id);
create index idx_workspace_members_user
on model_platform.workspace_members (user_id, member_status);
create table model_platform.workspaces
(
workspace_id char(26) not null
primary key,
workspace_code varchar(64) not null,
workspace_name varchar(150) not null,
active_root_uri varchar(1500) not null comment '活动工作区,建议 NFS/PVC/file URI',
quota_bytes bigint default 0 not null comment '0 表示不限额',
used_bytes bigint default 0 not null,
status varchar(24) default 'active' not null comment 'creating/active/suspended/deleting/deleted',
created_by char(26) not null,
created_at datetime(3) default CURRENT_TIMESTAMP(3) not null,
updated_at datetime(3) default CURRENT_TIMESTAMP(3) not null on update CURRENT_TIMESTAMP(3),
description varchar(1000) null,
artifact_bucket varchar(128) null comment 'S3 bucket',
artifact_prefix varchar(512) null comment 'S3 object key prefix',
is_deleted tinyint(1) default 0 not null,
deleted_at datetime(3) null,
constraint uk_workspaces_code
unique (workspace_code)
)
comment 'Workspace';
create index fk_workspaces_created_by
on model_platform.workspaces (created_by);
create index idx_workspaces_status
on model_platform.workspaces (status);