# 核销表 · 第12环 归档 > 90分级验收(孙总方法论·硬门禁)。已吸收独立交叉验证修正(⚠️)。auto=机器判定,human=目视。 ## 模块目标 ①history 按日期/品类筛选(现平铺) ②approved 下载交付包 zip 结构完整 ③多套打包(1-3套各独立笔记夹) ④客户输入物(产品/标杆)可导出(数据归属红线)。 ## Ralph 修改步骤 | # | 改什么 | 落点 | |---|--------|------| | S1 | list_tasks 加 date_from/date_to/product_id query param;fromisoformat 转换,invalid 返回 40001 | tasks.py:137-149 | | S2 | _fmt_task 加 product_name(调用方传入);list_tasks 一次性 `Product.id.in_([...])` 建 id→name dict 避免N+1;⚠️顶部补 `from app.models.product import Product`(当前无此import) | tasks.py:58-64 | | S3 | history/page.tsx 加 dateFrom/dateTo state + 两个 ``(提 DateRangeFilter 子组件防超100行) | history/page.tsx:28-44 + JSX 50-94 | | S4 | HistoryTable 表头/行加"产品"列(显 product_name ?? '-') | history/components.tsx:70-127 | | S5 | TaskListItem 加 `product_name?:string` | dto.tasks.ts:8-16 | | S6 | ⚠️多套打包:查所有 is_selected=True TextCandidate 按 angle_label 分组每组取第一;notes 列表长度=选中套数(改 line 77-85 为 for loop);images_data 循环外查一次,每套传 `list(images_data)` 副本(退化:图共享复制进各套夹) | packaging_task.py:45-47,77-85 | | S7 | 新建 GET /products/export + /benchmarks/export(format=json/csv);require_write_permission;⚠️benchmarks/export 需 Product JOIN 填 product_name(复用 _fmt_benchmark 不够,只有 product_id) | products.py + benchmarks.py | | S8 | 配置页产品 tab + 历史归档页各加"导出数据"按钮,通用 ExportButton(url,filename) 调 getBlob 下载 | history/components.tsx 或 config/ExportTab.tsx | ## 验收清单(做完一个勾一个) - [ ] **A1**〔auto〕日期筛选:传 date_from/date_to → items 全落区间;传 date_from=2099 → 空数组 total=0。⚠️审计补:再验 `date_from=not-a-date` → code=40001(非500)。 - [ ] **A2**〔auto〕product_id 筛选:传 product_id=1 → 每条 product_id=1 且 product_name 非空。 - [ ] **A3**〔human〕前端产品列:/history 表头有"产品"列,每行显产品名(非数字),与 GET /products/{id}.name 一致。 - [ ] **A4**〔human〕日期控件:有开始/结束 date input,填后表格刷新,XHR URL 含 date_from/date_to,结果≤全量。 - [ ] **A5**〔human〕下载交付包:approved 任务点"下载交付包",≤60s 触发下载 delivery-{id}.zip(>1KB);`unzip -l` 含 note_01/文案.txt/发布清单/合规说明。 - [ ] **A6**〔auto〕zip 结构完整:`unzip -l` grep 文案.txt/发布清单/合规说明/note_01//.jpg 全非空。⚠️审计补:若 .jpg 空先查 ImageCandidate.url 文件容器内可达(packaging_task.py:64-70 读失败静默跳过)。 - [ ] **A7**〔auto〕文案.txt 格式:`unzip -p note_01/文案.txt` 含"【标题】"、不以 `{` 开头(JSON 已解析)。 - [ ] **A8**〔human+auto〕多套打包:⚠️审计改 human(需先 seed 3条 is_selected TextCandidate)→构造后 `unzip -l|grep 'note_0[1-3]/'|wc -l`≥3,三套标题互不相同。 - [ ] **A9**〔auto〕产品 JSON 导出:GET /products/export → 200,application/json,数组,每元素含 id/name/selling_points,且 selling_points 是数组(非JSON字符串)。 - [ ] **A10**〔auto〕标杆 CSV 导出:GET /benchmarks/export?format=csv → 200,text/csv,首行表头含 product_id/highlights/created_at,行数≥已录标杆数。 - [ ] **A11**〔auto〕导出权限:⚠️审计注——require_write_permission 当前只查 workspace_members 不分角色(workspace_guard.py:65-88);未登录调 → code=40101,workspace 成员 → 200。 - [ ] **A12**〔human〕不显 token 余额(红线):⚠️审计改 human(curl 拿不到Next.js水合后DOM)——浏览器 Ctrl+F 搜"余额/用量/tokens/balance"无结果。 ## 待北哥定义(不阻断代码) - 多套打包图片套间对应:ImageCandidate 无 set_index,是否加列关联文案?还是退化"图共享复制进每套夹"(本期默认退化)。 - 导出入口位置(产品管理 tab 按钮 / 历史归档页全局)。 - CSV 是否加 UTF-8 BOM(Windows Excel 不乱码)。