docs: add design spec for date-specific download API (developer interface)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
131
docs/2026-07-29-指定日期下载接口-design.md
Normal file
131
docs/2026-07-29-指定日期下载接口-design.md
Normal 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` 的统一透传签名;百世任务实际不会带 date(server 已拦截)。
|
||||
|
||||
## 六、业务日期快照
|
||||
|
||||
`_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)
|
||||
|
||||
- 前端 UI(checkbox / 日期选择器)——开发者接口,不进前端。
|
||||
- 周期调度指定日期——周期恒走 offset。
|
||||
- 批量日期 / 日期范围下载——单次单日。
|
||||
Reference in New Issue
Block a user