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 }