feat: prepare ARR for controlled public deployment

This commit is contained in:
Wyndham ARR
2026-07-29 16:38:05 +08:00
commit a701de9f0e
271 changed files with 48472 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
# ARR 数据落库 MVP v1 数据字典
更新时间2026-07-28
权威 DDL`database/008_arr_mvp_v1_rebuild.sql`
已部署目标:`<ARR_DB_HOST>:5432/booking_test`(远程测试库)
## 1. 模型边界
数据库只保存三类内容:
1. `ingestion`:私有 OSS/本地 fixture 工件身份、Agent 处理运行、交付验收与 outbox
2. `booking`:预订附件来源行、行级 Agent 解析版本和房型明细;
3. `finance`:日报不可变版本、每条 XML 记录的全部 outcome、current 指针和渠道计数。
月报、渠道明细和每 10 日公司用房表均从只读视图/普通程序生成,不建 `report_versions` 或报表行表。第一版不包含多酒店、账户权限、RSVN 任务卡、邮件元数据和前端追溯页面。
统一类型:主键为 `bigint identity`,业务日期为 `date`,时间为 `timestamptz`,金额为 `numeric(18,2)`,完整 JSON 为 `jsonb`
## 2. `ingestion` schema
### `ingestion.artifacts`
只保存不可变私有对象的元数据,不保存文件字节、签名 URL 或凭据。
| 字段 | 类型 | 含义 |
|---|---|---|
| `id` | bigint | 工件 ID |
| `artifact_kind` | text | `booking_source_md``booking_excel``opera_xml``daily_xlsx``monthly_xlsx``channel_detail_xlsx``company_ten_day_xlsx``exception_xlsx``result_json``structured_result_json` |
| `storage_provider` | text | `oss``s3``local_fixture` |
| `bucket_alias` | text | 内部 bucket 别名,不含路径符 |
| `object_key` | text | 私有对象键;禁止绝对路径、`..`、查询串和 fragment |
| `object_version_id` | text? | 对象存储版本号 |
| `etag` | text? | provider ETag内容身份仍以 SHA-256 为准 |
| `original_filename` | text | 安全原文件名 |
| `sha256` | char(64) | 内容哈希 |
| `byte_size` | bigint | 文件大小,允许 0 |
| `mime_type` | text? | MIME |
| `created_at` | timestamptz | 登记时间 |
唯一性:`artifact_kind + sha256`;同时约束 provider/bucket/object/version 身份唯一。
### `ingestion.processing_runs`
ARR 拥有的一次逻辑处理运行,不是 RSVN 任务卡。
关键字段:`run_key``pipeline_type``opera_daily`/`booking_source_import`)、源/结果工件 ID、`run_status`、请求及交付 processor/rule/schema 版本、`delivery_sha256``delivery_json``business_date`、安全失败码/消息和完整时间线。
终态规则accepted 的 Opera 日报必须具备结果工件、营业日、交付版本、规则哈希和交付哈希rejected/failed 必须有失败码。
### `ingestion.processing_attempts`
一次运行的可重试尝试。
关键字段:`processing_run_id``attempt_no``attempt_status`、64 位 `idempotency_key`、可选 `remote_run_id`、安全失败字段和时间线。运行内 attempt 序号唯一idempotency key 全局唯一。
### `ingestion.processing_deliveries`
ARR 收到并验收的 DeliveryEnvelope。
关键字段:`delivery_key`、run/attempt、`envelope_sha256`、完整 `envelope_json``delivery_status``result_status`、processor/rule/schema、营业日、五类输入/输出工件 FK、`daily_version_id`、失败字段和接收/验收/提交时间。
同一个 delivery key 只能代表同一份交付;相同 attempt + envelope 哈希不得重复。
### `ingestion.outbox_events`
与业务事务一起提交的隐私最小化可靠事件。
关键字段:`event_key``aggregate_type``aggregate_id``event_type``payload`、发布状态/次数/可用时间/发布时间和最后失败码。payload 只放 opaque ID、日期、计数和安全状态不放住客字段。
## 3. `booking` schema
### `booking.source_batches`
一份预订 Excel 或当前 MD fixture 的导入批次。
字段:`source_artifact_id``source_kind``expected_fixture`/`booking_excel`)、`source_format_version`、可选参考年月、`batch_status`、source/accepted/failed 计数、失败字段和时间线。一个工件只能形成一个批次。
### `booking.source_rows`
一行对应原文件中的一行,重复 Group Code 也必须分行保存。
| 字段 | 类型 | 含义 |
|---|---|---|
| `id` | bigint | 来源行 ID |
| `source_batch_id` | bigint | 所属导入批次 |
| `source_worksheet` | text | 原工作表 |
| `source_row_no` | integer | 原行号 |
| `group_code_raw` | text | 原始 Group Code/Tour Code |
| `group_code_key` | text generated | `upper(btrim(group_code_raw))`,精确关联键 |
| `type_of_room_raw` | text | 原始 `TYPE OF ROOM`/`โรงแรม` 文本 |
| `no_of_rooms` | integer | 该来源行总房数,必须大于 0 |
| `source_row_sha256` | char(64) | 来源坐标和业务值的确定性哈希 |
| `created_at` | timestamptz | 落库时间 |
唯一性只针对“批次 + worksheet + 行号”和“批次 + 行哈希”,不把 Group Code 设为唯一。
### `booking.parse_versions`
一条来源行的不可变 Agent 解析版本。
字段:`source_row_id``version_no``parse_status``accepted`/`needs_review`/`failed`)、结果 Schema、processor 名称/版本、规则哈希、输入行哈希、结果哈希、完整已验收 `result_json`、失败字段和验收时间。版本唯一键为 `source_row_id + version_no`
精确 JSON 合同:`database/contracts/booking-row-parse-result.schema.json`
### `booking.room_items`
解析后可按 Group Code 查询的房型项目;一条来源行可拆出多项。
| 字段 | 类型 | 含义 |
|---|---|---|
| `parse_version_id` | bigint | 所属解析版本 |
| `item_no` | integer | Agent 输出顺序 |
| `room_type_raw` | text | 原始房型显示 |
| `room_type_code` | text? | 可选标准房型码 |
| `quantity` | integer | 房数,必须大于 0 |
| `unit_price` | numeric? | 解析单价;未知可为空 |
| `currency_code` | char(3)? | ISO 三位大写币种 |
| `price_token_raw` | text? | 例如 `12`,用于解释 1200 |
| `source_fragment` | text? | 对应原文片段 |
| `created_at` | timestamptz | 落库时间 |
同一解析版本内 `item_no` 唯一。
### `booking.current_row_parses`
每条来源行唯一指向一个当前 accepted parse。触发器同时验证批次已 accepted、parse 已 accepted、所有 room item 数量合计等于来源行 `no_of_rooms`
### Booking 视图
- `booking.v_current_room_items`:逐来源行返回当前 accepted 的全部 room item 和来源坐标。
- `booking.v_group_room_item_summary`:查询时按 `group_code_key + 房型` 汇总数量;不修改原始行。
- `booking.v_group_booking_rooms`:生成 `booking_room` 展示串、总预订房数和命中来源行数。
## 4. `finance` schema
### `finance.daily_versions`
一次 ARR 验收结果形成一个不可变日报版本。
字段:营业日/版本号、`processing_run_id`、源 XML/日报/result/structured-result/异常工件 FK、`version_status`、processor/rule/schema、`result_sha256`、五类 outcome 计数、失败字段和创建/验收/激活/替换时间。
五类计数必须严格满足:
```text
source_rows = retained_rows
+ excluded_rate_code_rows
+ duplicate_rows
+ validation_failed_rows
+ price_unmatched_rows
```
验收成功版本状态为 validated/active/superseded失败交付可保存为 rejected但绝不能成为 current。
### `finance.daily_records`
保存 structured-result 中每一条 XML reservation包括未进入正式日报的记录。
来源与处理字段:
| 字段 | 含义 |
|---|---|
| `daily_version_id` | 所属不可变版本 |
| `source_sequence` | Agent 连续源序号 |
| `source_location` | 如 `reservation[1]` |
| `source_worksheet/source_row_no` | Excel 型输入坐标;直接 XML 必须同时为空 |
| `outcome` | `retained``excluded_rate_code``duplicate``validation_failed``price_unmatched` |
| `decision_codes` | 处理决定码数组 |
| `duplicate_of_record_id` | duplicate 指向同版本原记录 |
日报业务字段:
| 字段 | 类型 |
|---|---|
| `block_code` | text |
| `adults` / `children` | integer |
| `company_name` / `company_key` | text |
| `confirmation_no` | text |
| `disp_room_no` | text |
| `effective_rate_amount` | numeric(18,2) |
| `full_name` | text |
| `res_comment` | text |
| `group_code_key` | generated`upper(btrim(res_comment))`;空值为 null |
| `trace_text` | text |
| `no_of_rooms` | integer |
| `products` | text |
| `rate_code` | text |
| `normalized_rate_code` | generated规范化 rate code |
| `room_category_label` | text |
| `arrival` / `departure` | date |
| `nights` | integer |
| `real_price` / `total_price` / `kb_amount` | numeric(18,2) |
| `channel_key` / `pricing_method` | text |
预订关联字段:`booking_source_match_status`matched/unmatched/missing_group_code/not_checked`booking_source_match_count`。重复 Group Code 命中多条来源行仍是 matched并记录实际命中数。
retained 记录强制约束:日期/晚数一致、金额非负、去重键完整、预订查询已检查,并且:
```text
total_price = real_price × no_of_rooms × nights
```
### `finance.daily_channel_metrics`
每个日报版本的渠道名称、确定性顺序和 retained 行数。主键为 `daily_version_id + channel_key`;同版本 `channel_order` 唯一。
### `finance.current_daily_versions`
每个营业日唯一指向当前 active 版本。触发器阻止指向未激活或日期不一致的版本。同日修正先完整落新版本,最后原子切换该指针。
## 5. 业务只读视图
- `finance.v_active_daily_facts`:仅 current daily version + `outcome='retained'`;所有普通业务查询的权威源。
- `finance.v_monthly_report_rows`:月报 20 列投影;`RATE_AMOUNT``ROOM_CATEGORY` 为字段别名,`Booking Room` 来自预订 Group Code 查询,`TOTAL PRICE` 直接使用日报值。
- `finance.v_channel_details`ARRIVAL、DEPARTURE、NIGHTS、BLOCK_CODE、RES_COMMENT、Booking Room、Total Booking Price 等Total Booking Price 等于该日报行 `total_price`,不按 Group Code 二次汇总。
- `finance.v_company_report_source`:公司 10 日报表程序使用的 Finance 日期/价格事实和预订房型补充。
- `finance.v_daily_processing_audit`:当前与历史版本的全部 outcome、决定码、duplicate lineage、源文件哈希、processor/rule/schema 和验收时间;用于以后追溯页面。
## 6. 精确关联规则
```text
upper(trim(finance.daily_records.RES_COMMENT))
= booking.source_rows.group_code_key
```
禁止使用 `BLOCK_CODE` 代替,禁止模糊匹配。预订数据只提供 Booking RoomFinance 自己提供到离日期、晚数、实际房数、价格和 `total_price`