Skip to content

Commit 1642c1d

Browse files
author
Matt Schwar
committed
fix(qa): ISSUE-PR3 — parseYear preserves zero and full years
1 parent 509d20d commit 1642c1d

3 files changed

Lines changed: 74 additions & 9 deletions

File tree

CURRENT_STATE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Status below reflects the current local verification pass.
6464

6565
## Known Risks
6666

67-
- The dashboard loads D3 from a CDN, so the browser smoke harness is a preflight rather than an offline-safe guarantee.
67+
- The dashboard is offline-safe and loads local manifest/CSV assets instead of a remote D3 CDN.
6868
- Some plot rows remain speculative or projection-based and should not be reworded into facts without source review.
6969
- Generated outputs need to be rebuilt after data or source edits to stay fresh.
7070
- The repo depends on the Python packages listed in `requirements.txt`.
@@ -79,7 +79,7 @@ Status below reflects the current local verification pass.
7979

8080
## Handoff Note
8181

82-
- Changed: the unified dashboard now loads only local assets and renders with inline SVG instead of a remote D3 CDN.
83-
- Verified by: `python -m pytest tests/test_dashboard.py tests/test_browser_smoke.py -q`, `python scripts/browser_smoke.py`, and browser QA after serving the repo locally.
84-
- Remaining risk: the browser smoke harness is still a preflight check; the real browser pass remains the proof for visual behavior.
82+
- Changed: the unified dashboard now loads only local assets and renders with inline SVG instead of a remote D3 CDN; the year parser now preserves zero-valued years and full-width year strings.
83+
- Verified by: `python -m pytest tests/test_dashboard.py tests/test_dashboard_year_parsing.py tests/test_browser_smoke.py -q`, `python scripts/browser_smoke.py`, and browser QA screenshots on the live Pages site.
84+
- Evidence: `.gstack/qa-reports/screenshots/dashboard-desktop.png`, `.gstack/qa-reports/screenshots/dashboard-mobile.png`, `.gstack/qa-reports/screenshots/homepage-desktop.png`.
8585
- Next feature: provenance coverage for speculative rows.

dashboard/dashboard.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@ function loadCSV(url) {
175175
}
176176

177177
function parseYear(row) {
178-
const raw = row.year || row.Year || row.Years_Ago || row.date;
179-
if (!raw) {
178+
const raw = row.year ?? row.Year ?? row.Years_Ago ?? row.date;
179+
if (raw === null || raw === undefined || raw === '') {
180180
return null;
181181
}
182-
if (row.Years_Ago) {
183-
const yearsAgo = Number.parseFloat(raw);
182+
if (row.Years_Ago !== undefined && row.Years_Ago !== null && row.Years_Ago !== '') {
183+
const yearsAgo = Number.parseFloat(String(raw).trim());
184184
return Number.isFinite(yearsAgo) ? 2026 - yearsAgo : null;
185185
}
186-
const parsed = Number.parseFloat(String(raw).slice(0, 4));
186+
const parsed = Number.parseFloat(String(raw).trim());
187187
return Number.isFinite(parsed) ? parsed : null;
188188
}
189189

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Regression tests for dashboard year parsing."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
import subprocess
7+
from pathlib import Path
8+
9+
DASHBOARD_JS = Path("dashboard/dashboard.js")
10+
11+
12+
def _extract_function(source: str, signature: str) -> str:
13+
start = source.index(signature)
14+
brace_depth = 0
15+
end = None
16+
17+
for index in range(start, len(source)):
18+
char = source[index]
19+
if char == "{":
20+
brace_depth += 1
21+
elif char == "}":
22+
brace_depth -= 1
23+
if brace_depth == 0:
24+
end = index + 1
25+
break
26+
27+
assert end is not None, f"Could not extract function for {signature}"
28+
return source[start:end]
29+
30+
31+
def test_parse_year_preserves_zero_and_full_year_values():
32+
"""Regression: ISSUE-PR3 — parseYear treated 0 as falsy and truncated long years.
33+
34+
Found by /qa on 2026-05-20
35+
Report: .gstack/qa-reports/qa-report-plots-2026-05-20.md
36+
"""
37+
38+
source = DASHBOARD_JS.read_text()
39+
parse_year = _extract_function(source, "function parseYear(row) {")
40+
41+
cases = [
42+
{"row": {"year": 0}, "expected": 0},
43+
{"row": {"Year": 12345}, "expected": 12345},
44+
{"row": {"date": "2024-01-01"}, "expected": 2024},
45+
{"row": {"Years_Ago": 2}, "expected": 2024},
46+
{"row": {"year": ""}, "expected": None},
47+
]
48+
49+
script = "\n".join(
50+
[
51+
parse_year,
52+
f"const cases = {json.dumps([case['row'] for case in cases])};",
53+
"const results = cases.map((row) => parseYear(row));",
54+
"process.stdout.write(JSON.stringify(results));",
55+
]
56+
)
57+
58+
completed = subprocess.run(
59+
["node", "-e", script],
60+
check=True,
61+
capture_output=True,
62+
text=True,
63+
)
64+
65+
assert json.loads(completed.stdout) == [case["expected"] for case in cases]

0 commit comments

Comments
 (0)