feat: sync latest ARR implementation

This commit is contained in:
Wyndham ARR
2026-07-31 15:11:42 +08:00
parent d6f8a747fa
commit bf7939dd1a
185 changed files with 17527 additions and 2260 deletions

131
README.md
View File

@@ -1,52 +1,105 @@
# Wyndham ARR
# ARR 2.0:程序化 XML 入库
ARR 是一套受控的 Opera 日报处理与经营数据应用。当前可部署链路为:
ARR 2.0 是独立于原 ARR 工程的程序化版本。用户只上传一次 Opera `RES_DETAIL` XML后续处理、校验、工件保存和 Finance 入库全部由 ARR 自己完成;生产路径不调用 Agent不暴露 MCP也不需要 prompt、`fetch_oss_file`、submission grant 或公网源文件地址。
## 处理链路
```text
业务页面上传 XML
-> ARR 写入私有 OSS 并登记任务
-> ARR 调用 SuperAgent Open API
-> SuperAgent 读取 OSS XML 并执行确定性日报 Skill
-> SuperAgent 调用 ARR MCP 的 arr_submit_processing_result
-> ARR 独立复验源 XML 后事务落库
-> 日报历史、月度投影和渠道看板从数据库读取
浏览器上传 XML
-> ARR 将源 XML 作为 private 不可变对象写入 OSS
-> ARR 登记 processing run / attempt并切换为 running
-> 固定版本 process_daily.py 在隔离临时目录生成完整 JSON/XLSX
-> ARR 将结果工件作为 private 不可变对象写入 OSS
-> DeliveryValidator 校验 Schema、哈希、行数、业务恒等式和独立日报复验
-> PostgresIngestionRepository 在一个事务中写入/激活 Finance 版本
-> 写入 arr.daily_version_committed outbox 事件并返回 succeeded / failed 的终态回执
-> 独立 monthly worker 领取事件
-> 从数据库内 ARRIVAL 派生月份与“更新至”日期
-> 生成并校验月报,原子登记 reporting 元数据和可下载工件
```
## 公网部署
上传接口仍返回 HTTP `202` 以兼容现有页面,但请求会等到校验和数据库提交结束,因此响应体里的任务状态已经是 `succeeded``failed`,不是“远端已接收”。
仓库提供 Docker 镜像与 Docker Compose + Caddy 部署入口。Dockerfile
默认 Web CMD 和 Compose Web command 都显式使用 `--enable-processing`
打开 XML 上传处理Compose 另使用 `--secure-cookies`、HTTPS、Web Basic Auth
和 MCP Bearer 保护公网服务。
## 保留的安全边界
完整步骤见 [`deploy/README.md`](deploy/README.md)。部署后必须确认:
- 源 XML、日报、两份 JSON 和异常清单都按 SHA-256、字节数、MIME 和对象身份校验。
- 成功结果由独立 `validate_daily.py` 再验一次;验证失败不会写入半成品 Finance 版本。
- 同一 delivery 的重放保持幂等;用户重新上传会创建新的 job并按现有业务日期版本规则在成功后安全替换当前版本。
- 数据库只对可识别的瞬时并发错误做最多四次事务重试。这不是模型或工具重试。
- 当前 bucket 可以是 `private``public-read`,但 ARR 2.0 写出的每个对象 ACL 都是 `private``public-read-write` 和已启用/暂停 versioning 会被拒绝。
- `GET /api/health``database_ready=true`
- `GET /api/health``processing_ready=true`
- MCP 未认证请求返回 `401`
- 认证后的 MCP 工具发现只返回 `arr_submit_processing_result`
## 本地启动
应用源码默认仍为 fail-closed直接运行 `python -m arr_web.run` 不会自动开启上传处理。
运维若覆盖镜像 CMD必须在 Web 命令中保留 `--enable-processing`
## 主要入口
- `arr_web`:上传、任务历史、月度数据与渠道看板 Web/API
- `arr_mcp`SuperAgent 结构化处理结果的单工具 MCP 入库网关;
- `arr-opera-daily-ingest`Opera XML 的确定性日报处理 Skill
- `database`PostgreSQL 15+ schema、增量迁移和 MCP 契约;
- `tests`:单元、契约和无隐私合成链路测试。
## 当前能力边界
- XML 上传、私有 OSS 保存、SuperAgent 任务发起和 MCP 直接落库代码已接线;
- 正式公网环境仍需部署者注入数据库、OSS、SuperAgent 和 MCP Secret并完成一次无隐私 XML 竖切验收;
- “日报落库后自动触发月报”与月报 `TOTAL PRICE` 指定公式仍未实现,本部署配置不会开启月报/公司报表写入端点,也不会把远端 Agent `success` 误报为数据库已提交。
## 本地测试
建议使用 Python 3.12
```bash
python3 -m unittest discover -s tests -v
python -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
cp .env.example .env.local
```
项目不会从仓库内自动加载 `.env`。真实凭据、业务 XML/XLSX、数据库文件、运行输出和虚拟环境均不得提交。
`.env.local` 中的数据库、OSS 和凭据注入当前 shell 后启动。Web 还必须从运行时 Secret
注入 `ARR_WEB_USERNAME` 和至少 12 位的 `ARR_WEB_PASSWORD`;任一缺失都会拒绝启动,真实值不得提交到仓库:
```bash
.venv/bin/python -m arr_web.run \
--host 127.0.0.1 \
--port 8765 \
--enable-processing \
--enable-monthly-generation \
--enable-company-reports \
--node-binary /absolute/path/to/node \
--artifact-tool-module /absolute/path/to/artifact_tool.mjs
```
另开一个进程启动月报消费者:
```bash
.venv/bin/python -m monthly_reports.worker \
--db-config /absolute/path/to/booking-test-db.env \
--node-binary /absolute/path/to/node \
--artifact-tool-module /absolute/path/to/artifact_tool.mjs
```
浏览器访问 `http://127.0.0.1:8765` 后会先进入 ARR 登录页。登录后,`GET /api/health` 中相关 readiness
均为 `true` 才表示页面处理和下载能力可用;未登录的容器只使用无详情的 `GET /healthz` readiness。
worker 是独立无端口进程,应由进程管理器单独保活。
当前测试库权威结构为 `database/008_arr_mvp_v1_rebuild.sql` 加 009015 增量迁移。012 只增加月报发布元数据、Finance 日版本 lineage 和受控本地工件身份,不复制月报业务/住客行013 将用户上传的 XML 文件名独立保存为任务来源信息,内部源工件仍统一命名为 `source.xml`014/015 增加 Booking 当前整表指针以及可编辑的 Excel 提取草稿。ARR 2.0 使用原有 `artifact_callback` 通用工件交付表;不会读写 009/010 的 grant/MCP submission 表。
## Booking Excel 房型提取
“公司渠道明细”页可以上传原始 `.xlsx`,程序从同一工作表的 `Tour Code` 与精确泰文表头 `โรงแรม` 提取房型记录。提取结果先进入草稿,不会直接覆盖当前 Booking 数据:
- Tour Code 会删除制表符、换行和空格;同一 Tour Code 以物理位置最后一行为准,最后一行明确写明取消时移除,单元格底色不代表取消。
- 只分析酒店文本圆括号内的房型信息。`【房型】` 后面的整数是数量;未单独写数量时按 1。
- `U-TWN`/`U-DBL` 后带任意数字或小数仍分别归一为 `U-TWN`/`U-DBL``高级房TWN``高级房DBL` 分别归一为 `TWN``DBL`
- 无法确认的中括号内容保留原始标识、保留已识别数量并标为“待人工确认”;没有中括号的房型文本也进入人工确认。
- 多个房型拆成多条记录,重复房型不在提取阶段合并。附加费、儿童早餐和导游房等非客房项目忽略。
- 自动识别与人工记录都可修改或删除;待人工项目确认前不计入有效房量。全部待人工项确认或删除后,才可一次性启用整份工作簿;重新提取原文件会按源文件重建草稿。
对应持久化迁移是 `database/014_booking_current_source_batch.sql``database/015_booking_excel_review_drafts.sql`
## 测试
```bash
.venv/bin/python -m unittest discover -s tests -p 'test_*.py' -v
```
`tests/test_arr_programmatic.py` 是关键竖切:它使用真实固定处理器、不可变对象存储、独立验证器和入库 repository 覆盖成功与业务失败。
## 部署
生产配置见 [deploy/README.md](deploy/README.md)。当前 Compose 模板仍只打包 `web``caddy`Caddy 负责
HTTPSWeb 负责应用登录和会话;没有 MCP 端口、MCP 域名或 Agent Secret。月报 worker 必须作为独立进程部署,
并使用同一数据库、共享输出卷以及已经打包 Node/artifact-tool 的运行镜像;当前本地工作站已按这一方式运行。
## 当前月报行为
- 用户只上传 XML页面不再要求月份、截止日或单独点击“生成月报”。
- worker 只消费成功提交的日报事件,使用纳入月报数据的最大 `ARRIVAL` 作为“更新至”XML 文件名和当前时间都不参与推导。
- 月报版本、lineage、渠道行数和两个工件身份持久化到 `reporting` schema列表和下载由这些元数据驱动。
- 月报页可见时自动同步发布记录,新版本直接加入列表,不需要点击“刷新”。
- 每条数据行的 `TOTAL PRICE` 都是 `REAL PRICE × NIGHTS × NO_OF_ROOMS` 的 Excel 公式(当前列布局为 `=R[row]*C[row]*G[row]`)。
原工程 `/Users/chillishark/ARR项目0727` 未被修改,可继续作为 ARR 1.x 回滚基线。