feat/dataset-detail-chart #5

Merged
gaozheng merged 74 commits from feat/dataset-detail-chart into main 2026-06-13 17:30:37 +08:00
2 changed files with 1385 additions and 0 deletions
Showing only changes of commit 751b486254 - Show all commits

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,202 @@
# 数据集详情图:扩展到其余 dd 类型 实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: 用 `superpowers:subagent-driven-development` 执行(每个 bite-sized 任务派一个 subagentTDD频繁提交。步骤用 `- [ ]`
> **铁律(项目记忆,务必遵守):** 任何对原版的不确定,**必须用 Playwright 实地学习原版**,禁止联想猜测;严格 **1:1 复刻**,不加原版没有的特性。**没有活样本的 dd 类型一律 BLOCKED禁止凭想象写渲染代码。**
**Goal:** 把「数据集详情图」从只支持 ERT 反演(`dd_inversion_data`)扩展到其余 dd 类型。先打通「控制器按 ddCode 走策略注册表」的分派骨架(未注册类型优雅降级「暂不支持」),再按"样本可得性"逐类落地有活样本的ERT 测量类、TEM/timeSensor做完整复刻无活样本的dd_grid / 轨迹 / 测井 / GPR只列调研占位 + 取样前置条件BLOCKED。
**Architecture:** 沿用现有分层,不重造:
- 编排层 `DatasetDetailController`(现状对非 `dd_inversion_data` 直接拒绝)→ 改为持 `ChartStrategyRegistry`,按 ddCode 选策略;策略决定「拉哪些接口 + 用哪个 View 渲染」。
- 异步数据层 `IAsyncDatasetRepository` + `ChartLoad`/`GridLoad` 句柄(`ApiBatch` 多请求聚合 + 注入解析函数)。每类新 dd 视形态新增句柄类型(如折线 `LineLoad`、图像 `ImageLoad`)或复用现有句柄。
- DTO 解析层 `src/data/dto/`(纯函数,单测友好,用抓到的真实响应做夹具)。
- 渲染层 `src/app/panels/chart/`:复用 QwtPlot轴/交互/图例)+ VTK 算法(等值线几何)。按形态归类复用或新建 View等值面类复用 `ContourPlotItem`/`GridDataChartView`;散点类复用 `ScatterPlotItem`/`RawDataChartView`;折线类(测井)新建 `LineChartView`图像类GPR新建 `ImageChartView`
- 页面壳 `DatasetDetailPanel`(多 Tab) / `DatasetDetailPage`(单 ds 内部页签)。
**Tech Stack:** C++17、Qt6 Widgets、Qwt 6.2`cmake/qwt.cmake`,目标名 `qwt`,头在 `external/qwt-src/src`、VTK 9.6仅算法层、GoogleTest/CTest、vcpkg仅非 Qt 依赖)。
**依据文档:**
- `docs/superpowers/specs/2026-06-11-dataset-detail-view-design.md`§2.4 后续 dd 类型、§5.3 策略框架、§3 原 web 分析方法)。
- `docs/superpowers/HANDOFF-dataset-detail-chart.md`(技术栈/构建/已完成范围)。
- `docs/apis/business_OpenAPI.json`(各 dd 类型取数接口,下文已核对路径/参数)。
**构建/测试命令(本机工具链,固定):**
- 构建:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-build.ps1`
- ⚠️ 若 `LNK1104 无法打开 geopro_desktop.exe`:先 `Get-Process geopro_desktop | Stop-Process -Force` 再构建。
- 测试:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-test.ps1`(先 dev-build 再 dev-test。**基线 89/89 绿。**
- 单测过滤:`build/release/tests/geopro_tests.exe --gtest_filter=<Suite>.*`
- 运行(视觉验收):`build/release/src/app/geopro_desktop.exe`(需登录)。
---
## 0. 现状核实(已读真实代码,勿臆测)
**关键结论:策略框架已存在但未接入控制器。** 详情:
- `src/app/panels/chart/IDatasetChartStrategy.hpp``IDatasetChartStrategy`(只有纯虚 `std::string ddCode()`+ `ChartStrategyRegistry``add/find/supports``std::map<string, unique_ptr>`)。**已写好,但全仓只有单测引用它,控制器/main.cpp 从未构造/使用注册表。**
- `src/app/panels/chart/ErtInversionStrategy.hpp`:仅一个 stub —— `ddCode()` 返回 `"dd_inversion_data"`**无任何 load/render 逻辑**。
- `src/controller/DatasetDetailController.cpp`
- `openDataset(dsId, ddCode)``if (ddCode != "dd_inversion_data") { emit loadFailed("暂不支持该数据类型的预览"); return; }` —— **硬编码**,未走注册表。
- `loadGridData(dsId, ddCode)`:同样 `if (ddCode != "dd_inversion_data") return;` 硬编码。
- `tests/app/test_chart_strategy_registry.cpp`:仅测 `add/find/supports/降级`**未与控制器联动**。
- `src/data/repo/IAsyncDatasetRepository.hpp``loadChartAsync`scatter+色阶type1/ `loadGridAsync`rows+色阶type2+异常),均**写死 ERT 反演接口**(见 `ApiDatasetRepository.cpp`)。
- ddCode 来源:`src/data/dto/NavDto.cpp:124` 从数据集列表项 `o["ddCode"]` 解析;`main.cpp:501` 双击时从 `kDsDdCodeRole` 取出传给 `openDataset`。**ddCode 是 API 给的字符串,源码里除 `dd_inversion_data` 外无其它 dd code 常量**grep 到的 `dd_custom_command` 等是 CMake `add_custom_command` 等构建符号,非数据类型)。
**结论:本 plan 的 Phase 1 必须先「打通控制器→注册表分派」,这是所有后续 dd 类型的前置。** 现有 `IDatasetChartStrategy` 接口过窄(只有 ddCode需扩展为能驱动「加载 + 渲染」。
---
## 1. 各 dd 类型:样本可得性 + 渲染归类 + 可推进/BLOCKED 矩阵
> 现实约束spec §2.4):当前可访问租户**仅 ERT / TEM / GPR 三类****GPR 对象无数据、无测井数据样本**。多数 dd 类型无活样本可参照。
> ddCode 列为推断(源码无常量),**Phase 0 须用 Playwright 抓数据集列表项的真实 `ddCode` 字段核对后回填本表**,禁止写死未经核对的 ddCode。
| 数据类型(菜单/spec | 推断 ddCode待 Phase 0 核对) | 取数接口(已核对 OpenAPI 路径/参数) | 渲染归类 / 复用 | 样本可得性 | 状态 |
|---|---|---|---|---|---|
| ERT 反演(已完成基线) | `dd_inversion_data` | `getErtRawDataScatterGraph/{id}`、`inversion/rows/{id}`、色阶 type1/2、`queryException/{id}` | 散点 + 等值面(已实现) | 有 | ✅ 已完成 |
| ERT 测量原始 | 待核对 | `GET dd/ert/measurement/scatter/graph?dsObjectId&vFieldCode`、`GET dd/ert/measurement/rows?dsObjectId` | 散点类 → 复用 `ScatterPlotItem`/`RawDataChartView`rows 形态待 Phase 0 定 | ERT 租户在,**须 Phase 0 找到有数据对象** | 候选可推进(待 Phase 0 确认有样本) |
| ERT 测量/高密度gr | 待核对 | `GET dd/ert/measurement/gr/rows?dsObjectId` | 待 Phase 0 看形态(散点/伪剖面) | 同上 | 候选可推进(待 Phase 0 确认) |
| TEM 时序(设备时序) | 待核对 | `POST dd/ert/timeSensor/rows`body `DDTimeSensorDataQueryReqVO`)、`GET dd/ert/timeSensor/page` | 时序折线类 → 新建 `LineChartView`x=时间y=数值) | **TEM 租户在**,须 Phase 0 找到有数据对象 + 抓响应 | 候选可推进(待 Phase 0 确认有样本) |
| dd_grid网格 | 待核对 | `GET dd/ert/grid/rows?dsObjectId&pageNo&pageSize` | 等值面类 → 复用 `ContourPlotItem`/`GridDataChartView` | 当前租户**无样本** | 🚫 BLOCKED待样本 |
| 轨迹 | 待核对 | `GET dd/ert/trajectory/rows?dsObjectId`、`GET dd/ert/trajectory/line?dsObjectId&frontCrsCode` | 折线/路径类 → 待样本定(可能复用散点连线或新 `LineChartView` | 当前租户**无样本** | 🚫 BLOCKED待样本 |
| 测井well logging | 待核对(菜单「测井参数表」) | **OpenAPI 未见明确专用 rows 接口** —— Phase 0 须从原版抓真实请求确认接口 | 折线类 → 新建 `LineChartView`y=深度向下 / x=数值;或 x=时间 y=数值曲线) | **无测井数据样本** | 🚫 BLOCKED待样本 + 待接口确认 |
| GPR雷达剖面图像 | 待核对 | `GET dd/gpr/channel/image/{dsObjectId}`、`GET dd/gpr/channel/trace/spectrogram`、`GET dd/gpr/channel/querySegmentation` | 图像类 → 新建 `ImageChartView`(位图剖面 + 坐标轴) | **GPR 对象无数据** | 🚫 BLOCKED待样本 |
**矩阵小结:** 唯一确定有数据的是 `dd_inversion_data`(已完成)。其余全部需 Phase 0 实地探查ERT 测量类与 TEM 是**最可能**找到样本的(租户在),但「是否有具体对象带数据」必须 Phase 0 验证后才解锁实现任务。
---
## 文件结构(新建/修改总览)
| 文件 | 动作 | 职责 |
|---|---|---|
| `src/app/panels/chart/IDatasetChartStrategy.hpp` | 改 | 扩展接口:除 `ddCode()` 外,加描述「加载/渲染契约」的虚方法(详见 Task 1.1 |
| `src/app/panels/chart/ErtInversionStrategy.{hpp,cpp}` | 改 | 把 stub 实化为「ERT 反演策略」:声明它需要 chart+grid 加载、用散点/等值面视图 |
| `src/controller/DatasetDetailController.{hpp,cpp}` | 改 | 持 `ChartStrategyRegistry&``openDataset`/`loadGridData` 改为走注册表分派,未注册→`loadFailed("暂不支持…")` |
| `src/app/main.cpp` | 改 | 构造 `ChartStrategyRegistry`,注册 `ErtInversionStrategy`,注入控制器 |
| `tests/controller/test_dataset_detail_controller.cpp` | 改 | 加「未注册 ddCode 优雅降级 / 已注册走加载」用例 |
| `tests/app/test_chart_strategy_registry.cpp` | 改 | 已有降级用例;按接口扩展补充 |
| `docs/superpowers/sample-probe-other-dd-types.md` | 建Phase 0 产出) | 各类 dd 的「样本可得性矩阵 + 渲染规格 + 真实 API 响应夹具索引」 |
| `tests/fixtures/dd/*.json` | 建Phase 0 产出) | 抓到的真实响应,做 DTO 单测夹具(仅有样本的类型) |
| `src/data/dto/DatasetChartDto.{hpp,cpp}` | 改/拆 | 新增各类 dd 的 parse 函数(如 `parseMeasurementScatter`、`parseTimeSensorSeries`)。若文件超 ~400 行则按类型拆 `MeasurementDto.*` / `TimeSensorDto.*` |
| `src/data/api/ApiDatasetRepository.{hpp,cpp}` | 改 | 新增对应 `load*Async`(仅有样本类型)|
| `src/data/api/DatasetLoads.hpp` | 改 | 新增 `*Parts` 结构(仅有样本类型)|
| `src/data/api/DatasetLoadHandles.hpp` + `.cpp` | 改 | 视形态新增句柄类型(如 `LineLoad`/`ImageLoad`),或复用现有 |
| `src/app/panels/chart/LineChartView.{hpp,cpp}` | 建(仅当 TEM/测井解锁) | 折线视图QwtPlot + QwtPlotCurve |
| `src/app/panels/chart/ImageChartView.{hpp,cpp}` | 建(仅当 GPR 解锁) | 图像剖面视图 |
| `src/app/CMakeLists.txt` / `tests/CMakeLists.txt` | 改 | 注册新文件/测试 |
---
## Phase 0样本探查必做先于一切实现
> 目的:把矩阵里的「待核对/BLOCKED」逐一坐实。**不写任何渲染代码。**产出是「样本规格 + 真实响应夹具」,作为后续 TDD 的 RED 依据。
- [ ] **Task 0.1(核对 ddCode 真值)** 用 Playwright 登录原版 `http://tenant.geomative.cn`,进入 `#/projectSpace/datasetMange/datasetInfo`,遍历可见数据集,抓每个数据集列表项的 `ddCode` 字段(网络响应或前端状态)。把真实 ddCode 回填到本 plan §1 矩阵。→ verify矩阵「推断 ddCode」列全部替换为实测值无残留「待核对」。
- [ ] **Task 0.2(逐类样本探查)** 对 ERT 测量原始 / ERT 测量gr / TEM / dd_grid / 轨迹 / 测井 / GPR 七类,逐一在原版找「有数据的对象」:
- 打开其详情页,截图渲染形态(散点?等值面?折线?图像?);
- 在 Network 抓其取数请求的**完整 URL含 query/path 参数)+ 完整响应 JSON**
- 记录坐标轴语义x/y 单位、方向、是否等比)、色阶/图例有无、是否有异常叠加。
- → verify每类得出「有样本 / 无样本」结论,有样本的把响应存到 `tests/fixtures/dd/<type>.json`,写进 `docs/superpowers/sample-probe-other-dd-types.md`
- [ ] **Task 0.3(产出规格文档)**`docs/superpowers/sample-probe-other-dd-types.md`:每类一节,含「样本可得性 / 真实接口 / 响应结构 / 渲染规格 / 渲染归类 / 是否解锁」。无样本类明确标 **BLOCKED待样本**,列「解锁前置条件」(如「需租户导入 GPR 数据 / 需测井样本数据集」)。→ verify文档七类齐全与 §1 矩阵一致。
- [ ] **Task 0.4(提交)** `docs: dd 类型样本探查矩阵 + 真实响应夹具`。提交规格文档与 fixtures。
**Phase 0 决策门:** 完成后回到本 plan把 §1 矩阵中实测有样本的类型从「候选」升为 Phase 2+ 的实现任务;无样本的保持 BLOCKED不进入实现 Phase。
---
## Phase 1打通策略分派骨架前置无须样本即可做
> 把控制器从硬编码 `dd_inversion_data` 改为走 `ChartStrategyRegistry`。**行为对 `dd_inversion_data` 必须零回归(仍正常出图),对未注册类型仍降级「暂不支持」。** 这是所有后续 dd 类型的地基。
- [ ] **Task 1.1(扩展策略接口)** 先读 `IDatasetChartStrategy.hpp`、`DatasetDetailController.{hpp,cpp}` 真实签名。把 `IDatasetChartStrategy` 从「只有 `ddCode()`」扩展为能表达「该类型支持哪些加载阶段」的最小契约,**避免过度设计YAGNI**。建议最小形:
- 保留 `std::string ddCode() const`
- 加 `bool hasGridPhase() const`ERT 反演=true纯散点/折线/图像类=false—— 让控制器据此决定是否允许 `loadGridData`,替代当前对 `loadGridData` 的硬编码 ddCode 判断。
- (渲染分派暂不进接口:现阶段 `chartReady`/`gridReady` 信号 + 现有 `DatasetDetailPage` 已驱动渲染;待新 View 真要接入时再在对应 Phase 扩展,不在 Phase 1 预先抽象。)
- 先改 `tests/app/test_chart_strategy_registry.cpp`:加「策略报告 hasGridPhase」的断言RED→ 改接口 + `ErtInversionStrategy`GREEN
- → verify`dev-test` `ChartStrategyRegistry.*` 绿。
- [ ] **Task 1.2(控制器走注册表 — openDataset**`DatasetDetailController`:构造函数加 `app::ChartStrategyRegistry& registry`(与现有 `IAsyncDatasetRepository&` 并列)。`openDataset`:把 `if (ddCode != "dd_inversion_data")` 改为 `if (!registry.supports(ddCode)) { emit loadFailed(dsId, "暂不支持该数据类型的预览"); return; }`,其余加载逻辑暂不变(仍走 `loadChartAsync`)。
- 先改 `tests/controller/test_dataset_detail_controller.cpp`:用「空注册表 → openDataset 任意 ddCode → 收到 loadFailed」+「注册了 dd_inversion_data → 走加载mock repo 的 loadChartAsync 被调用」两个用例RED。读现有该测试看 mock repo/句柄如何桩(`tests/data/test_dataset_load_handles.cpp` 有句柄桩可参考)。
- → verify`DatasetDetailController.*` 测试绿。
- [ ] **Task 1.3(控制器走注册表 — loadGridData** `loadGridData`:把 `if (ddCode != "dd_inversion_data") return;` 改为「查策略,`!strategy || !strategy->hasGridPhase()` → return」。补单测注册一个 `hasGridPhase()==false` 的 fake 策略 → `loadGridData` 不触发 `loadGridAsync`。→ verify测试绿。
- [ ] **Task 1.4main.cpp 接线)**`main.cpp` 构造 `geopro::app::ChartStrategyRegistry registry;``registry.add(std::make_unique<ErtInversionStrategy>());`,把 `registry` 注入 `DatasetDetailController detailCtrl(datasetRepo, registry);`。注意生命周期registry 须比 detailCtrl 活得久同作用域、registry 在前声明)。
- → verify`dev-build` 通过;启动 app双击 ERT 反演 ds **零回归**(原数据散点 + 网格等值面 + 异常均正常出图);双击其它类型仍显示「暂不支持」。
- [ ] **Task 1.5(提交)** `refactor(detail): 控制器按 ddCode 走 ChartStrategyRegistry 分派, 未注册优雅降级 (替代硬编码 dd_inversion_data)`
---
## Phase 2ERT 测量类(散点形态,仅当 Task 0.2 确认有样本才进行)
> 🔓 解锁条件Phase 0 确认 ERT 测量(`measurement/scatter/graph` 或 `measurement/rows`)有活样本且抓到真实响应。**未解锁则本 Phase 全部 BLOCKED跳过。**
> 渲染归类:散点类 → **复用** `ScatterPlotItem`/`RawDataChartView`,不新建 ViewDRY
> 接口(已核对):`GET /business/dd/ert/measurement/scatter/graph?dsObjectId=&vFieldCode=`、`GET /business/dd/ert/measurement/rows?dsObjectId=`。注意:**dsObjectId 是 query 参数(≠ 反演的 path 参数)**,且 scatter 需 `vFieldCode`Phase 0 抓其真实取值)。
- [ ] **Task 2.1DTO 解析 + 单测)**`tests/fixtures/dd/ert-measurement-scatter.json` 真实响应做夹具,先写 `tests/data/test_dataset_chart_dto.cpp` 新用例断言字段映射RED→ 在 `DatasetChartDto.cpp`(或新拆 `MeasurementDto.cpp`,若主文件超 ~400 行)实现 `parseMeasurementScatter(QJsonObject)`GREEN。映射须严格按真实响应字段**不臆造字段名**。→ verifyDTO 测试绿。
- [ ] **Task 2.2(色阶处置)** Phase 0 确认测量散点是否有独立色阶接口/type反演散点用 `colorGradation/getDetail` type1。若有按真实 type 拉若无色阶纯散点ColorBar 隐藏。补单测覆盖色阶解析或缺省。→ verify测试绿。
- [ ] **Task 2.3(异步句柄 + 仓储)** 复用 `ChartLoad`/`ChartParts`(结构相同则直接复用;字段不同则在 `DatasetLoads.hpp``MeasurementParts` + 句柄)。在 `ApiDatasetRepository``loadMeasurementChartAsync(dsId)``ApiBatch` 组 scatter[+色阶] 请求 + 注入解析)。注意 query 参数编码(参考现有 `enc()`)。补 `tests/data/test_dataset_load_handles.cpp` 用例。→ verify测试绿。
- [ ] **Task 2.4(策略 + 控制器分派)** 新建 `MeasurementStrategy``ddCode()` 用 Task 0.1 实测值,`hasGridPhase()` 按 rows 是否等值面而定)。`DatasetDetailController::openDataset` 据策略选择调 `loadMeasurementChartAsync`(若加载形态与反演不同,控制器按 ddCode/策略分支;保持函数 <50 必要时抽私有 helper)。`main.cpp` 注册该策略补控制器单测。→ verify测试绿
- [ ] **Task 2.5(接入视图 + 视觉验收)** `DatasetDetailPage`/`RawDataChartView` 接收 `chartReady` 渲染散点。启动 app 双击测量类 ds**对照 Phase 0 截图逐项验收**(点形/色阶/轴/等比),截图发用户确认。→ verify视觉等价。
- [ ] **Task 2.6rows 形态)** 若 Phase 0 显示 `measurement/rows` 是另一种展示(如伪剖面/列表),据规格归类(等值面→复用 `ContourPlotItem`;表格→另议),重复 2.12.5 节奏。**形态不明则 BLOCKED 待 Phase 0 规格。**
- [ ] **Task 2.7(提交)** 每 12 个 Task 一次原子提交,保持构建绿。`feat(detail): ERT 测量散点详情图 (DTO+仓储+策略+视图)`。
---
## Phase 3TEM / 设备时序(折线形态,仅当 Task 0.2 确认有样本才进行)
> 🔓 解锁条件Phase 0 确认 TEM/timeSensor 有活样本且抓到响应。**未解锁 BLOCKED跳过。**
> 渲染归类:时序折线类 → **新建** `LineChartView`QwtPlot + `QwtPlotCurve`x=时间y=数值)。
> 接口(已核对):`POST /business/dd/ert/timeSensor/rows`body `DDTimeSensorDataQueryReqVO`Phase 0 抓真实 body 字段)、`GET dd/ert/timeSensor/page`。
- [ ] **Task 3.1DTO 解析 + 单测)** 用真实响应夹具 `tests/fixtures/dd/tem-timesensor.json`先写测试RED→ 实现 `parseTimeSensorSeries`(→ 时间数组 + 多通道数值序列模型;模型若无现成 core 类型,加最小 `core::TimeSeries`YAGNI。→ verify测试绿。
- [ ] **Task 3.2(异步句柄 + 仓储)** 因是 POST + 不同返回,加 `SeriesLoad`/`SeriesParts` 句柄(参照 `ApiChartLoad`/`ApiGridLoad` 写 `ApiSeriesLoad`+ `loadTimeSensorAsync(dsId)``postJsonAsync` 组 body。补句柄桩单测。→ verify测试绿。
- [ ] **Task 3.3LineChartView + 组件测)** 新建 `src/app/panels/chart/LineChartView.{hpp,cpp}`QwtPlot + 每通道一条 `QwtPlotCurve` + 图例 + 平移/缩放(复用 `LivePanner` 模式)。组件测:给定 series → 断言曲线条数/点数。→ verify测试绿。
- [ ] **Task 3.4(策略 + 控制器 + 页面接入)** `TimeSensorStrategy`(实测 ddCode`hasGridPhase()==false`)。控制器据策略走 `loadTimeSensorAsync` → 新增 `seriesReady` 信号 → `DatasetDetailPage``LineChartView` 渲染(页内页签按类型选 View。main.cpp 注册。补控制器单测。→ verify测试绿 + 启动 app 双击 TEM ds对照 Phase 0 截图验收。
- [ ] **Task 3.5(提交)** `feat(detail): TEM 时序折线详情图 (LineChartView+DTO+仓储+策略)`
---
## Phase 4BLOCKED待样本—— dd_grid / 轨迹 / 测井 / GPR
> 以下类型当前租户**无活样本**spec §2.4 + Phase 0 复核)。**绝不规划凭想象的渲染任务**(违反 1:1 复刻原则)。本 Phase 只列「解锁前置条件 + 调研占位」,待样本到位后各自展开为类似 Phase 2/3 的 TDD 任务序列。
- [ ] **Task 4.1dd_grid 网格)** 🚫 BLOCKED。
- 解锁前置:租户内出现带数据的 dd_grid 对象Phase 0 抓 `GET dd/ert/grid/rows?dsObjectId&pageNo&pageSize` 真实响应 + 详情页截图。
- 预归类:等值面类 → 复用 `ContourPlotItem`/`GridDataChartView`(与反演 rows 同构则可大量复用 `parseInversionGrid` 思路;**须实测响应字段是否一致再决定复用/新 parse**)。
- 注意grid/rows 带分页参数,须确认是否需多页拼接。
- [ ] **Task 4.2(轨迹 trajectory** 🚫 BLOCKED。
- 解锁前置:带数据的轨迹对象,抓 `GET dd/ert/trajectory/rows?dsObjectId` + `GET dd/ert/trajectory/line?dsObjectId&frontCrsCode` 响应 + 截图。
- 预归类:路径/折线类 → 待样本定(散点连线 or `LineChartView`)。`frontCrsCode` 取值须 Phase 0 抓。
- [ ] **Task 4.3(测井 well logging** 🚫 BLOCKED双重阻塞无样本 + 接口未确认)。
- 解锁前置:(a) 拿到测井数据样本;(b) **OpenAPI 未见明确测井 rows 接口** → Phase 0 须在原版「测井参数表」相关页面抓真实请求确认接口。
- 预归类:折线类 → 复用 `LineChartView`y=深度向下 / x=数值;或 x=时间 y=数值曲线,按菜单「测井参数表」两种形态,实测后定)。
- [ ] **Task 4.4GPR 雷达剖面)** 🚫 BLOCKED。
- 解锁前置GPR 对象有数据spec 明确当前 GPR 对象无数据),抓 `GET dd/gpr/channel/image/{dsObjectId}` 响应(确认返回是图片 URL / base64 / 像素数组)+ 详情页截图。
- 预归类:图像类 → 新建 `ImageChartView`(位图剖面 + 坐标轴叠加)。返回形态决定加载方式(图片下载 vs JSON 像素)。
- 参考相关接口:`dd/gpr/channel/trace/spectrogram`、`dd/gpr/channel/querySegmentation`、`radar/trajectory/ds/{dsObjectId}`。
---
## 任务顺序与可构建性
1. **Phase 0 先行**无样本不写代码。Phase 0 是纯调研 + 夹具,零代码风险。
2. **Phase 1 是地基**:策略分派打通后,每个新 dd 类型才能「注册即生效」。Phase 1 必须保证 ERT 反演零回归(已有 89/89 测试 + 视觉验收兜底)。
3. **接口/控制器改形原子落地**`DatasetDetailController` 构造函数签名变更(加 registry+ `main.cpp` 接线**须在同一提交**完成(否则构建红)。参照详情图 v2 「Task5+6 合并」教训:跨文件签名变更不拆提交。
4. **Phase 2/3 仅在 Phase 0 解锁后展开**Phase 4 永远 BLOCKED 直到样本到位。
5. **每 12 个 bite-sized Task 一次提交**,每次提交前 `dev-test` 必须绿。
## 范围边界
- 本 plan 仅「详情图渲染扩展」。**不含**工具条编辑功能(白化/滤波/色阶配置/异常框注/自动标注/网格化/另存为/导出/描述富文本/大视图全屏)—— 另案spec §2.3)。
- 不改中央 2D/3D VTK 地图视图。
- 不改 ApiClient 异步机制(已单独立项,详情详情链路已异步)。
---
## Self-Review写完计划的自查结论
- **核实优先**:所有接口路径/参数query vs path、POST body schema均已对 `docs/apis/business_OpenAPI.json` 核对(见 §1 表未臆造。ddCode 除 `dd_inversion_data` 外源码无常量,已明确标注「待 Phase 0 实测核对」而非编造。
- **关键现状坐实**:已读 `DatasetDetailController.cpp` 确认控制器硬编码 `dd_inversion_data`、未用注册表;`ErtInversionStrategy` 是空 stub。Phase 1「打通分派」据此设计是真实缺口而非假想。
- **诚实对待样本约束**:唯一确定有样本的是已完成的反演;其余全部置于 Phase 0 探查门之后。无样本类dd_grid/轨迹/测井/GPR一律 BLOCKED + 解锁前置条件,**未规划任何凭想象的渲染任务**,符合 1:1 复刻铁律。
- **DRY/复用**:散点复用 `ScatterPlotItem`、等值面复用 `ContourPlotItem`、异步复用 `ApiBatch`/句柄模式仅折线TEM/测井与图像GPR两种新形态新建 View且各自有样本/解锁条件兜底。
- **可构建性**:标注了「构造函数签名变更 + main.cpp 接线须同提交」的原子落地约束,避免中间态构建红。
- **TDD 无占位符**:每个可推进 Task 给出确切文件路径、RED→GREEN 顺序、verify 命令BLOCKED Task 给出明确解锁前置条件而非空泛描述。
- **风险点**(1) Phase 0 可能发现 ERT 测量/TEM 也无具体带数据对象 → 则 Phase 2/3 同样 BLOCKEDplan 仍成立Phase 1 独立有价值)。(2) `IDatasetChartStrategy` 接口扩展刻意保守(只加 `hasGridPhase()`),避免为未解锁类型过度抽象;待新 View 真接入时再演进。