diff --git a/docs/nas/backend-query-design.md b/docs/nas/backend-query-design.md index f1a682e..5f25309 100644 --- a/docs/nas/backend-query-design.md +++ b/docs/nas/backend-query-design.md @@ -1,6 +1,6 @@ # 后端汇总与分页查询设计 -日期:2026-10-17。状态:用户于2026-10-17确认设计;实施计划待审阅,尚未修改查询接口或部署后端。 +日期:2026-10-17。状态:用户确认设计及实施计划;实现、本机验证与最终审查通过,待NAS部署确认。 ## 目标与范围 diff --git a/docs/nas/backend-query-review.md b/docs/nas/backend-query-review.md new file mode 100644 index 0000000..f1f057f --- /dev/null +++ b/docs/nas/backend-query-review.md @@ -0,0 +1,66 @@ +# 后端汇总与分页:最终审查及交付 + +日期:2026-10-17。状态:实现、本机验证、各任务审查及最终跨模块审查通过,尚未部署NAS。 + +主目录:D:\CodeProject\mi-fitness-health;分支nas-health;最终产品代码提交317194c(最后一提交仅为QA截图采样修正,产品实现截至6292755,部署脚本截至ecbd485)。本记录提交只更新文档,不改候选镜像、发布包或产品。 + +本机:85项Python、53项Node、7/30天八类页面、四身份原生附件、隔离容器验证完成。性能范围和限制见 backend-query-validation.md。90万心率旧响应73569039字节,新首屏3570字节;这不是NAS耗时测量。统计/图表/每页10条明细由服务器返回,CSV双端流式,不在浏览器构建全量数组。 + +离线包:artifacts/query-summary/release;tar SHA256为2aeb958f5251d25a8d749f289c725cbb38043e7e687c0cef1c2dce6c063509b9,manifest SHA256为a1a2d51843b4b3b5bbf71bbfb38c8ca87ab2733f83d41e7b1c875641f63e346e。已本机复核全部9项发布哈希,未带Deploy参数。 + +仅拟替换健康backend/web为local/mi-fitness-nas:20261017-summary8,worker保持audit3;原密码来源、数据库和密钥挂载、日历及端口不变,失败尝试回退。正式更新前仍需用户明确批准,并在本机交互终端输入sudo密码。 + +兼容副本已同步13个文件、0删除;最终复核0文件需更新,未覆盖并发修改。未推送网络仓库。 + +已知边界:一次早期浏览器采集只有Uncaught文本、没有堆栈;增强记录后的两轮完整验收未再现,原证据保留,未宣称修复。窄屏头部拥挤是保留的原样式。SQL仍随样本数扫描;大数据NAS查询耗时尚未实测。同刻多设备体重使用明确的稳定主键次序,个别latest可能与旧未定义次序不同,均值/样本集合不变。 + +## 最终独立审查原文 + +# 整分支最终独立审查 + +范围:`497fd1c..317194c21c51373b82decb351840ea9985dd349a`,分支 `nas-health`。首先阅读已批准的 `docs/nas/backend-query-design.md`、`docs/superpowers/plans/2026-10-17-backend-query-summary.md`,继而核对各任务已通过审查、最终验证记录、整分支产品/部署差异与直接调用方。本轮重点为跨模块集成,未重复局部全量审查或测试。 + +**Spec verdict:APPROVED。** + +**Quality verdict:APPROVED。** 未发现本次引入且需要阻塞交付的新缺陷,无 must-fix。 + +## 跨模块证据 + +| 核查项 | 结果与位置 | +| --- | --- | +| 筛选集合贯通 | `storage/view_queries.py:95` 的共享 CTE 统一用户、北京时间范围、心率类型、小睡和压力等级,之后再取最近 N 条/最新记录。`:190`、`:219`、`:257`、`:264` 的统计、图表、分页总数及导出复用同一选择。`web_assets/static/js/health-ui.js:23` 为首屏、records、export 构造同一日期/筛选参数,导出不携带当前 page。 | +| 统计及前端消费 | `services/view_query_service.py:17` 输出统计、coverage、points 和 page;`health-views.js:128` 直接消费服务端统计,不以当前 10 条重算均值。分页 detail 使用 page.total 与当前 rows,`health-ui.js:88` 仅替换明细并在总数减少时回退有效末页。`app.js:374` 单次 overview 请求的字段与服务端 `view_query_service.py:31` 一致。睡眠实睡回退、缺失值、样本均值和体重全部测量均值保持设计口径。 | +| SQLite 一致性和排序 | `view_queries.py:86` 显式 query_only/BEGIN,`view_query_service.py:17` 将首屏所有读取置于同一事务;分页另开短事务。所有读取按真实整秒、小数秒、id 排序;北京时间归日避免 SQLite 毫秒舍入跨午夜。`storage/__init__.py:269` 最终关闭连接;未改数据库结构或同步写入实现。 | +| 旧响应与日历 | `health-ui.js:67` 的当前查询判断包含请求编号、类型、日期与筛选;`:88` 又核对分页编号和当前 model。日历晚到只作用于当前 model,并使用最新页码/rows 重绘。overview 另有请求编号与范围保护。已有前端测试明确覆盖这些交错路径。 | +| 查询/导出身份一致 | `app.js:152` 与 `health-ui.js:110` 共用 getHeaders:state Key 优先、输入框回退。浏览器 Key 用临时同源 POST 表单提交并立即清除;无 Key 保留原生 GET。`web.py:156` 和 `:180` 对服务器 Key 统一优先,并按大小写无关方式去除旧头,避免摘要/导出用户不同。POST 只对白名单导出转换为无 body GET。 | +| Origin 与授权 | `web.py:68` 的既有 Host、Basic、Origin 检查仍先于代理执行;新 API 仍经过全局 `_gate`(`api.py:176`),并由 `_resolve_context`(`:243`)解析身份。Key 未进入导出 URL,响应头白名单不回传鉴权头;after_request 保留 no-store。 | +| 双端流生命周期 | `api.py:978` 在每个导出的单一线程内创建、推进和关闭迭代器,取消时关闭任务排入原线程且不被 shutdown 丢弃;`:1003` 在发送失败/断开时显式 aclose。`view_queries.py:264` 分批 fetchmany(256),finally 关游标。`web.py:195` 的 finally 与 `:203` 的 call_on_close 分别覆盖已消费及未开始消费的响应;没有全量 CSV 缓冲。正在执行的 SQLite 读取可先完成再关闭,是已有明确边界。 | +| 部署范围 | 对照既有 `deploy/compose.override.yml` 与 `deploy/query-summary/compose.override.yml`,除注释外仅 backend/web 镜像不同;worker、端口、密码来源、日历挂载相同,数据库挂载继承未改的 base compose。`update-health.sh:54` 只对 backend/web 使用 `up --no-deps --no-build --pull never`,另比较其他健康服务 id/image。`:50` 安装回退 trap,保留旧配置与镜像。 | +| 发布身份与生产核对 | `update-health.sh:25` 严格验证去末尾 CR/LF 后的 digest,仅接受同包索引/配置身份。`check-ready.py:35` 仅取 OpenAPI paths 元数据,不发健康查询/导出;资源、代理、匿名拒绝、挂载/端口和日历检查符合批准边界。最终截图提交仅改 QA,产品、镜像与发布包未因截图采样变化。 | + +上述产品路径均相对于 `src/mi_fitness_mcp/`。 + +## 采信的验证及本轮检查 + +- 已核对 `docs/nas/backend-query-validation.md`、task-4-report.md、task-1-review.md、task-2-review.md、task-3-fix-1-review.md、task-4-fix-1-review.md,并阅读对应流式、分页测试和一致性入口的直接证据。 +- 采信最终已有 **85 Python + 53 Node**、真实浏览器 16 视图与四种身份原生附件、独立 Linux 容器、合成 1x/10x 和发布身份/就绪模型验证。本轮没有重跑这些已有效的检查,也未把这些数字称为本轮执行结果。 +- 本轮实际执行 `git diff --check 497fd1c..317194c`,exit 0;读取时 HEAD 为 `317194c21c51373b82decb351840ea9985dd349a`,工作区干净。未发现需要额外最小复现的具体产品疑点。 + +## 已知边界与交付结论 + +- 同刻体重旧 oracle 72.9、新稳定 id 次序 72.91 是已批准的规格差异,记录集合及均值不变,不列为回归。 +- 首轮 Uncaught 缺 stack 无法归因,随后增强观测两轮未复现;继续保留其记录,不宣称已修复,也不单凭该历史事件要求重跑所有验收。 +- SQLite 范围扫描仍随样本规模增长;90 万心率的本机统计仍约 8.3 秒。首屏约 3.6 KB 证明传输有界,不证明 NAS 即时响应。已有窄屏控件拥挤和依赖弃用警告不扩大处理。 +- **NAS 未部署。** 本审查批准本机实现和可审阅发布候选,不代替仅替换 backend/web 的用户部署确认,也不声称 NAS 引擎导入已实测。 +- 本轮仅新增本报告;未改产品、QA、部署源、发布包、Git index 或 HEAD;未连接 NAS,未读取真实健康记录、数据库或凭据。 + + +## 执行中作出的决定(完整记录) + +Ruling: Tool rejected a fresh implementation agent with thread limit reached. Reuse idle implementation agent with a fresh task brief, retain separate read-only reviewer — no close-agent capability is exposed — cost if wrong: more context carryover; narrow task scope and independent review mitigate it. + +Ruling: CSV iteration must use one dedicated worker thread (including SQLite iterator creation/next/close), with async chunk delivery — SQLite connections are thread-bound and event-loop SQL would stall unrelated requests — cost if wrong: an extra worker per active export; no full-result buffering and lifecycle tests bound the cost. + +Ruling: Preserve selected client identity for CSV via a same-origin POST form and export-only proxy adaptation — ordinary attachment links cannot send X-API-Key and returning a full Blob violates bounded browser memory — cost if wrong: a narrowly scoped POST download branch; test auth/Origin/GET compatibility and no Key in URL. + +Ruling: For equal-instant body measurements, latest uses the approved time/id DESC order (synthetic tie72.9 vs72.91 now chooses72.91), not the old API's unspecified tie order — freeze the oracle, compare unambiguous fixtures and test ties separately — cost if wrong: one tied measurement may differ from old display; sample set and mean remain unchanged, report explicitly. diff --git a/docs/superpowers/plans/2026-10-17-backend-query-summary.md b/docs/superpowers/plans/2026-10-17-backend-query-summary.md index b060b40..de906bb 100644 --- a/docs/superpowers/plans/2026-10-17-backend-query-summary.md +++ b/docs/superpowers/plans/2026-10-17-backend-query-summary.md @@ -45,14 +45,14 @@ **Interfaces:** `ViewSelection(kind:str,start_date:str,end_date:str,sample_type:str|None=None,include_naps:bool=True,pressure_level:str|None=None,latest_only:bool=False,limit:int|None=None)`是不可变参数对象。`ViewRepository(db:Database,user_id:str)`提供`snapshot()`上下文(返回sqlite3.Connection)、`statistics(selection,connection)->dict`、`points(selection,connection)->list[dict]`、`page(selection,page:int,connection)->dict`、`iter_records(selection,connection)->Iterator[dict]`。`ViewQueryService(db:Database,user_id:str)`提供`get_view(selection,page:int=1)->dict`、`get_page(selection,page:int=1)->dict`、`get_overview(start_date:str,end_date:str)->dict`和`iter_export(selection)->Iterator[dict]`。 -- [ ] 写失败测试:`test_selection_before_limit_and_paging`断言31条压力样本先筛26–50再取最近11条,第一页10条、第二页1条;总数11、均值来自全部11条,完整导出也是这11条。 -- [ ] 写失败测试:`test_equal_timestamps_stable_order_and_owner_isolation`将相同时间的多种类型/设备记录与其他用户混入,断言连续两次查询顺序一致、分页并集恰为筛选集合且没有别人的记录。 -- [ ] 写失败测试:`test_china_midnight_sleep_and_nulls`用北京时间跨日、UTC等价时刻、8小时实睡+15分钟清醒、小睡和NULL阶段数据,断言醒来日、实睡480分钟、有效日期均值正确;压力0保留,缺失日不补0。 -- [ ] `qa/view-statistics-oracle.cjs`仅在测试中调用基线`HealthViews.build`,读取stdin合成记录,输出卡片原始数值、图表、参考均值。`test_all_eight_kinds_match_frontend_baseline`逐类比较后端结果与oracle,浮点容差1e-9;不把JS运行时加入产品依赖。 -- [ ] 运行`python -m pytest tests/test_view_queries.py tests/test_view_statistics.py -q`,确认尚无新模块导致失败。 -- [ ] 实现上述接口。表与字段用固定白名单、值参数化。时间先用用户+宽时间范围缩小候选,再以SQLite时区转换精确筛选;无时区时间戳按既有数据的UTC含义解释并在测试固定。睡眠分期在SQLite JSON函数中计算,不加载所有会话至Python列表。均值按样本sum/count,不平均日均值。长范围桶大小`ceil(days/120)`;体重每桶选最近记录,其他桶口径与oracle一致。 -- [ ] `get_overview`只输出`steps,active_kcal,distance_m,resting_mean_bpm,mean_daily_asleep_minutes,mean_spo2_pct,mean_stress,activity_days,sleep_days,resting_count,spo2_count`,不含原始列表。睡眠以批准设计的实际实睡字段优先,统一旧总览只读duration字段的差异。 -- [ ] 增加`test_first_view_uses_one_read_snapshot`与`test_large_input_uses_sql_paging`,断言首屏同一事务、明细只读取当前页、摘要不调用原始样本全量查询方法。运行本任务测试通过后,只暂存相关文件,保存本地提交。 +- [x] 写失败测试:`test_selection_before_limit_and_paging`断言31条压力样本先筛26–50再取最近11条,第一页10条、第二页1条;总数11、均值来自全部11条,完整导出也是这11条。 +- [x] 写失败测试:`test_equal_timestamps_stable_order_and_owner_isolation`将相同时间的多种类型/设备记录与其他用户混入,断言连续两次查询顺序一致、分页并集恰为筛选集合且没有别人的记录。 +- [x] 写失败测试:`test_china_midnight_sleep_and_nulls`用北京时间跨日、UTC等价时刻、8小时实睡+15分钟清醒、小睡和NULL阶段数据,断言醒来日、实睡480分钟、有效日期均值正确;压力0保留,缺失日不补0。 +- [x] `qa/view-statistics-oracle.cjs`仅在测试中调用基线`HealthViews.build`,读取stdin合成记录,输出卡片原始数值、图表、参考均值。`test_all_eight_kinds_match_frontend_baseline`逐类比较后端结果与oracle,浮点容差1e-9;不把JS运行时加入产品依赖。 +- [x] 运行`python -m pytest tests/test_view_queries.py tests/test_view_statistics.py -q`,确认尚无新模块导致失败。 +- [x] 实现上述接口。表与字段用固定白名单、值参数化。时间先用用户+宽时间范围缩小候选,再以SQLite时区转换精确筛选;无时区时间戳按既有数据的UTC含义解释并在测试固定。睡眠分期在SQLite JSON函数中计算,不加载所有会话至Python列表。均值按样本sum/count,不平均日均值。长范围桶大小`ceil(days/120)`;体重每桶选最近记录,其他桶口径与oracle一致。 +- [x] `get_overview`只输出`steps,active_kcal,distance_m,resting_mean_bpm,mean_daily_asleep_minutes,mean_spo2_pct,mean_stress,activity_days,sleep_days,resting_count,spo2_count`,不含原始列表。睡眠以批准设计的实际实睡字段优先,统一旧总览只读duration字段的差异。 +- [x] 增加`test_first_view_uses_one_read_snapshot`与`test_large_input_uses_sql_paging`,断言首屏同一事务、明细只读取当前页、摘要不调用原始样本全量查询方法。运行本任务测试通过后,只暂存相关文件,保存本地提交。 ### Task 2: 只读API、CSV与代理 @@ -61,12 +61,12 @@ **Consumes:** 任务1的`ViewSelection`、`ViewQueryService`。 **Produces:** `GET /api/views/{kind}`、`GET /api/views/{kind}/records`、`GET /api/views/{kind}/export`、`GET /api/overview`;既有接口及响应不变。 -- [ ] 写失败测试:`test_view_auth_validation_and_legacy_compatibility`断言未授权访问被拒绝、已授权可查;非法kind、倒序日期、>3660天、page<=0/>1000000、limit<=0与无效压力等级被拒绝;已有`/api/heart-rate`仍返回原始列表。 -- [ ] 写失败测试:`test_export_matches_filtered_set_and_escapes_formula`断言CSV恰含同一筛选集合和源字段,压力包含源level与当前等级、UTF-8 BOM和中文表头,文本`=1+1`被安全转义,导出不受当前页限制。 -- [ ] 写失败测试:`test_stream_disconnect_closes_cursor_and_proxy_response`断言取消消费会关闭数据库连接与httpx响应,不先执行全量list/read;代理鉴权头不转发到浏览器响应,未授权网页导出返回401。 -- [ ] 运行`python -m pytest tests/test_view_api.py tests/test_view_export.py tests/test_web_streaming.py -q`,确认新接口缺失导致失败。 -- [ ] 路由中通过既有`_resolve_context`取得db/user,沿用现有授权,不额外发放Key。导出使用FastAPI StreamingResponse,连接生命周期在迭代器内,游标分批读取。仅新导出路径使用Flask流式代理和httpx流式响应,正常结束/异常/断开均在finally关闭;其他代理路径不改。 -- [ ] 运行本任务测试及`tests/test_web.py`,通过后保存本地提交。异常响应沿用现有错误样式,不把失败当空数据。 +- [x] 写失败测试:`test_view_auth_validation_and_legacy_compatibility`断言未授权访问被拒绝、已授权可查;非法kind、倒序日期、>3660天、page<=0/>1000000、limit<=0与无效压力等级被拒绝;已有`/api/heart-rate`仍返回原始列表。 +- [x] 写失败测试:`test_export_matches_filtered_set_and_escapes_formula`断言CSV恰含同一筛选集合和源字段,压力包含源level与当前等级、UTF-8 BOM和中文表头,文本`=1+1`被安全转义,导出不受当前页限制。 +- [x] 写失败测试:`test_stream_disconnect_closes_cursor_and_proxy_response`断言取消消费会关闭数据库连接与httpx响应,不先执行全量list/read;代理鉴权头不转发到浏览器响应,未授权网页导出返回401。 +- [x] 运行`python -m pytest tests/test_view_api.py tests/test_view_export.py tests/test_web_streaming.py -q`,确认新接口缺失导致失败。 +- [x] 路由中通过既有`_resolve_context`取得db/user,沿用现有授权,不额外发放Key。导出使用FastAPI StreamingResponse,连接生命周期在迭代器内,游标分批读取。仅新导出路径使用Flask流式代理和httpx流式响应,正常结束/异常/断开均在finally关闭;其他代理路径不改。 +- [x] 运行本任务测试及`tests/test_web.py`,通过后保存本地提交。异常响应沿用现有错误样式,不把失败当空数据。 ### Task 3: 前端只消费摘要与当前页 @@ -75,12 +75,12 @@ **Consumes:** 任务2的只读接口。 **Produces:** `HealthViews.fromSummary(kind,payload,range)->model`、现有`loadHealthView`/`healthPage`/`exportHealthRecords`改用新接口;`loadOverview`只调用一次`/api/overview`。 -- [ ] 写失败测试:`test_server_summary_drives_cards_without_raw_records`传入30个图表点和10条明细但总数90000,断言卡片和参考均值严格使用服务端statistics,不使用10条明细重算。 -- [ ] 写失败测试:`test_pagination_fetches_only_page_and_preserves_chart`断言翻页调用records端点、仅替换表格;CSV下载由导出URL完成,没有在JS拼接全量数组。 -- [ ] 写失败测试:`test_late_page_and_overview_responses_do_not_overwrite_new_range`覆盖翻页中换日期、换类型及日历晚到;旧响应不能覆盖新表格、图表、控制台。`test_empty_and_failure_remain_distinct`保留空与失败的区别。 -- [ ] 运行`node --test qa/health-server-summary.test.cjs qa/health-server-pagination.test.cjs`,确认新入口缺失失败。 -- [ ] `fromSummary`沿用已有配置、格式化、渲染器,构造卡片/参考值,不调用build执行样本聚合。表格取payload.page.rows,总数取page.total;CSV改为原生浏览器同源附件下载。固定参考值、纵轴下限40/80/40及双色日期横线不变。旧build保留为对照和兼容辅助,不走新的页面请求链。 -- [ ] 修改总览为单个紧凑接口,保留取消/过期请求保护;新静态资源版本升级。运行新增前端测试和受影响的原有前端测试,通过后保存本地提交。 +- [x] 写失败测试:`test_server_summary_drives_cards_without_raw_records`传入30个图表点和10条明细但总数90000,断言卡片和参考均值严格使用服务端statistics,不使用10条明细重算。 +- [x] 写失败测试:`test_pagination_fetches_only_page_and_preserves_chart`断言翻页调用records端点、仅替换表格;CSV下载由导出URL完成,没有在JS拼接全量数组。 +- [x] 写失败测试:`test_late_page_and_overview_responses_do_not_overwrite_new_range`覆盖翻页中换日期、换类型及日历晚到;旧响应不能覆盖新表格、图表、控制台。`test_empty_and_failure_remain_distinct`保留空与失败的区别。 +- [x] 运行`node --test qa/health-server-summary.test.cjs qa/health-server-pagination.test.cjs`,确认新入口缺失失败。 +- [x] `fromSummary`沿用已有配置、格式化、渲染器,构造卡片/参考值,不调用build执行样本聚合。表格取payload.page.rows,总数取page.total;CSV改为原生浏览器同源附件下载。固定参考值、纵轴下限40/80/40及双色日期横线不变。旧build保留为对照和兼容辅助,不走新的页面请求链。 +- [x] 修改总览为单个紧凑接口,保留取消/过期请求保护;新静态资源版本升级。运行新增前端测试和受影响的原有前端测试,通过后保存本地提交。 ### Task 4: 本机整体验证、可部署包与兼容同步 @@ -89,13 +89,15 @@ **Consumes:** 已通过任务1–3的服务和接口。 **Produces:** 只包含backend/web替换的离线包、可核对的数据一致性与性能报告;包和截图放入忽略的artifacts目录,不提交Git。 -- [ ] 本机生成30天合成数据,每天3000条心率、1000条血氧、1000条压力,另含活动/睡眠/体重/运动/异常样本。再以10倍密度比较首屏响应体积,`test_payload_does_not_scale_with_raw_samples`断言仍只含10条明细、<=120桶、无原始全量数组;记录具体字节、耗时和内存,不预先宣称倍数提升。 -- [ ] 通过同一只读数据库分别调用旧查询+基线统计与新汇总,核对7天/30天各类数值和CSV集合;检查分页并集及相同时间排序。只使用本机生成的记录。 -- [ ] 浏览器实际检查总览、八类健康页、7天/30天切换、翻页、导出、深浅主题、控制台、参考线和工作日标记;记录首屏节点、响应体积与加载耗时;断言没有全量原始接口请求和浏览器异常。 -- [ ] 本机Linux容器验证新backend/web的联通、认证、CSV流传输、取消与挂载;保留worker旧版本。更新包的防覆盖哈希从部署前只读查询取得,脚本使用`up -d --no-deps ... backend web`,核对worker及其他健康服务未重建,并保留回退镜像与覆盖配置。不得在NAS运行合成、压力或单元测试。 -- [ ] 做一次独立代码审查,修复本次引入的重大问题,仅重跑受影响验证。更新报告区分“本机已验证”与“NAS未部署”。运行`python scripts/sync_legacy_workspace.py`核对兼容副本,再核对差异保存本地提交,不推送。 +- [x] 本机生成30天合成数据,每天3000条心率、1000条血氧、1000条压力,另含活动/睡眠/体重/运动/异常样本。再以10倍密度比较首屏响应体积,`test_payload_does_not_scale_with_raw_samples`断言仍只含10条明细、<=120桶、无原始全量数组;记录具体字节、耗时和内存,不预先宣称倍数提升。 +- [x] 通过同一只读数据库分别调用旧查询+基线统计与新汇总,核对7天/30天各类数值和CSV集合;检查分页并集及相同时间排序。只使用本机生成的记录。 +- [x] 浏览器实际检查总览、八类健康页、7天/30天切换、翻页、导出、深浅主题、控制台、参考线和工作日标记;记录首屏节点、响应体积与加载耗时;断言没有全量原始接口请求和浏览器异常。 +- [x] 本机Linux容器验证新backend/web的联通、认证、CSV流传输、取消与挂载;保留worker旧版本。更新包的防覆盖哈希从部署前只读查询取得,脚本使用`up -d --no-deps ... backend web`,核对worker及其他健康服务未重建,并保留回退镜像与覆盖配置。不得在NAS运行合成、压力或单元测试。 +- [x] 做一次独立代码审查,修复本次引入的重大问题,仅重跑受影响验证。更新报告区分“本机已验证”与“NAS未部署”。运行`python scripts/sync_legacy_workspace.py`核对兼容副本,再核对差异保存本地提交,不推送。 - [ ] 给用户查看实际变更、测量结果与最终更新包,按规则确认仅更新健康backend和web。得到部署确认后,打开本机交互终端由用户输入sudo密码,部署就绪核对通过再报告成功。 ## 自查结论 已覆盖批准设计的八类统计、总览、分页、导出、只读兼容、时区、筛选、长范围、界面保持、本机性能验证、Git与旧副本同步、NAS回退。任务1先定义统一筛选及数值协议,任务2/3依赖该协议,顺序实施可避免接口分叉;推荐当前会话原生执行,最后独立审查。此文档为实施计划,不代表接口已经实现或性能已经改善。 + +执行状态:2026-10-17 本机实施与独立审查已完成,部署步骤等待用户确认。验证与审查见 docs/nas/backend-query-validation.md、docs/nas/backend-query-review.md。