# 调查进展与实时展示 调查执行中可按需整理正文和流程参考图,Server 累计保留路径并生成 Mermaid,尚未形成结论时也能发布。 Server 将阅读进展、业务报告和浏览器展示请求分别保存。运行结束仍要求同轮最终报告, 图节点不绑定实际执行任务,标记只解释最近整理的路线、结果和限制;不参与调度、审批、等待或结束判断。 路线有实质调整、形成重要结果、需要保留失败尝试或整理报告时更新即可,不要求逐步发布、频繁刷新或及时同步。 ## 进展快照 `POST /api/v1/operational-tasks/{task_id}/agent-artifacts` 新增可选 `progress`: ```json { "request_id": "", "run_id": "", "source_session_id": "", "progress": { "summary": "整理投诉时段的监测核查路线,尚未形成结论。", "graph_update": { "title": "调查流程参考", "nodes": [{ "node_id": "read", "label": "核查监测记录", "status": "reference", "evidence_refs": [], "action_refs": [], "map_targets": [{"source_id": "pipes", "feature_id": ""}] }], "edges": [] } } } ``` 示例中的编号由实际任务和资产替换。正文最长 20,000 字符,`graph_update` 可省略。 图示沿用 [调查报告契约](investigation-report-contract.md) 的 Mermaid 安全限制。 节点标记可省略或为 null,均显示为参考步骤;新增量更新只接受以下五种: | status | 显示 | 含义 | | --- | --- | --- | | reference | 参考步骤 | 路线或分支说明,不断言是否执行 | | recorded | 已有结果 | 已整理出结果或依据,不表示任务、计算或现场动作完成 | | needs_input | 待补充条件 | 记录缺少的资料、权限或人工事实,不控制任务等待 | | failed | 尝试失败 | 保留一次失败尝试及原因,后续路线另建节点 | | not_taken | 未采用路线 | 未采用、不适用或改走其他分支 | 旧图读取兼容:pending/running 转为 reference,completed 转为 recorded,waiting/waiting_human 转为 needs_input, skipped/cancelled 转为 not_taken;failed 保留。仅规范化读取结果,不改写历史文档,不再显示“正在执行”或“记录时执行中”。 REST 与 MCP 使用相同的旧标记映射。MCP 的 progress/report 图示在分页及 data_path 投影之前完成转换, 读取整份说明、节点列表或单个 status 时含义一致;实际 task/run/action 状态和旧执行计划状态保持各自语义。 `map_targets` 只接受 `junctions/pipes/valves/pumps/tanks/reservoirs/scada` 七类来源和要素编号。 图中节点引用唯一,证据和动作引用必须属于该任务。新 Agent 使用下述增量更新,节点编号在任务各轮保持稳定。 Server 不验证依赖执行顺序或推断节点完工。 `progress.graph_update` 包含可选 `title`、`nodes` 和 `edges`。节点沿用 `node_id/label/status/evidence_refs/action_refs/map_targets`, 可用 `detail` 记录实际结果或失败原因;连线为 `{source,target,label?}`。仅提交新增或变化的内容, `update_investigation_graph` 合并节点、追加明确连线并生成 Mermaid,保留已有路径和引用。 尝试失败时标记 `failed`,重试或替代路线使用新节点及连线;原失败节点不能改写为成功。 仅更新正文或传入 `diagram: null` 均保留累计图,原始 Mermaid 不再由新 Agent 生成。 首次增量更新沿用最近保存的旧图,将原源码保存在 `source_mermaid`,保留原分支和形状后追加内容; 旧图内节点不得改名或重用为其他步骤。已被旧实现覆盖且未存入报告的内容无法还原。 尚未迁移的旧图仍接受旧客户端显式提交的完整图;仅更新正文或传入 `diagram: null` 时自动接管旧图, 保留原源码并转为累计图。任务启用累计图后拒绝旧客户端重画覆盖,不提供通过空值清图的入口。 省略 `progress` 保留原快照,同一个请求编号重试只发布一次。 `workbench.operational_task_progress` 每个任务和 run 保存最新一份快照,独立于七天事件清理。 任务详情和 Agent 上下文返回 `latest_progress`,包含 `revision/run_id/execution_generation/` `published_at/source_session_id/progress`。优先返回当前执行代次;新轮次尚未发布时保留最近的累计图及其原始轮次。 `get_task_context(section="progress")` 同样允许读取上轮累计图以接续,摘要和图示不证明新轮次已执行。 报告发布时由 Server 存入累计图及其已有依据,历史报告保持独立快照;最多保留 100 个节点、200 条连线,超限显式报错,不静默裁剪。 快照更新发出 `operational.progress.changed`,前端据此读取最新内容。 ### 旧任务图迁移 在 Server 仓库运行 `python -m scripts.migrate_investigation_graphs --plan /受保护目录/plan.json --project-id <项目UUID>`; 多个项目重复指定 `--project-id`。预览只读数据库,计划文件以私有权限保存原始进度文档和迁移后的完整文档。 核对项目、业务库、任务和图数量后,运行 `python -m scripts.migrate_investigation_graphs --apply /受保护目录/plan.json`。 连接使用 MetaDB 路由,计划中不保存连接凭据。迁移逐项目事务执行,与正常发布使用相同的事件锁和任务锁, 预览后仍为旧图但已发生变化的行会使该项目回滚;重复执行跳过已经接管的图。 迁移仅更新已有进度图及其 revision,保留原 summary、发布时间、run、会话、节点说明和引用; 原 Mermaid 字节保存在 `source_mermaid`,旧节点标记转换为参考标记,不解析或猜测原始连线。 历史报告保持原样,通过读取兼容显示新标记。迁移逐项目校验任务、run 和报告整行摘要未变, 并发出进度变更事件及写入独立执行回执;不产生报告更新通知。备份和回执不得提交到仓库。 ## 浏览器实时联动 `POST /api/v1/operational-tasks/{task_id}/presentation-events` 接收 `request_id/run_id/source_session_id/command`,要求任务执行权限。 Server 校验当前 run、绑定会话、有效租约和幂等身份,记录 `operational.presentation.requested`。 响应为 `recorded/event_seq/run_id/execution_generation`,表示展示请求已保存, 不表示浏览器已显示或现场动作已执行。 | command.action | 参数 | | --- | --- | | locate_features | layer、feature_ids | | zoom_to_map | center:WGS84 经度纬度、zoom:0–24 | | highlight_features | layer、feature_ids、必填 effect:steady 或 pulse | | clear_highlights | 无 | | open_chart | chart_id、chart、非空 evidence_refs | | close_chart | chart_id | | set_overlay | overlay_id、title、mode:geometry 或 heatmap、evidence_id,可选 data | | clear_overlay | overlay_id | | render_scada_analysis | items:device_id 与 high/medium/low/unrated 等级,设备编号不重复 | | clear_scada_analysis | 无 | 以上十种动作与 Chat 的 `present_view`、Dev JSONL 共用扁平 `command: {action, ...字段}`。Chat 使用 `frontend-action@2` 和执行回执;任务仍只记录展示请求,Dev 只做本地预览。三者使用独立的地图源、SCADA 图层和图表状态。Dev 的动作数据行必须为 `{"record_type":"presentation","command":{...}}`;界面块行使用 `record_type: ui_block`,新示例明确带 `phase: final`。普通调查数据保持来源字段,不执行其中的 action。仓库示例见[前端统一展示夹具](../../next-tjwater-frontend/tests/fixtures/unified-presentation.jsonl),其中编号来自测试夹具。 图表使用普通折线 line、柱状 bar,或当前曲线加历史散点的 time_comparison,包含 `title/chart_type/x_data/series`。最多 100 个横轴值和 8 组有限数值序列, 序列长度与横轴对齐,引用证据必须属于当前任务。地图高亮来源沿用上述七类。 覆盖层的权威数据来自不可变证据。省略 `data` 时,Server 从证据快照提取现有 GeoJSON, 或使用已记录的 `longitude/latitude` 点位,将 `FeatureCollection` 加入事件中的 `command.data`。 若传入 `data`,必须与同一证据快照解析出的内容一致;Server 始终发布快照解析结果,不能用调用参数覆盖证据。 热力图只接受记录中的点,权重为有限非负数,未记录权重时取 1。 没有坐标的地址、空数据、非法几何、模型自绘区域均不能生成覆盖层。该接口不做地址解析、 范围推算或污染传播计算。展示事件总大小上限为 512 KiB,超限返回 422。 `generate_node_coverage` 是独立的节点空间覆盖工具,适用于普通项目聊天和后台调查, 不绑定爆管、阀门隔离或固定执行顺序。调用者提供需要比较的节点分组,Server 从当前项目 `gis.node_geometries` 读取真实位置。监测设备、投诉、地址等须先关联模型节点, 不接受模型自由生成的坐标。具体适用范围和参数见[节点覆盖工具](node-coverage-tool.md)。 计算先投影到局部米制坐标,取凹包(默认 ratio=0.25)、加展示缓冲(默认 25 m), 再以 2 m 容差简化并转回 WGS84;凹包比例与缓冲距离可在有界参数内选择。 默认展示各组的地理包络,不判断连接关系;需要时可按模型连接拆分,额外指定视为断开的连接。 少量点、重合点和共线点同样通过缓冲形成面。多组结果一起保存在 `result.map_payload`, 避免连续 `set_overlay` 覆盖前一组;`result.coverage` 保存来源节点、映射完整性及算法参数。 该面不推断故障、污染、实际停水、水力影响或用户服务边界,缓冲距离也不是供水服务半径。 缺失几何时标记 `partial`,没有可用位置时不生成面。输入最多 8 组、合计 10000 个不同节点; 几何输出遵守覆盖层顶点和大小限制,超限要求缩小范围,不静默抽样或改变原分析结果。 MCP 保存完整查询响应作为不可变证据,展示解析器同时支持响应信封中的 `data.result.map_payload`。Agent 使用 `set_overlay` 引用本次 `evidence_id` 并省略 `data`。 超过内联返回预算时,响应的 `data.presentation.command` 仍保留可发布的展示命令, `data.presentation.groups` 保留覆盖完整性计数, 完整几何留在证据中,不能把缩略返回误认为没有空间成果。调查按实际步骤同步发布展示: 地址得到明确坐标后缩放定位,取得候选资产后定位候选,检查影响节点后生成并展示覆盖示意, 形成或调整关阀方案后定位阀门,读取监测对照后展示点位及序列。 图表同样随结果返回发布:监测趋势与事件对照用折线图,可比候选评价或方案用柱状图, 水力及污染传播等模拟按实际返回的时刻、对象和指标画图。压力、流量、浓度等不同单位分图, 注明来源、范围与实测或模型属性;没有同口径基准时不计算或绘制改善量,缺测不补零。 过程图表通过 `open_chart` 提供 `chart_id/chart/evidence_refs`,每图最多100个横轴值、8组序列, 超限时分图或明确说明抽样、聚合范围;单值或无可比数据用文字或表格。 同指标、单位和窗口的同期复合图按对象分别发布稳定 chart_id,标题为“对象ID 共同标题”,每条序列名以“对象ID ”开头。共同标题、chart_type、轴名、x_data、当前序列去掉对象前缀后的名称与 sample_times 完全一致时,浏览器将对象数据集聚合为一个子图表:右侧按钮切换对象,下方图例控制序列;窄屏对象列表移到下方。普通趋势总览、柱状图和结构化 Chart 保持独立,不新增 objects 字段。原 chart_id 的 close_chart 只移除对应对象,关闭聚合卡片移除组内对象。 浏览器的总图表窗口统一管理过程图组与报告 Chart,子图表仍可手动独立弹窗放大。只有正在跟随的运行中任务新收到 open_chart 时可自动弹出总窗口;后续命令更新内容,不自动打开子窗口,用户关闭后不重弹。任务打开、切换、刷新、已完成任务载入、SSE 重连历史补载及历史导航只恢复数据,不弹窗。窗口意图是浏览器临时状态,不写入事件或历史快照,不新增协议字段;open_chart 的内容加载与窗口是否弹出是两个不同状态,Server 的 recorded 也不表示浏览器已经打开窗口。 time_comparison 的 x_data 使用精度一致、按窗口排列的 UTC+8 HH:mm 或 HH:mm:ss 共享槽,允许跨午夜但短于一天,不追加历史日期。series 同时包含当前 line 与历史 scatter,不混入 bar;sample_times 与 data 等长,有效值和真实带时区时间成对保留,缺测值与时间均为 null。时间转换后须与横轴槽一致,当前线使用同一窗口,每条历史序列来自同一历史日的对应窗口,不接受当天或未来日期。新任务展示与报告发布不满足这些规则时返回 TIME_COMPARISON_INVALID,旧保存内容保持结构读取。普通 line/bar 跳过同期语义,但不能将 line/scatter 复合图标为 line。 前端维护的脚本 scripts/export-view-command-contract.mjs 同步 Agent/Server 的结构协议及 tests/fixtures 下的统一 JSONL 和 chart-publication-cases.json;JSON Schema 不代替跨字段语义校验。Server 的独立测试用相同正反例校验 Pydantic 边界和 validate_time_comparison_chart;Agent 主机、原生工具及 Dev 的新图表输入也执行对应规则。 关键趋势与比较结论保留到报告中,最终报告的图表不替代过程发布。 ### SCADA 设备展示覆盖 SCADA 异常初次调查的监测过程图使用可选的 `chart.scada` 结构: `role=current|comparison`、`metric=pressure|flow`、`unit`、带时区的 `window_start/window_end`, 窗口固定为本轮截止时刻之前三小时;采样槽保持真实采样相位,截止非整刻不平移。超过100槽按连续时间分图,使用不同稳定chart_id,元数据仍声明完整三小时窗口;Server逐点核实后合并覆盖,缺少任一槽仍拒绝收尾。同期历史只取完整窗口末三个采样槽,同设备同期分图不得重叠。低频设备窗口无计划采样槽时,完整读取对应当天/历史三小时并核实无读数后,以截止时刻的单个null保留设备入口,标记no_scheduled_samples,不计作漏采。`value_field` 默认为 `monitored_value`,可显式选 `cleaned_value`。 每条序列绑定 `device_id` 和 `day_offset`(当天 0,此前七天 1 至 7)。 同期图每个设备包含当天 line 和七组 scatter,历史仅末三个对应采样槽有值。 Server 校验设备属性、单位、完整采样槽、真实时间与引用快照的对应值,允许数值四舍五入保留 1 至 6 位小数, 保留负流量;缺测必须有完整查询或明确空值记录证明,未查询不能视为缺测。 `chart.scada.gaps` 由 Server 生成并存入展示事件,前端保留设备入口并展示缺测点数。 回放全量查询的 `coverage.frozen_start` 记录服务端已冻结的最早时刻;历史早于该边界时, 以本轮匹配 replay/batch 的完整回执证明 `outside_frozen_window`,无需虚构一次成功的历史读取。 地图的 SCADA 定位/标记、已验证图表和最终报告 `scada_device_ids` 累计本轮目标; 监测明细表使用该字段声明完整设备集合。按指标和单位分图,跨多张总览取设备并集, 同一 `chart_id` 更新只采用最新覆盖,累计目标不缩小。关闭图表及清空地图只影响浏览器, 不撤销产物覆盖。最终报告及成功结束均拒绝遗漏,返回 `409 SCADA_PRESENTATION_INCOMPLETE`, `related_refs` 列出待补设备;Agent 补图后可重试原请求。普通进度和 interim 报告可继续发布。 “仅作参考”不豁免展示;确有缺测时仍发布有空槽的图表并保留设备, 完整查询无法完成时保留 interim 和具体失败原因,不伪造缺测来通过 final。 覆盖事实保存在 `workbench.operational_task_runs.artifact_manifest.scada_presentation`, JSON 包含累计 `targets`、按 `chart_id` 保存的最新验证 `charts` 及 `updated_at`。 它与 Run 一起保留,独立于七天展示事件清理;不依赖浏览器在线或回执。 不同 Run、项目和截止窗口不能抵数。恢复复核阶段沿用恢复证据门禁,不要求重画异常轮次图表。 历史读取不要求新字段,已完成报告不回写;旧 Agent 的无绑定图表仍可读,但不能通过新轮次的覆盖门禁。 部署按[工作台数据库变更门禁](workbench-database-rollout.md)核实目标 BizDB、备份和回滚演练。 先停止目标项目的任务写入并安排配套切换,在维护窗口应用 [035 迁移](../resources/sql/035_scada_presentation_coverage.sql),随后发布 Server、Agent 和前端。 迁移只为 Run 新增通用 JSON 产物清单,默认空对象;不会自动回填旧图或自动迁移其他项目。 未应用迁移的 SCADA 写入与收尾返回明确的 503,历史读取保持可用。 Run 读取投影显式排除内部清单,REST 与 MCP 原有 Run 结构保持一致。 若回退到本次读取兼容修复之前的 Server,须先备份清单再移除新增列;旧代码的整行读取不能直接带着新列回退。 本次实现不代表迁移或上线已执行。 业务 Skill 说明本步骤应展示什么,调查指令统一说明发布方法、非空几何检查、去重和失败处理。 同一阶段批量展示,数据或方案没有变化时不重复请求;不为地图动作增加无关业务分析。 报告的 `map_targets` 仅为手动定位入口。关键步骤的数据缺失或展示失败需记录具体限制, 没有浏览器连接也不阻止分析或报告收尾。 `GET /api/v1/operational-tasks/{task_id}/presentation-cursor` 在同一事务中读取 `task_id/execution_generation/active_run/event_cursor`,其中活跃 run 仅返回 `run_id/generation/status`。 浏览器打开 Dev 联动、切换任务或断线重连时,以此建立新基线,只消费之后的展示事件。 跟随开关只影响当前浏览器的临时展示,正文和图示更新始终可读,调查仍由后台继续执行。 关闭 Dev 面板不改变跟随开关。关闭跟随开关、切换任务或断线时清理临时效果, 执行结束停止执行态动效,已显示的图表与覆盖层保留供阅读。不回放历史请求,不重新执行调查。 ## 人工反馈续调 外围同步器根据动作回执找到关联子工单。样板创建固定为 `revision=1`,首次创建回执不作为 新的现场反馈入队。后续版本可唤醒已结束调查,旧报告和证据保留,生成新的执行代次。 现场备注从工单 `activity.kind=feedback` 的记录进入反馈证据,仍标明样板合成来源。 重复请求或同一源版本重放不增加执行代次,取消调查拒绝新反馈。 同步器先送达各父调查反馈,再推进预约工单投影的源版本。定向源事件和动作回执补查保留, 处理源更新早于回执保存的情况。全量对账只为源版本变化补查已结束调查,本次不回填历史反馈。 原投诉收到新版本后同步推进其已有来源修订号。数据不足仍正常结束本轮, 只有新的可信业务输入进入后续轮次,不依赖图示节点制造人工等待。 ## 部署与验证 新增 [023 迁移](../resources/sql/023_operational_task_progress.sql) 仅适用于 `tjwater_v2`, 增加进展表和两个事件 topic 的 CHECK 取值。2026-10-10 已在目标库完成全库备份、 迁移事务回滚演练及正式应用,其他项目和旧任务未改动。 实际 worker 曾成功投递指定调查,模型发布进展和报告,但因 service 账号的任务权限过滤 导致 `approved_data_scopes` 为空,误作数据不足结束。这不是业务数据确实不足。 用户确认开放规则后,任务 API 已移除该额外过滤,沿用账号已有的项目读取权限。 在线上下文返回七类来源,同一身份实际读取 SCADA 设备和外围系统工单成功;没有新增 角色权限或放宽跨项目、审批和设备控制边界。旧委托路径保留兼容,已有权限无需委托凭据。 用户级 `tjwater-operational-worker.service` 已重新启用。旧报告保留,未自动重新调查, 不能把旧报告视为修复后的业务验收。任务创建时间下界和并发配置见 [执行 worker](operational-task-workers.md),未回填旧任务。 进展读接口在表尚未部署时返回空,写入口须在迁移与 Server 更新后启用。 按现有数据库发布流程应用迁移,再更新 Server、Agent 和前端,运行中的 worker 需要加载更新。 聚焦检查: ```bash conda run -n server python -m pytest tests/unit/test_operational_tasks.py tests/unit/test_operational_presentation.py tests/unit/test_peripheral_task_sync.py tests/unit/test_peripheral_push_sync.py tests/api/test_operational_task_dynamic_flow.py tests/api/test_openapi_contract.py -q git diff --check ``` 这些检查覆盖隔离 HTTP 与状态机,不代替共享数据库迁移、真实模型运行或现场复测验收。