208 lines
18 KiB
Markdown
208 lines
18 KiB
Markdown
# 单日 ARR 自动下载卡片接入
|
||
|
||
2026-09-16。Web 卡片、任务队列、HTTP 入口及采集到处理的执行器框架已实现;当前 `arr_web.run` **尚未注入实际执行器**,
|
||
所以页面显示「自动下载服务暂未就绪」,按钮不可提交。不得把本文或合成测试解释为真实接口到 Finance 已接通。
|
||
|
||
2026-09-17:[独立本机XML重放入口](LOCAL_XML_REPLAY.md)已用用户准确XML跑通按钮、真实处理、隔离SQL和月报。
|
||
只在`arr_web.local_replay`装配中ready=true,来源明确为native_xml_replay;不是本文要求的OHIP正式来源适配,
|
||
不改变`arr_web.run`默认状态。可先用于按钮联调。
|
||
|
||
2026-09-17:[本机接口模拟入口](LOCAL_API_SIMULATION.md)8874也已完成152次HTTP采集、生成ARR处理XML、原XML字段核验及隔离入库/月报。`source_kind=local_api_simulation`,仍与Oracle实际来源验收区分;可沿用下面的提交/状态/下载契约联调。
|
||
|
||
## 日期与用户操作
|
||
|
||
- 新卡片位于 Upload ARR.XML 左侧。单个日期选择器的选值是本次下载的唯一日期来源。
|
||
- 表单初始建议值为曼谷日历昨天,用户可以改选;这仅是 Web 表单预填。
|
||
- 2026-09-18:日期选择与当前任务分离,进行中可预选下一报表日期,轮询不覆盖已编辑日期。
|
||
预选不提交、取消或改变原任务;运行中仍禁止新提交,待确认/中断重试继续沿用原编号/日期,按钮注明原日期。
|
||
- POST 必须显式带上有效 `report_date`,缺失、日期区间、多余字段、时间戳、无效日历日期一律拒绝。
|
||
下载执行器不重新计算昨日,也不按执行/重试当天限制输入日期。
|
||
- Web 调用下游时传两个相等的 Python `date`:`from_date=selected_day`、`to_date=selected_day`。
|
||
下游搜索的 arrivalStartDate/arrivalEndDate 和有效价的 detailDate 都必须使用这一天。
|
||
- 这与“上游系统提供明确 From/To,采集程序校验并冻结”的边界一致。本卡片是手动选择日期的上游入口;
|
||
其他系统的双日期调用协议仍由接口任务对接,不在本 Web API 中接受任意日期范围。
|
||
|
||
## HTTP 契约
|
||
|
||
所有入口要求现有登录会话,POST 还要求 `X-ARR-CSRF`。响应继续使用标准 `{ok, api_version, data}`。
|
||
|
||
| 方法与路径 | 请求 / 返回 |
|
||
|---|---|
|
||
| GET `/api/arr-downloads` | `ready`、`default_date`、`business_time_zone`、`latest_task`;读取无采集副作用 |
|
||
| POST `/api/arr-downloads` | 严格 JSON `{report_date:"2026-09-15", request_id:"32位小写十六进制"}`;202 为已登记任务,不是 Finance 成功 |
|
||
| GET `/api/arr-downloads/{request_id}` | 获取本次任务;未知编号404 |
|
||
| POST `/api/arr-downloads/{request_id}/retry` | 空 JSON `{}`;继续原编号/日期,不能带新日期 |
|
||
|
||
任务仅暴露 `request_id/report_date/from_date/to_date/status/job_id/can_retry/created_at/updated_at`。
|
||
状态有 queued、downloading、processing、needs_review、succeeded、failed、interrupted。
|
||
浏览器网络异常单独显示“重新查询原任务”,不把本地网络错误登记成业务失败。
|
||
用户请求编号先存入按登录名区分的 localStorage,再发起 POST;服务端 latest_task 提供另一路恢复入口。
|
||
首次连接失败或服务未就绪时,可见页面每10秒通过GET重新检查;切回页面或网络恢复时立即检查,隐藏页面暂停。
|
||
恢复检查等待会话初始化完成,同一配置请求不重叠,只恢复一次本地任务编号,保留用户已经选择/正在编辑的日期。
|
||
这些检查不会自动POST新任务或重试;中断任务仍需用户明确点击,沿用原请求编号和日期。
|
||
|
||
## 执行器与待完成的来源适配
|
||
|
||
[CapturedARRExecutor](arr_download_executor.py) 已实现 [ARRDownloadExecutor](arr_downloads.py) 的同步 `execute`。
|
||
其顺序如下,实际来源适配与独立映射验证仍需接口任务交付:
|
||
|
||
```python
|
||
def execute(self, *, request_id, from_date, to_date, report_stage):
|
||
# 1. Validate equal explicit dates and bind request_id to a stable capture batch.
|
||
# 2. Acquire/archive the complete accepted source, including date-specific prices.
|
||
# 3. Apply the accepted, versioned source adapter and independent mapping checks.
|
||
report_stage("processing")
|
||
# 4. Prepare/replay the SAME frozen package using the existing validation/ingestion path.
|
||
# 5. Return an authoritative acknowledged result, never just capture completeness.
|
||
return DownloadOutcome("succeeded", job_id=acknowledged_job_id)
|
||
```
|
||
|
||
上面的说明不是源适配实现。必须完成整日采集、字段/范围/顺序验收后才能连接真实数据。
|
||
`request_id` 必须固定关联 capture batch、交接文件组和 Finance job;未知提交结果重试必须核对原事务,
|
||
不能调用每次分配新 UUID 的人工 `ProgrammaticUploadCoordinator.submit`。
|
||
|
||
执行器需要显式注入:已验收的 `adapter.adapt(VerifiedArchive) -> bytes`、独立的
|
||
`mapping_validator.validate(VerifiedArchive, payload) -> None`、共同版本化的 `adapter_contract`、酒店 ID、
|
||
返回匹配版本 Reader 的 `reader_factory`,以及现有处理器策略、对象存储和入库服务。
|
||
映射验证发现任何不符必须抛异常;返回 False/其他值也会拒绝。它不能仅重跑同一适配逻辑或固定返回通过。
|
||
构造函数检查必需依赖存在,不替代来源验收;仓库当前没有正式适配器或映射验证器,也没有启用参数。
|
||
|
||
2026-09-16 接入复核:接口任务新建的 `source_facts.build_source_facts` 和
|
||
`validate_source_facts.verify_source_facts` 是字段证据 JSON 的提取/校验入口。前者明确不生成 XML,后者的
|
||
`facts_verified=true` 只证明字段证据与原始响应一致;`source_mapping_verified/report_equivalence_verified/finance_ready`
|
||
仍为 false。它们尚未实现上方 `adapt(archive) -> XML bytes` 和 `validate(archive, XML bytes) -> None` 协议,
|
||
不能直接注入执行器,也不能据此改变页面 readiness。实际还需完成报表范围、字段选择、显示和顺序的适配并独立校验。
|
||
这项技术交付缺口与已关闭的日常 XML/Resv.-GEN 用户确认无关,不再向用户重复询问基准文件。
|
||
|
||
执行器将选中日期转成三个相等的 ISO 日期,默认传给v2 `capture_day_job.run_batch`;
|
||
批次编号固定为 `web-<request_id>`。整个请求绑定酒店、日期、采集契约、适配/验证版本和处理规则,
|
||
任一内容变化会拒绝旧任务重试。不得通过换一个来源或规则版本“修复”结果未知的原任务。
|
||
|
||
2026-09-17新增含姓名的v3选择:构造时显式传`capture_version="v3", max_profiles=N`,N必须是1–10000的整数,
|
||
不可缺省或传布尔值。v2不接受姓名查询上限;其旧请求格式和默认行为保持兼容。v3调用
|
||
`capture_named_day_job.run_batch`,工厂依次返回`Reader`、`RateInfoReader`、`ProfileSummaryReader`,最后一个Reader
|
||
使用同样的N。v3归档经完整性重放后,才交给适配器和独立验证器;两个组件必须明确支持此版本。
|
||
请求身份含采集版本和姓名上限;改变版本/上限不能复用旧请求,数字与布尔值或浮点数也不互相冒充。
|
||
采集范围还可显式传`page_size`、`max_pages`、`max_records`,分别限定每页条数、最多页数、最多预订数;
|
||
三个值须为正整数,上限分别100、100、10000,v3还要求`max_profiles <= max_records`。
|
||
构造时先校验,再允许创建任务或加载凭据;默认值沿用已有v2行为。已约定的测试范围可配置为
|
||
`page_size=100, max_pages=2, max_records=139, max_profiles=112`,这只是配置示例,不启动新采集。
|
||
范围写入持久化请求身份;超限停止于搜索阶段,旧请求重试不能改变范围。Reader工厂仍须使用相同姓名上限。
|
||
已冻结后的未知提交结果恢复仍直接复用原交付包,不再查询姓名或重新采集。
|
||
|
||
新`prepare_arr_source`仅输出逐笔候选及缺口,虽然可在503期间离线运行,也不是已验收来源适配器/验证器。
|
||
构造依赖检查会拒绝它;不得改变页面readiness。此扩展只有合成来源和本地隔离处理测试,正式来源与运行接线仍待验收。
|
||
|
||
2026-09-17交付检查补充:执行器每次交付都向`processing_handoff.deliver`传完整`expected_binding`与
|
||
`expected_policy`。持有交付锁后,先核对实际冻结包的批次、酒店、日期、采集摘要、适配契约和处理规则,
|
||
匹配后才可上传或登记数据库任务。改动`prepared.json`里的采集摘要也不能绕过这一检查。
|
||
采集`job.json`及交接身份文件均按规范化JSON摘要比较;浮点数不能冒充原来的整数,字段顺序变化则允许。
|
||
|
||
`root` 必须是仓库外的私有持久目录,其父目录需已存在;执行器创建0700目录、0600文件:
|
||
|
||
- `requests/<request_id>/request.json`:在第一次采集前保存请求身份。
|
||
- `captures/web-<request_id>/`:保留采集尝试及原始响应;失败采集可沿用同批次重新取得整日。
|
||
- `handoffs/<job_id>/`:由现有交接库保存并验证源 XML、处理产物和不可变交付文件组。
|
||
- `requests/<request_id>/prepared.json`:在第一次外部对象写入/Finance交付前保存文件组摘要。
|
||
|
||
存在已准备文件组的恢复点时,重试直接验证并交付原文件组,不调用酒店、适配器或处理器;
|
||
若在保存恢复点前中断,会重读已完成采集并重新适配,但 `prepare` 必须复用同一文件组,改变内容即拒绝。
|
||
恢复点不是成功凭证,每次仍需 `deliver` 核对权威提交结果;摘要不符则停止。采集失败尚未形成处理任务时
|
||
可返回明确失败并允许同批次重试;未知状态、映射拒绝和交付异常保留为中断,不宣称 Finance 失败。
|
||
|
||
- `succeeded`:完整结果已验证并获得权威 Finance 提交回执,必须返回处理 job_id。
|
||
- `needs_review`:已登记原有人工价格复核,必须返回对应 job_id;尚无最终 Finance/月报结果。
|
||
- `failed`:权威终止失败,可有 job_id;只有确实可沿用相同身份安全重试时才能设 retryable=True。
|
||
- 异常/非法回执:Web 记录 interrupted,保留身份;不保存异常正文、不宣称提交失败。
|
||
- 默认最多10次执行;取消/失败原因应通过现有业务任务证据或部署诊断核实,不把原始响应返回前端。
|
||
- Web 查询需要复核的任务时继续读取现有权威任务状态,复核通过后显示 succeeded。
|
||
|
||
### 冻结处理回执转换
|
||
|
||
`arr_web.arr_download_handoff.outcome_from_handoff` 已提供严格的回执转换。未来执行器调用
|
||
`processing_handoff.deliver` 并获得本次权威响应后,将该响应与预期 `job_id`、所选 Python `date`
|
||
及 `prepare` 返回的 `manifest_sha256` 一同传入。不要用本地缓存 `receipt.json` 代替交付核对。
|
||
|
||
| 交付回执 `ingestion_status` | Web 任务结果 |
|
||
|---|---|
|
||
| `committed` | `succeeded`;必须有正整数 Finance 版本编号且业务日期等于所选日期 |
|
||
| `recorded_review` | `needs_review`;没有 Finance 版本,进入现有人工价格复核 |
|
||
| `recorded_failure` | `failed`;确定的业务失败,不自动重试;失败审计回执的业务日期可为null |
|
||
|
||
回执版本、处理任务、交付编号、文件组摘要、日期或提交状态不符均抛异常,由 Web 保留为 `interrupted`;
|
||
未知状态不会默认成功。转换函数本身不提供采集/来源适配,不证明 API 字段等价性,也没有使运行环境就绪。
|
||
合成联调的测试执行器仅存在于测试文件中;正式执行器仍须完成上方来源验收及身份绑定要求。
|
||
|
||
2026-09-17失败回执修正:真实PostgreSQL会为`recorded_failure`保存`version_status=rejected`的审计行,
|
||
所以`daily_version_id`可以是正整数,也可兼容为null;`version_no`必须为null。这不是有效日报版本,
|
||
不能据此改成成功或未知状态。`recorded_review`的两个版本字段仍必须都是null。
|
||
Web将权威失败显示为`failed`、保留`job_id`且不提供下载重试;提交后丢回执时暂为`interrupted`,
|
||
沿原冻结交付核对后收敛到同一失败,不重新采集/处理。数据库可保留一条`arr.processing_failed`审计事件;
|
||
不得产生`arr.daily_version_committed`事件、激活日报或触发月报。
|
||
|
||
### 首轮真实沙箱联调的结果边界
|
||
|
||
已有OHIPSB02固定139条采集的115条可取费率代码均不在本酒店现有白名单内,24条缺少有效费率代码,
|
||
见[原始覆盖检查](../.project-docs/50-evidence/topics/2026-09-16-arr-input-gap-triage.md)。
|
||
这批数据不能作为成功日报/月报的正例;不改白名单或删行来使其通过。
|
||
须先完成来源适配与独立映射验收,之后才能检查现有处理器给出的确定业务失败;
|
||
不能用预期业务失败替代来源验收,也不能把来源/网络失败当作确定处理失败。
|
||
成功处理链已有本机隔离模拟覆盖,真实正例留待有适当来源数据后验收。
|
||
|
||
处理器对白名单行要求公司名称、确认号、房号和姓名等必填值,公司参与价格匹配,备注首条参与Group Code。
|
||
`BLOCK_CODE`、`PRODUCTS`、`ROOM_CATEGORY_LABEL`允许来源确实为空;允许空不等于允许将未取得/未核实值填空。
|
||
依照[现有字段契约](../arr-opera-daily-ingest/references/field-contracts.md)保留其来源和值,不新增业务规则。
|
||
|
||
在受控运行组合中创建 `PersistentARRDownloads(private_jobs_root, accepted_executor)`,通过
|
||
`PortalApplication(..., arr_downloads=coordinator)` 注入,并负责关闭协调器和相关依赖。
|
||
不要仅添加环境变量就把未验收执行器标记为可用。
|
||
|
||
### 受控启动装配(2026-09-17)
|
||
|
||
`arr_web.arr_download_runtime.CapturedARRSource`封装显式来源依赖;
|
||
`arr_web.run.main(argv, arr_source=source)`已支持将其注入标准Web启动流程。
|
||
默认`main(argv)`和现有容器命令仍不配置来源,没有新增CLI/环境变量开关,也没有加载酒店凭据。
|
||
这一步只完成程序装配,不签发业务验收;真实适配器和独立映射验证器仍待接口任务交付。
|
||
|
||
已验收来源的Python启动包装器可以使用以下组合(变量须由受控包装器提供,不是当前可运行的真实来源示例):
|
||
|
||
```python
|
||
source = CapturedARRSource(
|
||
root=private_state_directory, hotel_id=hotel_id, adapter_contract=accepted_contract,
|
||
adapter=accepted_adapter, mapping_validator=independent_validator,
|
||
reader_factory=reader_factory,
|
||
capture_version="v2", page_size=100, max_pages=100, max_records=10000,
|
||
)
|
||
main(["--enable-processing"], arr_source=source)
|
||
```
|
||
|
||
- 须同时启用并成功装配处理依赖。下载与人工上传共享同一处理策略、处理器、对象存储、入库仓库及独立校验服务。
|
||
不重复创建一套可能不同的处理规则。构造来源不会调用Reader或酒店接口;已持久化的queued任务会在启动后恢复调度。
|
||
- `root`是仓库外的私有0700目录,其父目录须存在。`source.json`在队列启动前固定服务/应用命名空间、酒店、
|
||
采集协议及上限、适配版本和处理规则。任何变化拒绝复用旧根目录,尚未开始采集的queued任务也不能被改绑。
|
||
无此绑定文件的既有queue/acquisition目录会被拒绝;不要直接指向8873/8874实例或借删除绑定绕过检查。
|
||
- `compose_arr_downloads(source, processing_runtime)`可供其他受控组合复用;只拥有返回的队列,处理依赖仍由调用方拥有。
|
||
reader_factory若持有客户端/凭据上下文,应由包装器保持到`main`返回后再关闭。
|
||
- Web构造/服务异常都会关闭已取得的资源;队列先停止调度,并通过`close(wait=True)`等待正在执行的任务结束,
|
||
再关闭共享存储。标准主线程的显式来源启动还将SIGTERM接入这个退出过程,并恢复先前的信号处理器。
|
||
强制终止仍依赖既有中断/冻结恢复机制,不能保证正在执行的网络请求立即返回。
|
||
- 原文件重放及本机模拟的关闭路径也等待队列,再停止月报worker和自有数据库。当前运行的录制实例未重启。
|
||
- 月报仍由原有独立worker消费outbox;本装配不新增定时下载、月报按钮或来源切换UI。
|
||
|
||
## 持久化与运行边界
|
||
|
||
- 单机私有 SQLite 队列;目录0700、数据库0600;完整事务写入请求后才交给单个后台线程执行。
|
||
- 进程独占文件锁避免同一队列被两个进程调度。不是跨机器队列,必须使用同一可靠的本地持久卷。
|
||
- 同日期有 queued/downloading/processing/interrupted 任务时,新点击会返回已有任务。
|
||
- 同编号改日期返回409;同编号完成后重复提交返回旧结果;显式新编号可重新获取已完成日期的数据。
|
||
- 重启时未开始的 queued 可执行;下载/处理中的任务变为 interrupted,由用户继续原任务核对结果。
|
||
- `.close()`保留最多2秒的兼容等待,正在执行的调用继续持有锁到返回;资源拥有者使用`.close(wait=True)`
|
||
等待执行器结束后再关闭依赖。标准显式来源装配和本机重放/模拟已采用完整等待。
|
||
- 原始数据、适配后的源、处理文件及金融幂等性属于真实执行器/现有交接库的责任,不能从本队列推定已验收。
|
||
|
||
## 已验证 / 未验证
|
||
|
||
新单元测试覆盖单日与校验、并发点击、原任务重试、跨进程锁、重启恢复、身份冲突、鉴权/CSRF和人工复核状态。
|
||
合成浏览器检查覆盖中英泰/五种窗口宽度、失去响应与未登记请求恢复、复核入口和未就绪运行状态。
|
||
没有调用酒店接口、生产数据库或 OSS,没有发布/重启正式服务;真实来源验收及运行注入仍待完成。
|