Polish QA metric evidence cards
This commit is contained in:
@@ -7,6 +7,14 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## Sprint 71 QA/QC metric card polish (2026-06-19)
|
||||
|
||||
- Added core metric evidence cards for precision, recall, F1, mean IoU and false positive/negative counts.
|
||||
- Kept the raw persisted metric list available below the promoted metric evidence.
|
||||
- Added number formatting for compact metric display while preserving persisted metric values.
|
||||
- Added static regression coverage for metric promotion and responsive metric-card styles.
|
||||
- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed.
|
||||
|
||||
## Sprint 70 QA/QC handoff polish (2026-06-19)
|
||||
|
||||
- Added candidate/reference handoff cards to the QA/QC workspace.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_quality_panel_promotes_core_metrics_before_raw_metric_list() -> None:
|
||||
quality_panel = (
|
||||
ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "CORE_METRIC_ORDER" in quality_panel
|
||||
assert "precision" in quality_panel
|
||||
assert "recall" in quality_panel
|
||||
assert "f1" in quality_panel
|
||||
assert "mean_iou" in quality_panel
|
||||
assert "false_positive_count" in quality_panel
|
||||
assert "false_negative_count" in quality_panel
|
||||
assert "qualityMetricValue" in quality_panel
|
||||
assert "qualityMetricLabel" in quality_panel
|
||||
|
||||
|
||||
def test_quality_panel_renders_metric_evidence_cards_and_raw_metrics() -> None:
|
||||
quality_panel = (
|
||||
ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "quality-metric-grid" in quality_panel
|
||||
assert "quality-metric-card" in quality_panel
|
||||
assert "quality-metric-card-critical" in quality_panel
|
||||
assert "Metric evidence" in quality_panel
|
||||
assert "Raw metrics" in quality_panel
|
||||
assert "check.metrics.map" in quality_panel
|
||||
|
||||
|
||||
def test_quality_metric_styles_are_dense_and_responsive() -> None:
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
|
||||
assert ".quality-metric-grid" in css
|
||||
assert "repeat(auto-fit, minmax(7.5rem, 1fr))" in css
|
||||
assert ".quality-metric-card" in css
|
||||
assert ".quality-metric-card-critical" in css
|
||||
assert ".quality-metric-card strong" in css
|
||||
@@ -2666,3 +2666,31 @@ Limitations:
|
||||
|
||||
Next recommended pass:
|
||||
- Continue with QA/QC result card polish, especially making metric groups easier to scan in long-lived demo projects.
|
||||
|
||||
## Sprint 71 QA/QC metric card polish (2026-06-19)
|
||||
|
||||
Changed:
|
||||
- Added core metric evidence cards to QA/QC result cards for precision, recall, F1, mean IoU, false positives and false negatives.
|
||||
- Kept the raw persisted metric list visible below the promoted evidence cards.
|
||||
- Added compact metric label/value formatting in `QualityResultsPanel`.
|
||||
- Added responsive `.quality-metric-grid`, `.quality-metric-card` and `.quality-metric-card-critical` CSS.
|
||||
- Added `backend/tests/test_sprint71_quality_metric_polish.py`.
|
||||
- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||
|
||||
Tested:
|
||||
- Red step: `python -m pytest backend/tests/test_sprint71_quality_metric_polish.py -q` failed on missing metric promotion helpers, markup and styles.
|
||||
- `python -m pytest backend/tests/test_sprint71_quality_metric_polish.py backend/tests/test_sprint70_quality_handoff_polish.py backend/tests/test_sprint51_quality_export_polish.py backend/tests/test_sprint30_workbench_components.py -q` (`13 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 rendered 12 promoted metric cards across 2 persisted quality checks, retained 2 raw metric sections, no console warnings/errors and no horizontal overflow on desktop or mobile.
|
||||
- Screenshots captured under `artifacts/sprint71-quality-metric-polish/`.
|
||||
- `bash scripts/run_readiness_check.sh` (`253 passed`)
|
||||
|
||||
Open:
|
||||
- Redeploy Tower and verify live QA/QC metric cards after deployment.
|
||||
|
||||
Limitations:
|
||||
- Frontend readability pass only; no QA calculation, persistence, API, migrations, provider or AI/model changes.
|
||||
|
||||
Next recommended pass:
|
||||
- Continue with QA/QC metric/result filtering or result-card density for long-lived demo projects.
|
||||
|
||||
@@ -342,3 +342,4 @@ This file now starts with the current implementation status. Older preparation/b
|
||||
- [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.
|
||||
- [x] Add QA/QC metric card polish for precision, recall, F1, IoU and error counts.
|
||||
|
||||
@@ -283,6 +283,8 @@ When using the repository Docker Compose stack, the frontend is published on hos
|
||||
|
||||
The frontend API client uses same-origin requests by default. In Docker Compose, nginx serves the built frontend and reverse proxies `/api` and `/health` to the backend service, so browser clients on LAN hosts do not call their own `localhost:8000`.
|
||||
|
||||
The QA/QC workspace promotes core metrics into evidence cards before the raw metric list, keeping precision, recall, F1, mean IoU and false positive/negative counts scan-friendly while preserving the persisted raw metrics.
|
||||
|
||||
## Useful repository scripts
|
||||
|
||||
- `bash scripts/frontend_install.sh`
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { useMemo } from 'react'
|
||||
import type { DatasetCreateResponse, QualityCheckRead } from '../../types'
|
||||
import type { DatasetCreateResponse, MetricRead, QualityCheckRead } from '../../types'
|
||||
|
||||
const CORE_METRIC_ORDER = [
|
||||
'precision',
|
||||
'recall',
|
||||
'f1',
|
||||
'mean_iou',
|
||||
'false_positive_count',
|
||||
'false_negative_count',
|
||||
]
|
||||
|
||||
interface QualityResultsPanelProps {
|
||||
selectedProjectId: string | null
|
||||
@@ -10,6 +19,29 @@ interface QualityResultsPanelProps {
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
function qualityMetricLabel(metricKey: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
precision: 'Precision',
|
||||
recall: 'Recall',
|
||||
f1: 'F1',
|
||||
mean_iou: 'Mean IoU',
|
||||
false_positive_count: 'False positives',
|
||||
false_negative_count: 'False negatives',
|
||||
}
|
||||
return labels[metricKey] ?? metricKey.replaceAll('_', ' ')
|
||||
}
|
||||
|
||||
function qualityMetricValue(metric: MetricRead | undefined): string {
|
||||
if (!metric || metric.metric_value === null || metric.metric_value === undefined) {
|
||||
return 'n/a'
|
||||
}
|
||||
const value = Number(metric.metric_value)
|
||||
if (!Number.isFinite(value)) {
|
||||
return String(metric.metric_value)
|
||||
}
|
||||
return Number.isInteger(value) ? String(value) : value.toFixed(3)
|
||||
}
|
||||
|
||||
export function QualityResultsPanel({
|
||||
selectedProjectId,
|
||||
qualityChecks,
|
||||
@@ -123,11 +155,27 @@ export function QualityResultsPanel({
|
||||
<strong>{check.id}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="quality-metric-section">
|
||||
<span>Metric evidence</span>
|
||||
<div className="quality-metric-grid">
|
||||
{CORE_METRIC_ORDER.map((metricKey) => {
|
||||
const metric = check.metrics.find((item) => item.metric_key === metricKey)
|
||||
const isCriticalCount = metricKey === 'false_positive_count' || metricKey === 'false_negative_count'
|
||||
return (
|
||||
<div className={isCriticalCount ? 'quality-metric-card quality-metric-card-critical' : 'quality-metric-card'} key={metricKey}>
|
||||
<span>{qualityMetricLabel(metricKey)}</span>
|
||||
<strong>{qualityMetricValue(metric)}</strong>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<span className="metric-list-label">Raw metrics</span>
|
||||
<ul className="metric-list">
|
||||
{check.metrics.map((metric) => (
|
||||
<li key={metric.id}>
|
||||
<span>{metric.metric_key}</span>
|
||||
<strong>{metric.metric_value ?? 'n/a'}</strong>
|
||||
<span>{qualityMetricLabel(metric.metric_key)}</span>
|
||||
<strong>{qualityMetricValue(metric)}</strong>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -1304,6 +1304,64 @@ button.entity-card {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quality-metric-section {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.metric-list-label,
|
||||
.quality-metric-section > span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.quality-metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.quality-metric-card {
|
||||
min-width: 0;
|
||||
border: 1px solid rgba(15, 118, 110, 0.18);
|
||||
border-radius: 8px;
|
||||
padding: 0.58rem;
|
||||
background: #f8fbf9;
|
||||
}
|
||||
|
||||
.quality-metric-card-critical {
|
||||
border-color: rgba(185, 28, 28, 0.18);
|
||||
background: #fff7ed;
|
||||
}
|
||||
|
||||
.quality-metric-card span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.quality-metric-card strong {
|
||||
display: block;
|
||||
margin-top: 0.24rem;
|
||||
overflow: hidden;
|
||||
color: var(--ink);
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.15;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.metric-list-label {
|
||||
margin-top: 0.82rem;
|
||||
}
|
||||
|
||||
.metric-list {
|
||||
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
||||
margin-top: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user