docs: add design spec for date-specific download API (developer interface)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-07-29 12:58:08 +08:00
parent e2ae3acd68
commit a68fd46c51

View File

@@ -0,0 +1,131 @@
# 指定日期下载接口(开发者)— 设计文档
> 日期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。
- 批量日期 / 日期范围下载——单次单日。