Skip to content

Commit cb4f0b5

Browse files
committed
Add readiness confidence indicators
1 parent 7396a6e commit cb4f0b5

8 files changed

Lines changed: 678 additions & 2 deletions

File tree

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Issue #157 - Confidence and Uncertainty Indicators</title>
7+
<style>
8+
:root {
9+
color-scheme: light;
10+
--bg: #f7f8fb;
11+
--panel: #ffffff;
12+
--text: #20242c;
13+
--muted: #687180;
14+
--border: #d9dee7;
15+
--accent: #2f6fed;
16+
--ok: #16764f;
17+
--warn: #9a5b00;
18+
--bad: #a73434;
19+
--code: #f0f3f8;
20+
}
21+
* { box-sizing: border-box; }
22+
body {
23+
margin: 0;
24+
font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
25+
color: var(--text);
26+
background: var(--bg);
27+
}
28+
main {
29+
max-width: 980px;
30+
margin: 0 auto;
31+
padding: 32px 18px 48px;
32+
}
33+
header, section {
34+
background: var(--panel);
35+
border: 1px solid var(--border);
36+
border-radius: 8px;
37+
padding: 20px;
38+
margin: 0 0 16px;
39+
}
40+
h1, h2 {
41+
line-height: 1.2;
42+
margin: 0 0 12px;
43+
}
44+
h1 { font-size: 28px; }
45+
h2 { font-size: 19px; }
46+
p { margin: 0 0 10px; }
47+
ul, ol { margin: 8px 0 0 22px; padding: 0; }
48+
li { margin: 6px 0; }
49+
code {
50+
background: var(--code);
51+
border-radius: 4px;
52+
padding: 1px 5px;
53+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
54+
font-size: 0.92em;
55+
}
56+
table {
57+
width: 100%;
58+
border-collapse: collapse;
59+
margin-top: 8px;
60+
overflow-wrap: anywhere;
61+
}
62+
th, td {
63+
border: 1px solid var(--border);
64+
padding: 8px 10px;
65+
text-align: left;
66+
vertical-align: top;
67+
}
68+
th { background: #f3f5f9; }
69+
.meta {
70+
display: flex;
71+
flex-wrap: wrap;
72+
gap: 8px;
73+
margin-top: 14px;
74+
}
75+
.pill {
76+
border: 1px solid var(--border);
77+
border-radius: 999px;
78+
padding: 3px 9px;
79+
color: var(--muted);
80+
background: #fbfcfe;
81+
font-size: 13px;
82+
}
83+
.callout {
84+
border-left: 4px solid var(--accent);
85+
background: #eef4ff;
86+
padding: 10px 12px;
87+
margin-top: 10px;
88+
border-radius: 4px;
89+
}
90+
.approval {
91+
border-color: #b9c8ee;
92+
background: #f2f6ff;
93+
}
94+
</style>
95+
</head>
96+
<body>
97+
<main>
98+
<header>
99+
<h1>Issue #157: Confidence and Uncertainty Indicators</h1>
100+
<p>
101+
Plan to add dashboard UI for readiness freshness, coverage, and confidence
102+
using the server-owned <code>readiness_serving</code> contract delivered by
103+
issue #166 / PR #177.
104+
</p>
105+
<div class="meta">
106+
<span class="pill">Date: 2026-06-26</span>
107+
<span class="pill">Status: implemented and verified</span>
108+
<span class="pill">Scope: dashboard UI + tests</span>
109+
<span class="pill">No production code changed by this plan</span>
110+
</div>
111+
</header>
112+
113+
<section>
114+
<h2>Task Summary</h2>
115+
<p>
116+
The dashboard should make it clear when the readiness score is fresh,
117+
still accruing data, stale, missing core inputs, low coverage, or adjusted
118+
by a safety cap. Users should not have to infer whether a quiet score means
119+
"all OK" or "not enough reliable data yet".
120+
</p>
121+
<p>
122+
The implementation must not invent presentation-only heuristics. It should
123+
render the canonical <code>ReadinessServingState</code> object already
124+
exposed by <code>/api/health-briefing</code>.
125+
</p>
126+
</section>
127+
128+
<section>
129+
<h2>Current Behavior</h2>
130+
<ul>
131+
<li><code>internal/health/types.go</code> defines <code>ReadinessServingState</code> with <code>status</code>, <code>confidence</code>, <code>reason</code>, and component summaries.</li>
132+
<li><code>internal/health/readiness.go</code> maps evidence into <code>fresh</code>, <code>missing</code>, <code>stale</code>, <code>data_accruing</code>, <code>low_coverage</code>, and <code>capped</code>.</li>
133+
<li><code>internal/health/readiness_evidence_test.go</code> already pins the backend serving contract, including data-accruing, low-coverage, missing, and capped cases.</li>
134+
<li><code>internal/ui/handler.go</code> passes only legacy top-level fields to the dashboard: <code>ReadinessConfidence</code>, <code>ReadinessCapReason</code>, and <code>ReadinessRawScore</code>.</li>
135+
<li><code>internal/ui/templates/pages/dashboard.html</code> renders a coarse note only when confidence is not final. It does not distinguish all serving states or show a positive fresh/covered state.</li>
136+
<li>The template already has suitable UI patterns nearby: methodology badges and compact stress-flag chips.</li>
137+
</ul>
138+
</section>
139+
140+
<section>
141+
<h2>Desired Behavior</h2>
142+
<ul>
143+
<li>Show a compact readiness trust badge near the existing methodology badge in the hero score block.</li>
144+
<li>Render all server statuses in human language: fresh, missing, stale, still settling, low coverage, and adjusted.</li>
145+
<li>Show a subtle positive state when data is fresh and confidence is final, for example "Fresh data" or "Full confidence".</li>
146+
<li>Use tooltips or accessible titles to explain low-confidence reasons without leaking implementation details such as enum names.</li>
147+
<li>Dim the score area only when confidence is genuinely low or the status is missing, stale, or low coverage. Provisional/accruing and capped states should be visible but not visually treated as broken.</li>
148+
<li>Keep copy concise and non-technical across English, Russian, and Serbian localizations.</li>
149+
<li>Preserve existing score thresholds, methodology badge behavior, and API compatibility.</li>
150+
</ul>
151+
</section>
152+
153+
<section>
154+
<h2>Assumptions</h2>
155+
<ul>
156+
<li>The first implementation targets the readiness hero score only, because readiness currently has the server-owned serving/freshness contract.</li>
157+
<li>EnergyBank and metric cards should not get similar badges until they have equally explicit serving contracts.</li>
158+
<li>Existing top-level readiness fields remain available as fallback for older paths, but the dashboard should prefer <code>ReadinessServing</code>.</li>
159+
<li>No new database schema or migration is needed.</li>
160+
</ul>
161+
</section>
162+
163+
<section>
164+
<h2>Likely File Changes</h2>
165+
<table>
166+
<thead>
167+
<tr>
168+
<th>File</th>
169+
<th>Expected change</th>
170+
</tr>
171+
</thead>
172+
<tbody>
173+
<tr>
174+
<td><code>internal/ui/handler.go</code></td>
175+
<td>Pass <code>ReadinessServing</code> into dashboard template data, ideally as a small view model with label, severity class, tooltip, and note text.</td>
176+
</tr>
177+
<tr>
178+
<td><code>internal/ui/templates/pages/dashboard.html</code></td>
179+
<td>Render the readiness trust badge and replace the coarse provisional note with state-specific output.</td>
180+
</tr>
181+
<tr>
182+
<td><code>internal/ui/style.go</code></td>
183+
<td>Add compact badge styles and low-confidence dimming styles that fit the current hero layout on desktop and mobile.</td>
184+
</tr>
185+
<tr>
186+
<td><code>internal/ui/i18n_en.go</code>, <code>internal/ui/i18n_ru.go</code>, <code>internal/ui/i18n_sr.go</code></td>
187+
<td>Add labels and short tooltip strings for serving statuses, confidence states, and common reasons.</td>
188+
</tr>
189+
<tr>
190+
<td><code>internal/ui/dashboard_illness_template_test.go</code> or a new dashboard readiness template test</td>
191+
<td>Add template coverage for fresh, data-accruing, missing/stale, low-coverage, and capped displays.</td>
192+
</tr>
193+
</tbody>
194+
</table>
195+
</section>
196+
197+
<section>
198+
<h2>Implementation Steps</h2>
199+
<ol>
200+
<li>Add a dashboard readiness-serving view model in the UI layer rather than placing enum mapping directly into the template.</li>
201+
<li>Map server statuses to stable visual severities:
202+
<code>fresh</code> to positive,
203+
<code>data_accruing</code> to pending,
204+
<code>capped</code> to neutral,
205+
<code>missing</code> and <code>stale</code> to warning,
206+
<code>low_coverage</code> to low confidence.
207+
</li>
208+
<li>Build a short tooltip from the server reason and core components. Prefer plain phrases such as "HRV has too few samples today" over raw enum names.</li>
209+
<li>Render the badge next to the readiness methodology badge in the hero label row. Keep the old legacy note path only as fallback if <code>ReadinessServing</code> is nil.</li>
210+
<li>Add a short visible note under the score only for states that require attention: missing, stale, data-accruing, low-coverage, or capped.</li>
211+
<li>Add CSS for the badge, tooltip title affordance, and low-confidence dimming. Verify the hero layout does not shift or overlap.</li>
212+
<li>Add EN/RU/SR localization keys for labels, notes, and reason copy.</li>
213+
<li>Add focused template tests for the rendering matrix and keep backend readiness tests unchanged unless a gap is discovered.</li>
214+
</ol>
215+
</section>
216+
217+
<section>
218+
<h2>Test Plan</h2>
219+
<ul>
220+
<li>Run focused UI template tests for the readiness serving states.</li>
221+
<li>Run <code>go test ./internal/ui -count=1</code>.</li>
222+
<li>Run <code>go test ./internal/health -run Readiness -count=1</code> to confirm the serving contract still holds.</li>
223+
<li>Run <code>go test ./internal/ui ./internal/health -count=1</code> before committing.</li>
224+
<li>If the implementation touches shared handler behavior, run <code>go test ./... -count=1</code> as a final check.</li>
225+
</ul>
226+
</section>
227+
228+
<section>
229+
<h2>Manual QA Checklist</h2>
230+
<ul>
231+
<li>Desktop: readiness hero shows methodology and trust badges without wrapping awkwardly.</li>
232+
<li>Mobile: badge row, score, status, and note stay readable and do not overlap the narrative or EnergyBank blocks.</li>
233+
<li>Fresh/final fixture: positive badge appears subtly and no dimming is applied.</li>
234+
<li>Data-accruing fixture: the user sees that data is still settling, not that recovery is bad.</li>
235+
<li>Missing/stale fixture: the user sees that the score is limited by missing or old inputs.</li>
236+
<li>Low-coverage fixture: score area is visibly lower-confidence and tooltip explains why in plain language.</li>
237+
<li>Capped fixture: badge explains that the displayed score was adjusted, without implying missing data.</li>
238+
<li>English, Russian, and Serbian labels fit within the hero layout.</li>
239+
</ul>
240+
</section>
241+
242+
<section>
243+
<h2>Risks</h2>
244+
<ul>
245+
<li><strong>Duplicating backend logic:</strong> avoid by mapping only display labels/severity from <code>ReadinessServing.Status</code>, not recomputing freshness in UI.</li>
246+
<li><strong>Over-warning users:</strong> keep fresh/capped/accruing visually calm; reserve dimming for genuinely low-confidence states.</li>
247+
<li><strong>Hero clutter:</strong> use compact badges and the existing methodology/stress-chip visual language.</li>
248+
<li><strong>Localization overflow:</strong> test long RU/SR strings on mobile and shorten copy if necessary.</li>
249+
</ul>
250+
</section>
251+
252+
<section>
253+
<h2>Rollback Plan</h2>
254+
<ul>
255+
<li>Revert the UI view model, template, CSS, and i18n changes in one PR if the display proves confusing.</li>
256+
<li>Because the backend contract already exists and no schema changes are planned, rollback does not require data migration.</li>
257+
<li>Keep legacy <code>ReadinessConfidence</code> fallback during implementation to reduce rollback risk.</li>
258+
</ul>
259+
</section>
260+
261+
<section>
262+
<h2>Open Questions</h2>
263+
<ul>
264+
<li>Should the positive fresh/full-confidence badge always be visible, or only visible when the user hovers/taps details? Recommendation: show it subtly so silence does not have to mean "unknown".</li>
265+
<li>Should this PR include only readiness, or also start a pattern for EnergyBank? Recommendation: readiness only for #157, because EnergyBank lacks the same serving contract today.</li>
266+
<li>Should component coverage be summarized as a percentage in the first pass? The current contract exposes component presence/freshness/sample counts, not a single canonical percentage. Recommendation: show component coverage text now and add numeric percentage only if the backend defines it.</li>
267+
</ul>
268+
</section>
269+
270+
<section class="approval">
271+
<h2>Implementation Notes</h2>
272+
<ul>
273+
<li>Added a dashboard readiness-serving view model in <code>internal/ui/handler.go</code>.</li>
274+
<li>Dashboard now renders a compact readiness trust badge from <code>ReadinessServing.Status</code> and keeps the legacy confidence note only as fallback.</li>
275+
<li>Added visual classes for fresh, pending, neutral, warning, and low-confidence states.</li>
276+
<li>Added English, Russian, and Serbian copy for serving statuses, notes, tooltips, and known cap reasons.</li>
277+
<li>Added focused dashboard template tests covering fresh, data-accruing, missing, stale, low-coverage, and capped states.</li>
278+
</ul>
279+
280+
<h2>Checks Run</h2>
281+
<ul>
282+
<li><code>git diff --check</code> — passed.</li>
283+
<li><code>gofmt -w internal/ui/handler.go internal/ui/i18n_en.go internal/ui/i18n_ru.go internal/ui/i18n_sr.go internal/ui/dashboard_illness_template_test.go</code> — passed.</li>
284+
<li><code>go test ./internal/ui -count=1</code> — passed.</li>
285+
<li><code>go test ./internal/health -run Readiness -count=1</code> — passed.</li>
286+
<li><code>go test ./internal/ui ./internal/health -count=1</code> — passed.</li>
287+
<li><code>go test ./... -count=1</code> — passed after allowing module downloads and local <code>httptest</code> binds.</li>
288+
</ul>
289+
290+
<h2>Known Limitations</h2>
291+
<ul>
292+
<li>No live browser/manual responsive QA has been run yet; automated template coverage verifies the expected hero states.</li>
293+
</ul>
294+
295+
<h2>Approval Gate</h2>
296+
<p>
297+
Implementation has started on <code>codex/issue-157-confidence-indicators</code>
298+
after approval and has passed automated verification. Next step is commit,
299+
push, and pull request review.
300+
</p>
301+
</section>
302+
</main>
303+
</body>
304+
</html>

0 commit comments

Comments
 (0)