Files
group_fqcd_jr/docs/28-场外与推广域数据表登记.md
T

159 lines
7.4 KiB
Markdown
Raw Normal View History

# 场外与推广域数据表登记(`docs/00` 基线的域外补充)
> **用途**:`docs/00-新数据库基线设计.md` 冻结的是**场内基金模拟交易域**的基线(不可变,规则 1)。
> 同事那条线(袁聪)合并进来的 **17 张表属场外基金运营域与推广域**,**不进 `docs/00`**,
> 由本文单独登记,避免"基线"这个概念因为塞入独立域而失效。
>
> **维护约定**:新增/删除这两个域的表时**必须同步本文**;本文是「**场外/推广这 17 张**从哪来」的唯一答案。
>
> ⚠️ **表数口径(2026-09-14 更正)**:本文此前写作
> `场内 51 + 场外/推广 17 = 68(共 69 张含 alembic_version)`,
> 那是**投顾域尚未并入时**的口径。当前全库已为:
>
> ```
> 场内 51(docs/00) + 场外/推广 17(本文) + 投顾 21(登记文档待补) = 89 张业务表
> (另加 alembic_version,库内共 90 张)
> ```
>
> **本文只负责场外/推广那 17 张**(10 张 `offsite_*` + 7 张 `promotion_*`),
> 逐表登记内容本身**未变、无缺陷**;投顾 21 张不在本文范围,其登记文档待补。
> 核验一律以 `python tools/audit_schema.py` 的实时输出为准。
---
## 1. 为什么另立文档(架构师裁决,2026-09-11)
依据 `AGENTS.md` 规则 8:
> 当前系统业务功能只针对场内基金模拟交易;**场外基金运营流程独立,不得写入场内交易表**。
⇒ 场外与推广是**独立域**。把它们塞进 `docs/00` 会让"基线"从"场内交易域的冻结定义"
变成"当前全部表",下次有人拿 `docs/00` 当依据时会错。因此:
| 文档 | 职责 |
|---|---|
| `docs/00-新数据库基线设计.md` | **场内交易域**基线(不可变,51 张) |
| **本文** | 场外/推广域(17 张)的登记与来源 |
| `docs/08-数据库结构审计基线.md` | 审计口径:场内 51 + 场外/推广 17 = 68 |
| `tools/audit_schema.py` | 期望值同为 68(已生效,见 §4) |
---
## 2. 17 张表逐表登记
### 2.1 场外基金运营域(`offsite_*`,10 张)
| 表名 | 来源迁移 | 建立它的提交意图 |
|---|---|---|
| `offsite_fund_mail` | `20260910_offsite_fund` | 场外申购邮件记录(运营入口) |
| `offsite_fund_attachment` | `20260910_offsite_fund` | 邮件附件(**该域唯一有数据的表,见 §3**) |
| `offsite_fund_document` | `20260910_offsite_fund` | 识别后的业务单据 |
| `offsite_execution_plan_task` | `20260910_offsite_fund` | 执行计划任务 |
| `offsite_rule_result` | `20260910_offsite_fund` | 规则校验结果 |
| `offsite_query_record` | `20260910_offsite_fund` | NL2SQL 查询记录 |
| `offsite_notification` | `20260910_offsite_fund` | 对外通知 |
| `offsite_mail_cursor` | `20260910_offsite_worker` | 邮件 Worker 的游标状态 |
| `offsite_recognition_attempt` | `20260910_offsite_recognition_attempt` | 识别尝试追踪(OCR/LLM 各一次尝试) |
| `offsite_field_correction` | `20260911_offsite_field_correction` | 人工字段纠正记录 |
### 2.2 推广域(`promotion_*`,7 张)
| 表名 | 来源迁移 | 用途 |
|---|---|---|
| `promotion_material_task` | `20260910_promotion_material` | 推广素材任务(主表) |
| `promotion_material_version` | `20260910_promotion_material` | 素材版本 |
| `promotion_input_snapshot` | `20260910_promotion_material` | 生成输入快照(可复现) |
| `promotion_attachment` | `20260910_promotion_material` | 素材附件(海报/PPT) |
| `promotion_compliance_check` | `20260910_promotion_material` | 合规校验结果 |
| `promotion_review_record` | `20260910_promotion_material` | 审核记录 |
| `promotion_delivery_record` | `20260910_promotion_material` | 投递记录 |
### 2.3 迁移文件清单(11 个,含 6 个 merge/辅助)
```
建表相关:20260910_offsite_fund / _offsite_worker / _offsite_recognition_attempt /
_promotion_material / _promotion_poster / 20260911_offsite_field_correction
合并链: 20260910_merge_heads / 20260911_merge_review_separation /
20260911_merge_risk_heads(当前 head)
其他: 20260910_offsite_worker_identity / 20260911_drop_review_separation
```
> ⚠️ **两个环境都踩过的坑**:本库曾处于"`alembic_version` 已指向同事的 revision,
> 但 `offsite_*`/`promotion_*` 表一张都没有"的状态(**版本号跑了、表没建**);
> 架构师那边则是"表没建、版本号也没跑"。**两种坏状态都靠 `alembic upgrade heads` 收敛。**
---
## 3. 当前数据量(2026-09-11 实测)
| 表 | 行数 |
|---|---|
`offsite_fund_attachment` | **60** |
其余 16 张 | **0** |
> 说明:只有附件表有数据(同事调试/演示时落盘的附件)。**这不是"该域已在生产运行"的证据**,
> 也不代表其它表可以删——**禁止删表**(规则 3)。
---
## 4. 跨域边界核对(规则 8 的两向检查)
### 4.1 场内代码是否引用这 17 张表 → **否** ✅
`app/` 下搜 `offsite_|promotion_` 只有 12 处命中,**全部是 `app/core/config.py` 里的环境变量名**
(`offsite_mailbox`、`offsite_allowed_senders` 等),**没有任何 SQL/ORM 级引用**。
场内模型(`app/model/fund.py` 等)与只读契约(`FUND_TABLES`)都只覆盖 `fin_*`。
### 4.2 场外代码是否写场内交易表 → **否** ✅
在 `app/service/offsite*.py` 与 `app/model/offsite*.py` 里搜
`fin_sim_order|fin_capital_flow|fin_cash_ledger|fin_transaction|fin_holding` —— **零命中**。
场外域只读写自己的 `offsite_*` 表。
⇒ **两个方向都不越界**,符合规则 8。这一点在合并评审时是要点,故在此留档。
---
## 5. 对审计工具的影响(**不是"有人偷偷建表"**)
```
$ .\.venv\Scripts\python.exe tools\audit_schema.py
schema audit passed: 68 business tables, no missing or unexpected tables
```
**表数从 51 跳到 68 是同事那 11 个迁移的**预期结果**,不是有人绕过基线偷偷建表。**
机制要说清楚(否则会被误读成"期望值被手工改过"):`tools/audit_schema.py` 的期望表集合
是**动态推导**的,不是一份手写清单:
```python
def expected_tables() -> set[str]:
tables = <从 alembic/baseline_generated.sql 正则提取 CREATE TABLE>
for path in (alembic/versions/*.py):
tables |= <从每份迁移里正则提取 CREATE TABLE> # ← 17 张新表由此自动进入
return tables
```
⇒ **期望值随迁移自动增长**,没有人手工改过它。优点是不会漏;**代价**是它只增不减 ——
若将来有迁移 **DROP** 掉某张表,期望集合仍会包含它,审计会报 `missing` 的假失败。
真要删表时(记得规则 3:**禁止删除已有表**)需同步调整该工具。
> ⚠️ 若你那边 `audit_schema.py` 报 `unexpected`,先确认是"库里多表"还是"迁移没进来"——
> 前者是绕过基线的违规,后者只是迁移没合。两者处置完全不同。
---
## 6. 与 `docs/00` 的关系(一句话)
**`docs/00` 一个字段、一张表都没动**(规则 1)。本文是它的**域外补充登记**,
两者合起来才是"当前库里的全部表":
```
docs/00(场内 51) + 本文(场外/推广 17) = 68 张业务表
```
---
*登记时点:2026-09-11 登记依据:现库 `information_schema` 实测 + `alembic/versions` 逐文件核对 +
`tools/audit_schema.py` 的期望值推导机制核对。*