Files
wyndham-ARR/PROJECT_GUIDE.md
2026-09-06 13:11:21 +08:00

353 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ARR 2.0 项目说明与交接指南
> 文档基线2026-09-06。本文说明仓库当前代码的职责、架构、运行方式和交接边界。生产环境是否已经运行同一版本必须通过发布记录、进程启动时间、健康检查和受控验收单独确认。
## 1. 项目概述
ARR 2.0 是面向酒店 Finance 团队的 Opera 数据接入与报表自动化系统。系统接收 Opera `RES_DETAIL` XML 和 Booking 原始 Excel在固定规则下完成解析、校验、数据库提交、月报发布、公司渠道明细生成与渠道 BI 展示。
项目的核心原则是:业务用户只提供源文件,内部处理参数由系统从已提交的业务事实中推导。生产 XML 主链由 ARR 自身的确定性程序完成,不依赖 Agent、MCP、Prompt 或公开源文件 URL。
### 主要使用者
- 酒店 Finance 操作员:登录、上传 XML、处理缺价复核、下载日报与月报。
- 公司渠道报表操作员:上传 Booking Excel、核对房型、生成公司渠道明细。
- BI 使用者:查看按月份、公司、渠道和房型汇总的数据。
- 运维与开发人员:维护 PostgreSQL、OSS、Web、月报 worker 和部署环境。
### 项目不做什么
- 不让业务用户手工输入月报年份、月份或“更新至”日期。
- 不让月报程序再次解析 XML、执行白名单、去重、定价或公司匹配。
- 不把真实登录凭据、数据库 DSN、OSS Secret 或住客隐私写入仓库、日志或浏览器响应。
- 不把远端 Agent/MCP 的成功响应当作 Finance 已提交ARR 2.0 的生产 XML 主链不使用该路径。
## 2. 系统全景
```text
┌──────────────────────────────┐
│ ARR Web / Caddy │
│ 登录、上传、复核、下载、BI │
└──────────────┬───────────────┘
┌─────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
Opera RES_DETAIL XML Booking 原始 Excel 只读查询/下载
│ │ │
▼ ▼ │
固定处理器 + 独立验证器 提取草稿 + 人工核对 │
│ │ │
▼ ▼ │
Finance 原子版本提交 当前 Booking 完整来源 │
│ │ │
├──────────────┬──────────┴──────────────┐ │
▼ ▼ ▼ │
outbox 月报事件 渠道 BI 公司渠道明细任务 │
│ │ │
▼ ▼ │
独立 monthly worker Python/openpyxl │
│ │ │
└───────────────┬────────────────────────┘ │
▼ │
私有 OSS 报表工件 ◄───────────────────────────┘
```
### 权威状态与存储职责
| 层 | 权威内容 |
|---|---|
| PostgreSQL | 任务、处理尝试、复核生命周期、Finance 当前事实、Booking 当前来源、outbox、月报发布元数据 |
| OSS | 源文件、日报、月报、公司报表、结果 JSON 与冻结人工价格清单等不可变工件 |
| Web | 登录会话、CSRF、上传与复核入口、只读查询、受控下载和页面交互 |
| monthly worker | 领取成功提交后的 outbox 事件,生成并发布月报 |
| `outputs/` | staging、历史本地兼容缓存和公司任务 `.web-jobs` 状态;不是新报表的最终权威存储 |
## 3. 核心业务流程
### 3.1 登录与访问边界
1. 桌面门户、详细健康信息、业务 API、上传、任务日志和下载默认需要登录。
2. 登录账号和密码只从 `ARR_WEB_USERNAME``ARR_WEB_PASSWORD` 注入;缺少任一变量时 Web 拒绝启动。
3. 会话由服务端保存Cookie 使用 `HttpOnly``SameSite=Strict`HTTPS 环境必须启用 `--secure-cookies`
4. 所有已登录写操作还需要 CSRF token。
5. 公共例外只有 H5 页面及其专用聚合接口和无详情 `/healthz`;公共投影不返回源哈希、对象键或运营元数据。
### 3.2 Opera XML → 日报 → Finance
1. 浏览器上传 Opera `RES_DETAIL` XML。
2. ARR 校验文件名、内容和大小,将源文件保存为私有不可变 OSS 对象。
3. 固定版本处理器在隔离临时目录运行,生成日报 XLSX、结果 JSON、结构化 JSON或者正式失败/缺价复核结果。
4. 独立验证器校验 Schema、SHA-256、字节数、MIME、行数、业务恒等式和日报重放结果。
5. 验证成功后ARR 在一个数据库事务中写入并激活 Finance 日版本,同时创建 `arr.daily_version_committed` outbox 事件。
6. PostgreSQL 终态是成功与否的权威HTTP 或控制台输出不能替代数据库提交证据。
上传接口为了兼容页面仍返回 HTTP `202`,但响应时初始处理已经收敛到 `succeeded``needs_review``failed`
### 3.3 纯缺价人工复核
只有非空错误集合全部为 `PRICE_UNMATCHED` 时,任务才进入人工复核:
- 初始阶段不生成可下载日报,不写 Finance 版本,也不创建失败事件或月报事件。
- 操作员只能为已验证的“公司 + Rate Code + Opera price”键填写非负整数显式 `0` 合法。
- 数据库存为精确两位小数,冻结清单把整数规范化为如 `0.00` 的文本。
- 确认生成时,系统将清单绑定到 job、case、源文件 SHA、业务日期、处理器/规则身份和完整键集合。
- 系统重新物化原 XML用同一固定处理器重放并独立验证只有最终重放成功才提交 Finance 和 outbox。
- 基础设施错误保留冻结清单以便重试;确定性错误关闭任务。开放 case 遇到处理器或规则版本变化时,应取消后重新上传。
### 3.4 Finance → 自动月报
1. 独立 `monthly_reports.worker` 使用租约和 `FOR UPDATE SKIP LOCKED` 领取 `arr.daily_version_committed`
2. worker 从该 Finance 版本实际保留的 `ARRIVAL` 推导受影响月份。
3. “更新至”日期取当前月报快照中最大的 `ARRIVAL`,不使用 XML 文件名或服务器时间推测。
4. 程序以 repeatable-read 快照生成 XLSX 和 `result.json`,重新打开并校验工作簿。
5. 每个数据行的 `TOTAL PRICE` 必须是 Excel 公式:`REAL PRICE × NIGHTS × NO_OF_ROOMS`,当前布局形如 `=R2*C2*G2`
6. 两份工件写入 OSS、登记并激活后outbox 才标记 `published`;相同快照重放不会重复发布。
月报页面只显示持久化发布记录,并在页面可见时轮询更新。用户无需手工选择月报月份或点击生成。
### 3.5 Booking Excel → 公司渠道明细
1. 操作员上传完整原始 `.xlsx`;解析器只接受同一表头行上的两类已批准完整表头。
2. 表头经过 Unicode、大小写、空白和标点归一化后仍需整格精确匹配不做文件名、月份、工作表名、包含词或模糊匹配。
3. 提取结果先进入草稿。自动识别和人工记录均可编辑或删除pending 项不计入有效 Booking 房量。
4. 草稿必须零 pending 且至少保留一条确认记录,才能原子激活为新的当前完整来源。
5. 激活是整表替换,不是静默追加;打开的草稿会阻止创建新的公司报表任务。
6. 公司报表从当前 Finance 事实读取价格与入住信息,用规范化完整 Group Code 关联当前 Booking 房型数量。
7. 没有 Group Code 或找不到 Booking 房型时Finance 行仍保留,`Booking Room` 留空;这不是报表失败。
当前支持 `LianTai``QBD``DY-AI-Easy-KB``FengRun``HanaTour` 五家公司。公司报表由 Python/openpyxl 生成,无公式;`Total Booking Price` 直接使用 Finance 的 `TOTAL PRICE`
### 3.6 渠道 BI
渠道 BI 直接读取当前 Finance 日事实,不读取月报工作簿。聚合口径包括:
- sold rooms`sum(NO_OF_ROOMS)`
- total price`sum(TOTAL PRICE)`,不再次相乘;
- room nights`sum(NIGHTS × NO_OF_ROOMS)`
- 数据范围:同一当前版本快照中的最小/最大 `ARRIVAL`
公开 H5 只提供去隐私的聚合数据;详情接口不返回住客姓名、确认号、房号、备注、对象键、凭据或处理 trace。
## 4. 代码模块
| 路径 | 职责 |
|---|---|
| [`arr_web/`](arr_web/) | Web 路由、登录会话、上传、复核、任务日志、下载、页面和运行时装配 |
| [`arr_processing/`](arr_processing/) | 固定处理器执行、版本/规则身份、运行策略与处理回执 |
| [`arr_ingestion/`](arr_ingestion/) | 交付契约、独立验证、生命周期与 Finance 原子入库 |
| [`arr_storage/`](arr_storage/) | 私有不可变对象存储、OSS 适配、工件身份与安全物化 |
| [`booking_ingestion/`](booking_ingestion/) | Booking Excel 解析、草稿复核和当前来源激活 |
| [`monthly_reports/`](monthly_reports/) | 月报快照、生成、校验、发布和独立 worker |
| [`company_reports/`](company_reports/) | 五家公司渠道明细的生成、校验和发布 |
| [`channel_analytics/`](channel_analytics/) | 当前 Finance 事实的只读 BI 投影 |
| [`database/`](database/) | PostgreSQL 基线、增量迁移、回滚保护和应用记录 |
| [`tests/`](tests/) | 单元、契约、迁移、Web、工作簿和竖切回归测试 |
| [`deploy/`](deploy/) | Caddy、Compose 环境样例与生产部署说明 |
| [`.project-docs/`](.project-docs/) | 项目定位、ADR、架构、业务规则、工作日志、证据和遗留项 |
历史 Agent/MCP 兼容模块仍为审计和旧测试保留,但不属于 ARR 2.0 的生产 XML 主入口。
## 5. 环境要求与配置
### 基础依赖
- Python 3.12
- PostgreSQL 15+,且目标数据库必须名为 `booking_test`
- 满足区域、加密、ACL 和 versioning 约束的阿里云 OSS
- 生产部署建议使用 Docker Compose、Caddy 和独立进程管理器。
安装本地依赖:
```bash
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -r requirements.txt
cp .env.example .env.local
```
根 [`requirements.txt`](requirements.txt) 聚合处理、入库、OSS 和月报依赖。公司报表、Booking 与历史兼容路径另有分模块 requirements 文件Docker 构建会复制并安装根聚合依赖。
### 关键环境变量
| 变量 | 用途 |
|---|---|
| `ARR_WEB_USERNAME` / `ARR_WEB_PASSWORD` | Finance 操作员登录;密码至少 12 位,生产必须使用独立长随机值 |
| `ARR_DATABASE_URL` | 主数据库连接,必须指向 `booking_test` |
| `MONTHLY_REPORT_DATABASE_URL` | 可选月报角色覆盖,默认回退到 `ARR_DATABASE_URL` |
| `DASHBOARD_DATABASE_URL` | 可选 BI 只读角色覆盖,默认回退到 `ARR_DATABASE_URL` |
| `ARR_OBJECT_PREFIX` | ARR OSS 对象前缀 |
| `ARR_OSS_REGION` / `ARR_OSS_ENDPOINT` / `ARR_OSS_BUCKET` | OSS 区域、端点和 bucket |
| `OSS_ACCESS_KEY_ID` / `OSS_ACCESS_KEY_SECRET` / `OSS_SESSION_TOKEN` | OSS SDK 凭据;优先使用 RAM 角色或 STS 注入 |
以 [`.env.example`](.env.example) 为唯一公开样例。不要提交 `.env.local``deploy/.env.production`、数据库配置文件或真实 Secret。
## 6. 本地运行
`.env.local` 中的配置安全注入当前 shell 后,启动完整 Web 能力:
```bash
.venv/bin/python -m arr_web.run \
--host 127.0.0.1 \
--port 8765 \
--enable-processing \
--enable-monthly-generation \
--enable-company-reports
```
另开一个终端启动自动月报 worker
```bash
.venv/bin/python -m monthly_reports.worker \
--output-root /absolute/path/to/ARR2.0/outputs/monthly_reports
```
如果不是通过环境变量提供数据库,可使用受控文件:
```bash
.venv/bin/python -m monthly_reports.worker \
--db-config /absolute/path/to/booking-test-db.env \
--output-root /absolute/path/to/ARR2.0/outputs/monthly_reports
```
访问 `http://127.0.0.1:8765`。登录后查看 `/api/health` 的详细 readiness容器和外部探针只使用无详情 `/healthz`
`--enable-monthly-generation` 打开的是 Web 内部受控恢复入口,不替代独立 worker。日常自动月报仍以 outbox + worker 为唯一主流程。
## 7. 数据库与迁移
当前权威结构为 [`database/008_arr_mvp_v1_rebuild.sql`](database/008_arr_mvp_v1_rebuild.sql) 加 009018 增量迁移。应用迁移前必须:
1. 确认连接数据库名称严格为 `booking_test`
2. 备份并保存 schema/data manifest
3. 校验迁移 SHA-256
4. 在恢复副本或外层回滚事务中先做 up/down 探针;
5. 按迁移依赖顺序发布,不改写已应用迁移。
017 增加日报缺价复核生命周期018 增加冻结人工清单工件类型。一旦存在 review case、人工 lineage 或冻结清单,对应 down migration 会主动拒绝破坏性回滚。详细步骤见 [`deploy/README.md`](deploy/README.md) 和 [`database/APPLIED_MIGRATIONS.md`](database/APPLIED_MIGRATIONS.md)。
## 8. 测试与质量门禁
运行全量测试:
```bash
.venv/bin/python -m unittest discover -s tests -p 'test_*.py' -v
```
常用聚焦验证:
```bash
.venv/bin/python -m unittest tests.test_arr_programmatic -v
.venv/bin/python -m unittest discover -s tests -p 'test_arr_web*.py' -v
.venv/bin/python -m unittest discover -s tests -p 'test_company_reports*.py' -v
```
交付前至少检查:
- `git diff --check` 无空白或冲突标记问题;
- Markdown 内部链接存在;
- Web 和 worker CLI 可加载 `--help`
- 关键竖切测试覆盖成功、待复核、失败、重试和幂等边界;
- 生成的 XLSX 被重新打开校验,月报公式引用同一行正确字段;
- `.project-docs` 的 planning gate 和 post-task documentation gate 通过。
涉及数据库或真实文件的验收应使用无 PII 测试数据和可回滚事务;任何真实 Booking 来源激活、人工定价确认或生产上传都需要业务操作员明确授权。
## 9. 生产部署
当前 Compose 模板包含 `web``caddy`
- Web 负责应用登录、XML 处理、公司报表和下载;
- Caddy 负责 HTTPS
- 月报 worker 是独立无端口进程,必须由 systemd、容器编排器或同等级工具持续守护。
部署前:
```bash
cp deploy/.env.production.example deploy/.env.production
chmod 600 deploy/.env.production
docker compose --env-file deploy/.env.production config --quiet
docker compose --env-file deploy/.env.production build web
docker compose --env-file deploy/.env.production up -d
```
单次 worker 探针:
```bash
docker compose --env-file deploy/.env.production run --rm web \
python -m monthly_reports.worker \
--once \
--output-root /app/outputs/monthly_reports
```
部署验收应覆盖登录/退出、健康状态、无 PII XML 竖切、纯缺价隔离、Finance 唯一 active 版本、outbox、月报公式、OSS 下载哈希,以及业务失败不触发月报。完整发布和回退步骤见 [`deploy/README.md`](deploy/README.md)。
## 10. 运行观测与故障定位
建议按以下顺序定位:
1. `/healthz` 是否返回 200
2. 登录后的 `/api/health` 中 database、processing、download、company report 和 Booking source readiness
3. PostgreSQL 任务/attempt/delivery/Finance/outbox 终态;
4. OSS 工件的对象身份、大小、MIME 和 SHA-256
5. monthly worker 是否存活,以及 outbox 的 `pending``publishing``dead` 数量;
6. 公司报表 `.web-jobs` 状态与最终 OSS 发布结果。
不要从一个成功 HTTP 响应推断整个链路完成。日报成功需要 Finance 原子提交;月报成功需要两份工件登记并激活;公司报表成功需要每家公司发布结果完成。
常见边界:
- `needs_review`:纯缺价,等待人工填写,不是失败;
- `failed`:确定性业务/Schema/身份错误,不能靠盲目重试修复;
- 基础设施错误:允许按既定上限重试,但不得绕过独立验证;
- 月报未更新:先查成功日报对应 outbox 和 worker再查页面刷新
- 公司报表缺少 `Booking Room`:先区分无 Group Code、未匹配 Booking 与真正的 Finance 数据错误。
## 11. 安全、隐私与幂等要求
- 所有 ARR 新写 OSS 对象显式使用 private ACL拒绝 `public-read-write` bucket 和启用/暂停的 versioning。
- 用户文件名只作为受控显示信息,不能进入 OSS 对象键。
- 临时处理目录在请求结束后清理;下载前重新校验路径/对象身份、大小和哈希。
- 日报价格复核只保存规范化价格键、聚合影响、revision 和前后价格,不保存住客姓名、备注或原始 trace。
- 同一 delivery 精确重放幂等;相同 ID 下字节变化必须冲突。
- 相同月报/公司报表语义快照复用权威发布物;不因 XLSX ZIP 元数据变化重复发布。
- 只有可识别的 PostgreSQL 瞬时并发错误可以做有界事务重试,业务或验证错误不重试。
## 12. 当前状态与交接注意事项
仓库内的实现、生产部署和业务验收是三个不同状态。交接时应特别核对:
- 本地 Daily 行交互与 Booking 双语表头兼容修改是否已发布到目标生产实例;
- Web 与 monthly worker 是否由可重启、可监控的持久 supervisor 管理;
- 真实 Booking 完整工作簿的上传、人工核对和整表激活是否已获业务授权并完成;
- 日报人工价格 case 是否由操作员确认了正确任务和正确价格后再重试;
- 生产登录密码是否已使用与用户名不同的高强度随机值;
- worker heartbeat、结构化日志、队列积压和 dead event 告警是否已补齐。
这些事项会随任务推进变化。权威开放项请查阅 [`.project-docs/90-maintenance/stale-items.md`](.project-docs/90-maintenance/stale-items.md),当前工作焦点查阅 [`.project-docs/30-worklog/current-state.md`](.project-docs/30-worklog/current-state.md)。不要仅依据本文日期判断生产状态。
## 13. 新成员交接清单
1. 阅读本文件和根 [`README.md`](README.md)。
2. 阅读 [项目定位](.project-docs/00-brief/project-positioning.md)、[系统总览](.project-docs/20-architecture/system-overview.md)、[业务规则](.project-docs/40-domain/business-rules.md) 和 [决策索引](.project-docs/10-decisions/decision-index.md)。
3. 确认当前分支、远端、工作树和最近提交,不覆盖未提交改动。
4. 从 Secret 管理器获得运行配置,不向对话、日志或仓库复制真实值。
5. 在隔离 `booking_test` 与测试 OSS 前缀上完成健康检查和自动化测试。
6. 对任何迁移、真实上传、Booking 激活、人工价格确认或生产重启单独取得授权。
7. 发布后验证数据库权威状态、OSS 哈希、下载、worker 和页面,而不是只看进程存在。
8. 更新 `.project-docs` 的当前状态、任务历史、证据与遗留项。
## 14. 相关文档
- [根 README快速启动与当前主链](README.md)
- [Web 接口与登录边界](arr_web/README.md)
- [验证后入库边界](arr_ingestion/README.md)
- [对象存储约束](arr_storage/README.md)
- [自动月报](monthly_reports/README.md)
- [公司渠道明细](company_reports/README.md)
- [渠道 BI](channel_analytics/README.md)
- [生产部署](deploy/README.md)
- [数据库 Schema 字典](DATABASE_SCHEMA_DICTIONARY.md)
- [项目决策索引](.project-docs/10-decisions/decision-index.md)
- [项目当前状态](.project-docs/30-worklog/current-state.md)