122 lines
8.9 KiB
Markdown
122 lines
8.9 KiB
Markdown
# ARR 2.0:OHIP 自动取数与 XML 报表处理
|
||
|
||
ARR 2.0 同时提供两条报表入口:选择日期后从OHIP获取业务数据,或上传Opera `RES_DETAIL` XML。
|
||
两条入口共用筛选、去重、定价、价格复核及日报/月报规则;后续处理、校验、工件保存和Finance入库由ARR程序完成。
|
||
自动取数直接处理数据,不生成中间XML。生产处理不调用Agent、不暴露MCP。
|
||
|
||
## 当前交付
|
||
|
||
- 接口路径:选日期 → 获取15字段业务数据 → 按原规则处理 → 生成日报供下载 → 自动更新月报。
|
||
- XML路径:上传XML → 解析XML → 按原规则处理 → 生成日报供下载 → 自动更新月报。
|
||
- 8个查询接口与页面、数据处理、复核、入库和月报已衔接;日期选择交互已获用户确认。
|
||
- 当前酒店由OHIP平台路由,ARR核对返回酒店`OHIPSB02`;用户确认生产切换时再调整对应配置。
|
||
- 开发和本机模拟完成,正式环境配置及真实数据验收待执行;基础Compose需显式加入OHIP启动参数和持久目录挂载。
|
||
|
||
交付入口:[字段范围](ARR_XML_RAW_FIELDS.md) · [接口及实际参数](ARR_OHIP_REQUEST_PARAMETERS.md) ·
|
||
[页面使用与运行配置](arr_web/DIRECT_DATA_ENTRY.md) · [生产启用说明](deploy/OHIP_RELEASE_HANDOVER.md)。
|
||
|
||
需要完整了解业务边界、系统架构、模块职责、配置、测试、部署和交接注意事项,请阅读 [ARR 2.0 项目说明与交接指南](PROJECT_GUIDE.md)。
|
||
|
||
## 原 XML 处理链路
|
||
|
||
```text
|
||
浏览器上传 XML
|
||
-> ARR 将源 XML 作为 private 不可变对象写入 OSS
|
||
-> ARR 登记 processing run / attempt,并切换为 running
|
||
-> 固定版本 process_daily.py 在隔离临时目录生成 v4 JSON,以及正式 XLSX 或纯 PRICE_UNMATCHED 复核结果
|
||
-> ARR 将结果工件作为 private 不可变对象写入 OSS
|
||
-> DeliveryValidator 校验 Schema、哈希、行数、业务恒等式和独立日报复验
|
||
-> 纯 PRICE_UNMATCHED:只登记审计化复核 case/items/events,返回 needs_review(无 Finance/outbox/日报下载)
|
||
-> 员工填写并冻结全部缺价键后,以原 XML + 固定价表 + 清单重新处理并独立复验
|
||
-> 最终成功才在一个事务中写入/激活 Finance 版本与 arr.daily_version_committed,返回 succeeded
|
||
-> 其他业务错误返回 failed;基础设施重试保留冻结清单
|
||
-> 独立 monthly worker 领取事件
|
||
-> 从数据库内 ARRIVAL 派生月份与“更新至”日期
|
||
-> 生成并校验月报,原子登记 reporting 元数据和可下载工件
|
||
```
|
||
|
||
上传接口仍返回 HTTP `202` 以兼容现有页面,但请求会等到初始校验和状态登记结束,因此响应体里的任务状态已经是 `succeeded`、`needs_review` 或 `failed`,不是“远端已接收”。
|
||
|
||
## 保留的安全边界
|
||
|
||
- 源 XML、日报、两份 JSON、异常清单和(最终人工复核时)冻结清单都按 SHA-256、字节数、MIME 和对象身份校验。
|
||
- 纯 `PRICE_UNMATCHED` 只会进入待复核,不会生成日报、Finance 版本或失败 outbox;混合错误仍走失败链路。
|
||
- 最终人工价格清单绑定 job、原 XML SHA、业务日期、处理器/规则身份、case 和完整问题键集合,独立 `validate_daily.py` 会用同一清单重放后才允许入库。
|
||
- 同一 delivery 的重放保持幂等;用户重新上传会创建新的 job,并按现有业务日期版本规则在成功后安全替换当前版本。
|
||
- 数据库只对可识别的瞬时并发错误做最多四次事务重试。这不是模型或工具重试。
|
||
- 当前 bucket 可以是 `private` 或 `public-read`,但 ARR 2.0 写出的每个对象 ACL 都是 `private`;`public-read-write` 和已启用/暂停 versioning 会被拒绝。
|
||
|
||
## 本地启动
|
||
|
||
建议使用 Python 3.12:
|
||
|
||
```bash
|
||
python -m venv .venv
|
||
.venv/bin/python -m pip install -r requirements.txt
|
||
cp .env.example .env.local
|
||
```
|
||
|
||
将 `.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
|
||
```
|
||
|
||
另开一个进程启动月报消费者:
|
||
|
||
```bash
|
||
.venv/bin/python -m monthly_reports.worker \
|
||
--db-config /absolute/path/to/booking-test-db.env \
|
||
--output-root /app/outputs/monthly_reports
|
||
```
|
||
|
||
浏览器访问 `http://127.0.0.1:8765` 后会先进入 ARR 登录页。登录后,`GET /api/health` 中相关 readiness
|
||
均为 `true` 才表示页面处理和下载能力可用;未登录的容器只使用无详情的 `GET /healthz` readiness。
|
||
worker 是独立无端口进程,应由进程管理器单独保活。
|
||
|
||
自动数据入口要求 `database/008_arr_mvp_v1_rebuild.sql` 加009–019增量迁移;019支持`ohip_json`来源和5.0结果,原XML保持4.0兼容。既有数据库实际版本须在部署时核对,不自动执行迁移。017必须在同一发布窗口先确认/应用016后才可应用;它增加价格复核case/item/event审计、`awaiting_review`生命周期、最终人工价格lineage和受保护回滚。018为冻结人工清单增加`manual_override_json`工件类型。012增加月报发布元数据和Finance日版本lineage;016允许新月报OSS工件并保留旧local记录;013保存用户上传XML文件名;014/015增加Booking当前整表指针和Excel提取草稿。ARR使用原有`artifact_callback`通用工件交付表,不读写009/010的grant/MCP submission表。
|
||
|
||
## Booking Excel 房型提取
|
||
|
||
“公司渠道明细”页可以上传原始 `.xlsx`。程序只在同一工作表中识别一组完整、精确的 Tour Code/Group Code 与酒店明细表头后提取房型记录:Tour Code 可为 `Tour Code`、`Group Code`、`Gourp Code`、`团号`、`团队代码` 或 `Tour Code / 主团号`;酒店明细可为 `โรงแรม`、`วางข้อมูลที่นี่ / 酒店明细` 或 `Raw Hotel Detail / วางข้อมูลที่นี่ / 酒店明细`。表头经过 Unicode、大小写、空白和标点归一化后仍须整格精确相等,不按月份、文件名、工作表名、关键词包含或模糊匹配。提取结果先进入草稿,不会直接覆盖当前 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 覆盖自动成功、纯缺价待复核、整数 `0` 输入并冻结为 `0.00` 重放、可重试基础设施错误和确定性最终失败。
|
||
|
||
## 部署
|
||
|
||
生产配置见 [deploy/README.md](deploy/README.md)。当前 Compose 模板仍只打包 `web` 与 `caddy`,Caddy 负责
|
||
HTTPS,Web 负责应用登录和会话;没有 MCP 端口、MCP 域名或 Agent Secret。月报和公司渠道明细都由
|
||
Python/openpyxl 生成,XLSX 与 `result.json` 上传现有 OSS;月报 worker 必须作为独立进程部署,
|
||
`.web-jobs` 队列状态仍使用 `/app/outputs` 持久卷。
|
||
|
||
## 当前月报行为
|
||
|
||
- 用户只上传 XML,页面不再要求月份、截止日或单独点击“生成月报”。
|
||
- worker 只消费最终成功提交的日报事件,使用纳入月报数据的最大 `ARRIVAL` 作为“更新至”;待人工处理和失败任务都不会创建该事件。
|
||
- 月报版本、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 回滚基线。
|