From 4ffa6d017828395b68d03a77163aaf24edb9c0b0 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 19 Jun 2026 04:39:48 +0200 Subject: [PATCH] Polish QA handoff context --- CHANGELOG.md | 8 ++++ .../test_sprint70_quality_handoff_polish.py | 48 +++++++++++++++++++ docs/CODEX_EXECUTION_LOG.md | 30 ++++++++++++ docs/TODO.md | 1 + frontend/README.md | 1 + frontend/src/App.tsx | 7 ++- .../quality/QualityResultsPanel.tsx | 43 +++++++++++++++-- frontend/src/styles/app.css | 36 ++++++++++++++ 8 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 backend/tests/test_sprint70_quality_handoff_polish.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b7e9e0..d9543532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 70 QA/QC handoff polish (2026-06-19) + +- Added candidate/reference handoff cards to the QA/QC workspace. +- Resolved persisted quality-check candidate/reference dataset IDs back to loaded dataset names where available. +- Filtered QA candidate context to non-reference vector/GeoJSON datasets while keeping persisted dataset roles unchanged. +- Added static regression coverage for QA handoff props, App wiring and responsive handoff styles. +- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed. + ## Sprint 69 Data catalog action polish (2026-06-19) - Added recommended-action hints to each dataset card so reference and candidate layers explain their QA role. diff --git a/backend/tests/test_sprint70_quality_handoff_polish.py b/backend/tests/test_sprint70_quality_handoff_polish.py new file mode 100644 index 00000000..42a861a3 --- /dev/null +++ b/backend/tests/test_sprint70_quality_handoff_polish.py @@ -0,0 +1,48 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_quality_panel_accepts_dataset_context_without_fetching() -> None: + quality_panel = ( + ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx" + ).read_text(encoding="utf-8") + + assert "DatasetCreateResponse" in quality_panel + assert "candidateDatasets" in quality_panel + assert "referenceDatasets" in quality_panel + assert "datasetNameById" in quality_panel + assert "fetch(" not in quality_panel + assert "api" not in quality_panel.lower() + + +def test_quality_panel_surfaces_candidate_reference_handoff_summary() -> None: + quality_panel = ( + ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx" + ).read_text(encoding="utf-8") + + assert "quality-handoff-grid" in quality_panel + assert "Candidate layers" in quality_panel + assert "Reference layers" in quality_panel + assert "Latest comparison" in quality_panel + assert "Candidate" in quality_panel + assert "Reference" in quality_panel + assert "quality-dataset-name" in quality_panel + + +def test_app_passes_quality_dataset_context() -> None: + app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + + assert "availableVectorDatasets.filter((item) => item.dataset_role !== 'reference')" in app + assert "candidateDatasets={candidateDatasets}" in app + assert "referenceDatasets={referenceDatasets}" in app + + +def test_quality_handoff_styles_are_responsive() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".quality-handoff-grid" in css + assert "repeat(auto-fit, minmax(11rem, 1fr))" in css + assert ".quality-dataset-name" in css + assert ".quality-check-dataset-link" in css diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index a08afc97..5a71481e 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -2634,3 +2634,33 @@ Limitations: Next recommended pass: - Continue with QA/QC workspace handoff polish, especially linking the existing candidate/reference dataset context more clearly to persisted QA results. + +## Sprint 70 QA/QC handoff polish (2026-06-19) + +Changed: +- Added candidate/reference handoff cards to the QA/QC results panel. +- Resolved persisted quality-check candidate/reference dataset IDs to loaded dataset names when available. +- Passed `candidateDatasets` and `referenceDatasets` from `App.tsx` into `QualityResultsPanel`. +- Tightened QA candidate context to non-reference vector/GeoJSON datasets while preserving persisted `dataset_role` values. +- Added responsive `.quality-handoff-grid`, `.quality-dataset-name` and `.quality-check-dataset-link` CSS. +- Added `backend/tests/test_sprint70_quality_handoff_polish.py`. +- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`. + +Tested: +- Red step: `python -m pytest backend/tests/test_sprint70_quality_handoff_polish.py -q` failed on missing dataset context props, handoff markup, App wiring and styles. +- `python -m pytest backend/tests/test_sprint70_quality_handoff_polish.py backend/tests/test_sprint39_frontend_orchestration_hooks.py backend/tests/test_sprint51_quality_export_polish.py -q` (`15 passed`) +- `python -m pytest backend/tests/test_sprint70_quality_handoff_polish.py backend/tests/test_sprint51_quality_export_polish.py backend/tests/test_sprint30_workbench_components.py backend/tests/test_sprint27_frontend_workflow_hooks.py -q` (`14 passed`) +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` +- Local browser check against `http://127.0.0.1:5175` with live API proxy: QA/QC workspace showed 1 candidate layer, 1 reference layer, latest comparison names and named dataset links inside persisted quality-check cards, with no console warnings/errors and no horizontal overflow. +- Screenshots captured under `artifacts/sprint70-quality-handoff-polish/`. +- `bash scripts/run_readiness_check.sh` (`250 passed`) + +Open: +- Run full readiness, redeploy Tower and verify the live QA/QC handoff after deployment. + +Limitations: +- This is a frontend handoff/readability pass only. It does not change QA persistence, metric calculations, API contracts, migrations, provider fetching or AI/model behavior. + +Next recommended pass: +- Continue with QA/QC result card polish, especially making metric groups easier to scan in long-lived demo projects. diff --git a/docs/TODO.md b/docs/TODO.md index 55ceb8d3..9379e827 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -341,3 +341,4 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add Map empty-state quick actions for ready vector datasets. - [x] Add Data catalog role-density polish for reference/candidate/source scanning. - [x] Add Data catalog action polish for map, metadata, export and QA affordances. +- [x] Add QA/QC handoff polish for candidate/reference context and persisted results. diff --git a/frontend/README.md b/frontend/README.md index ed1967a2..e28c199e 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -237,6 +237,7 @@ The workbench now uses a task-based shell instead of a single long panel stack. - When the Map workspace has no active result layer, it lists ready vector/GeoJSON datasets as direct quick actions so populated demo projects can jump straight from the empty state to map inspection. - The Data catalog shows a compact selected/reference/candidate/source summary and scan-friendly badges. Persisted `reference` datasets are shown as Reference, non-reference vector/GeoJSON layers are shown as QA Candidates for workbench scanning, and raster/other uploads remain Source. - Dataset cards explain the recommended next action and use compact two-line action buttons for inspect, map, export/QA and metadata refresh. Disabled actions keep a visible reason, such as `Vector/GeoJSON only`. +- The QA/QC workspace shows candidate/reference handoff cards and resolves persisted quality-check dataset IDs back to dataset names when the datasets are loaded in the current project context. ## Raster dependency visibility diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0b4a5cb8..6c7a8502 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -162,7 +162,10 @@ function App(): JSX.Element { () => availableVectorDatasets.filter((item) => item.dataset_role === 'reference'), [availableVectorDatasets], ) - const candidateDatasets = availableVectorDatasets + const candidateDatasets = useMemo( + () => availableVectorDatasets.filter((item) => item.dataset_role !== 'reference'), + [availableVectorDatasets], + ) const rasterDatasets = useMemo(() => datasets.filter((item) => item.dataset_type === 'raster'), [datasets]) const { providers, @@ -651,6 +654,8 @@ function App(): JSX.Element { selectedProjectId={selectedProjectId} qualityChecks={qualityChecks} qualityChecksError={qualityChecksError} + candidateDatasets={candidateDatasets} + referenceDatasets={referenceDatasets} onRefresh={() => loadQualityChecks()} /> diff --git a/frontend/src/components/quality/QualityResultsPanel.tsx b/frontend/src/components/quality/QualityResultsPanel.tsx index 567a6535..98b6c349 100644 --- a/frontend/src/components/quality/QualityResultsPanel.tsx +++ b/frontend/src/components/quality/QualityResultsPanel.tsx @@ -1,9 +1,12 @@ -import type { QualityCheckRead } from '../../types' +import { useMemo } from 'react' +import type { DatasetCreateResponse, QualityCheckRead } from '../../types' interface QualityResultsPanelProps { selectedProjectId: string | null qualityChecks: QualityCheckRead[] qualityChecksError: string | null + candidateDatasets: DatasetCreateResponse[] + referenceDatasets: DatasetCreateResponse[] onRefresh: () => void } @@ -11,10 +14,21 @@ export function QualityResultsPanel({ selectedProjectId, qualityChecks, qualityChecksError, + candidateDatasets, + referenceDatasets, onRefresh, }: QualityResultsPanelProps): JSX.Element { const completedChecks = qualityChecks.filter((check) => check.status === 'ok' || check.status === 'completed').length const latestCheck = qualityChecks[0] ?? null + const datasetNameById = useMemo(() => { + const lookup = new Map() + for (const dataset of [...candidateDatasets, ...referenceDatasets]) { + lookup.set(dataset.id, dataset.name) + } + return lookup + }, [candidateDatasets, referenceDatasets]) + const latestCandidateName = latestCheck?.candidate_dataset_id ? datasetNameById.get(latestCheck.candidate_dataset_id) : null + const latestReferenceName = latestCheck?.reference_dataset_id ? datasetNameById.get(latestCheck.reference_dataset_id) : null return (
@@ -39,6 +53,25 @@ export function QualityResultsPanel({ {latestCheck?.status ?? 'waiting'} +
+
+ Candidate layers + {candidateDatasets.length} +

{candidateDatasets[0]?.name ?? 'No candidate layer selected yet'}

+
+
+ Reference layers + {referenceDatasets.length} +

{referenceDatasets[0]?.name ?? 'No reference layer available yet'}

+
+
+ Latest comparison + {latestCheck?.status ?? 'waiting'} +

+ Candidate {latestCandidateName ?? latestCheck?.candidate_dataset_id ?? 'n/a'} / Reference {latestReferenceName ?? latestCheck?.reference_dataset_id ?? 'n/a'} +

+
+
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 70abc045..7d1e907d 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -1217,6 +1217,13 @@ button.entity-card { margin: 0.75rem 0; } +.quality-handoff-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); + gap: 0.65rem; + margin: 0 0 0.75rem; +} + .quality-summary-grid > div { border: 1px solid var(--line); border-radius: 8px; @@ -1224,7 +1231,16 @@ button.entity-card { background: #ffffff; } +.quality-handoff-grid > div { + min-width: 0; + border: 1px solid rgba(15, 118, 110, 0.18); + border-radius: 8px; + padding: 0.72rem; + background: #f8fbf9; +} + .quality-summary-grid span, +.quality-handoff-grid span, .quality-score-row span, .latest-export-card span, .metric-list span { @@ -1243,6 +1259,26 @@ button.entity-card { line-height: 1.2; } +.quality-handoff-grid strong { + display: block; + margin-top: 0.26rem; + color: var(--ink); + font-size: 1rem; + line-height: 1.2; +} + +.quality-dataset-name, +.quality-check-dataset-link { + overflow-wrap: anywhere; +} + +.quality-dataset-name { + margin: 0.35rem 0 0; + color: var(--muted); + font-size: 0.82rem; + line-height: 1.35; +} + .quality-score-row { display: grid; grid-template-columns: 6rem 5rem minmax(0, 1fr);