Files
InboundVerify/docs/2026-07-29-指定日期下载接口-design.md
2026-07-29 12:58:08 +08:00

132 lines
6.8 KiB
Markdown
Raw 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.
# 指定日期下载接口(开发者)— 设计文档
> 日期2026-07-29
> 定位:面向开发者的 HTTP 接口,**不进前端**。提供"指定一个具体日期,下载该日应到 / 实到数据"的能力,用于补下历史数据。
> 前置:中通跨月导航已实现并验证(见 `feat(zto): cross-month calendar navigation`)。
## 一、背景与目标
现状:下载日期由各站 **offset 偏移**0=今天1=昨天…,存 `state.db`,上限 `MAX_DATE_OFFSET=30`)决定,周期调度与手动触发都用 offset。无法指定一个具体日期。
目标:新增"指定日期"入口(开发者用),传一个 `YYYY-MM-DD` 日期,下载该日应到 / 实到数据。不替换 offset 机制,与之并存:传 date 用 date不传走 offset。
## 二、范围
| 站点 | 支持指定日期 | 说明 |
| --- | --- | --- |
| 顺心 / 中通 / 韵达 / 安能 | ✅ | 应到、实到均支持 |
| 百世 | ❌ | 固定下载当天,传 date 返回 400 |
## 三、接口契约
复用 `POST /tasks`body 新增可选字段 `date`(与 `force` 并列):
```json
{ "site": "中通", "kind": "expected", "date": "2026-06-14" }
```
- `date: Optional[str] = None`,格式 `YYYY-MM-DD`
- **优先级**:传 `date` 则本次用 date不传则走站点 offset 配置(默认行为完全不变)。
- `date``force` 可共存(指定日期 + 强制重下)。
### 合法性校验(仅在传了 date 时执行,失败返回 400
1. **格式**`datetime.strptime(date, "%Y-%m-%d")` 解析成功,否则 400。
2. **范围**`今天 - 90 天 ≤ date ≤ 今天`
- `date > 今天` → 400未来日期日历未来格子 `invalid` 物理上点不动,且不应下未来数据)。
- `date < 今天 - 90 天` → 400回溯上限 90 天)。
3. **百世**site=百世 且传 date → 400固定当天
> 合法性校验落在 `POST /tasks``server.py` `create_task`),入队前拦截,非法请求不产生任务。
## 四、透传链路(与现有 `force` 完全对称)
```
POST /tasks {site, kind, force, date}
→ task_queue.put((tid, {site, kind, force, date}))
→ dispatch_task(ctx, task_spec) # 读 task_spec["date"]
→ handler(ctx, force, date) # _web_handler / 安能 lambda / _site_undelivered_handler
→ impl(page, force, date) # 各站 download_impl
```
- `_web_handler``handler(ctx, force=False, date=None)`,透传 `download_func(pg, force, date)`;顺心双账号透传 `(pages, foreground, force, date)`
- `_site_undelivered_handler`(未到):连下 expected + actual**两个子任务共用同一个 date**。
- 安能 lambda`(ctx, force=False, date=None) → anneng_xxx_download(force=force, date=date)`
- **周期调度** `_enqueue_fetch` 投递的 task_spec 只有 `{site, kind}`(不带 date→ 恒走 offset**无需改动**。
## 五、各站 impl 改造(核心)
统一模式:**`target = parse(date) if date else (today offset)`**。
### 中通zto—— 复用跨月算法
把 date 折算成 effective offset复用现有 `target_time = today_time offset*86400000` 与跨月翻页(`_zto_flip_to_target_month`),零额外 UI 逻辑:
```python
def zto_expected_download_impl(page, force=False, date=None):
...
offset = state_store.get_offset("中通")
if date:
target_date = datetime.strptime(date, "%Y-%m-%d").date()
offset = (datetime.now().date() - target_date).days
# 后续 today_time / target_time / 跨月翻页 逻辑完全不变
```
`zto_actual_download_impl` 同理(用 `("中通","actual")` offset。expected / actual 两个 impl 都加 `date=None` 形参,`zto_expected_download` / `zto_actual_download` 公开入口同步加形参并透传。
### 韵达 / 顺心 / 安能 —— date 直接当 target
这三站 offset→日期是 `target = today timedelta(days=offset)` 后填**字符串**到日期控件(非日历格子),指定日期只需替换 target 来源:
```python
if date:
target = datetime.strptime(date, "%Y-%m-%d")
else:
target = today - timedelta(days=offset)
```
后接的"填起始/截止日期字符串"逻辑完全不变。各站 expected / actual 入口与 impl 都加 `date=None` 形参。
- 韵达:`yunda_expected_download(_impl)` / `yunda_actual_download(_impl)`
- 顺心:`shunxin_expected_download(_impl)` / `shunxin_actual_download(_impl)`(双账号入口透传 date 到各账号 impl
- 安能:`anneng_expected_download` / `anneng_actual_download`
### 百世baishi—— 签名兼容
`baishi_download_undelivered_data(page, date=None)``date=None` 形参(**忽略**),仅为对齐 `_web_handler` 的统一透传签名;百世任务实际不会带 dateserver 已拦截)。
## 六、业务日期快照
`_record_business_date(site, kind, date=None)`:有 date 则业务日期 = date否则维持现状 `today offset``dispatch_task``task_spec["date"]` 透传进去,保证状态盘 / 报告显示的"是哪天的数据"准确(不被 offset 算错)。
## 七、改动文件清单
| 文件 | 改动 |
| --- | --- |
| `inbound_verify/cli/server.py` | `TaskRequest.date` + `create_task` 合法性校验 + task_spec 透传 date |
| `inbound_verify/runtime.py` | `_web_handler` / `_site_undelivered_handler` / 安能 lambda 透传 date`dispatch_task` 读 date 透传给 handler 与 `_record_business_date``_record_business_date` 加 date |
| `inbound_verify/sites/zto.py` | expected/actual 入口+impl 加 `date`date→effective offset 复用跨月 |
| `inbound_verify/sites/yunda.py` | expected/actual 入口+impl 加 `date`date→target |
| `inbound_verify/sites/shunxin.py` | 同上(双账号透传 date |
| `inbound_verify/sites/anneng.py` | expected/actual 加 `date`date→target |
| `inbound_verify/sites/baishi.py` | 加 `date=None` 形参兼容(忽略) |
## 八、验证计划
1. **接口校验**curl/python urllib
- 合法 date过去某日→ 202任务成功。
- 未来日期 / 超 90 天 / 格式错 → 400。
- 百世 + date → 400。
- 不传 date → 走 offset行为不变
2. **各站实测**(指定一个过去日期触发任务):
- 中通:跨月日期(已知 OK复用已验证的跨月导航
- 顺心 / 韵达 / 安能:实测其日期控件是否接受任意过去日期字符串;若控件是日历选择器需翻月,则按中通同法扩展(本轮发现则记录、必要时追加改动)。
3. **业务日期快照**:下载后 `GET /status``*_business_date` == 指定 date。
4. 改完跑 Black + `py_compile`
## 九、非目标YAGNI
- 前端 UIcheckbox / 日期选择器)——开发者接口,不进前端。
- 周期调度指定日期——周期恒走 offset。
- 批量日期 / 日期范围下载——单次单日。