18 KiB
单日 ARR 自动下载卡片接入
2026-09-16。Web 卡片、任务队列、HTTP 入口及采集到处理的执行器框架已实现;当前 arr_web.run 尚未注入实际执行器,
所以页面显示「自动下载服务暂未就绪」,按钮不可提交。不得把本文或合成测试解释为真实接口到 Finance 已接通。
2026-09-17:独立本机XML重放入口已用用户准确XML跑通按钮、真实处理、隔离SQL和月报。
只在arr_web.local_replay装配中ready=true,来源明确为native_xml_replay;不是本文要求的OHIP正式来源适配,
不改变arr_web.run默认状态。可先用于按钮联调。
2026-09-17:本机接口模拟入口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 已实现 ARRDownloadExecutor 的同步 execute。
其顺序如下,实际来源适配与独立映射验证仍需接口任务交付:
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条缺少有效费率代码, 见原始覆盖检查。 这批数据不能作为成功日报/月报的正例;不改白名单或删行来使其通过。 须先完成来源适配与独立映射验收,之后才能检查现有处理器给出的确定业务失败; 不能用预期业务失败替代来源验收,也不能把来源/网络失败当作确定处理失败。 成功处理链已有本机隔离模拟覆盖,真实正例留待有适当来源数据后验收。
处理器对白名单行要求公司名称、确认号、房号和姓名等必填值,公司参与价格匹配,备注首条参与Group Code。
BLOCK_CODE、PRODUCTS、ROOM_CATEGORY_LABEL允许来源确实为空;允许空不等于允许将未取得/未核实值填空。
依照现有字段契约保留其来源和值,不新增业务规则。
在受控运行组合中创建 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启动包装器可以使用以下组合(变量须由受控包装器提供,不是当前可运行的真实来源示例):
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,没有发布/重启正式服务;真实来源验收及运行注入仍待完成。