Polish QA handoff context
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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<string, string>()
|
||||
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 (
|
||||
<section className="quality-results-panel" data-testid="quality-results-panel">
|
||||
@@ -39,6 +53,25 @@ export function QualityResultsPanel({
|
||||
<strong>{latestCheck?.status ?? 'waiting'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quality-handoff-grid" aria-label="QA/QC dataset handoff context">
|
||||
<div>
|
||||
<span>Candidate layers</span>
|
||||
<strong>{candidateDatasets.length}</strong>
|
||||
<p className="quality-dataset-name">{candidateDatasets[0]?.name ?? 'No candidate layer selected yet'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>Reference layers</span>
|
||||
<strong>{referenceDatasets.length}</strong>
|
||||
<p className="quality-dataset-name">{referenceDatasets[0]?.name ?? 'No reference layer available yet'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span>Latest comparison</span>
|
||||
<strong>{latestCheck?.status ?? 'waiting'}</strong>
|
||||
<p className="quality-dataset-name">
|
||||
Candidate {latestCandidateName ?? latestCheck?.candidate_dataset_id ?? 'n/a'} / Reference {latestReferenceName ?? latestCheck?.reference_dataset_id ?? 'n/a'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="button-row">
|
||||
<button
|
||||
type="button"
|
||||
@@ -64,8 +97,12 @@ export function QualityResultsPanel({
|
||||
<div>
|
||||
<strong>{check.check_type}</strong>
|
||||
<div className="entity-meta">
|
||||
<span>candidate: {check.candidate_dataset_id ?? 'n/a'}</span>
|
||||
<span>reference: {check.reference_dataset_id}</span>
|
||||
<span className="quality-check-dataset-link">
|
||||
candidate: {check.candidate_dataset_id ? datasetNameById.get(check.candidate_dataset_id) ?? check.candidate_dataset_id : 'n/a'}
|
||||
</span>
|
||||
<span className="quality-check-dataset-link">
|
||||
reference: {datasetNameById.get(check.reference_dataset_id) ?? check.reference_dataset_id}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className={check.status === 'ok' || check.status === 'completed' ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user