Problem
Every raki run produces a point-in-time snapshot. The first question is always "did this get better or worse?" but the report has no historical context.
Solution
Add inline sparklines and directional deltas to each metric score card in the HTML report, pulled from .raki/history.jsonl at render time.
Acceptance criteria
Implementation Plan
Task 1: Build sparkline data helper
File: src/raki/report/sparkline.py (new)
Write failing tests in tests/test_sparkline.py:
test_build_sparkline_data_returns_values — 10 history entries → list of 10 values per metric
test_build_sparkline_data_filters_by_manifest — only matching manifest entries included
test_build_sparkline_data_empty_history — returns empty dict
test_build_sparkline_data_caps_at_n — 20 entries, N=10 → last 10 only
test_build_direction_improving — delta > 2% for higher-is-better metric → "improving"
test_build_direction_lower_is_better — decrease in rework_cycles → "improving" (not declining)
test_build_direction_declining — delta < -2% → "declining"
test_build_direction_stable — delta within ±2% → "stable"
test_build_direction_insufficient_data — <3 entries → None
Implement:
@dataclass
class SparklineData:
values: list[float] # last N values for this metric
direction: str | None # "improving" / "declining" / "stable" / None
current: float | None # most recent value
def build_sparkline_data(
entries: list[HistoryEntry],
manifest_filter: str | None = None,
max_points: int = 10,
) -> dict[str, SparklineData]:
# Uses is_higher_is_better() from report/diff.py for direction logic
Task 2: Pass sparkline data to HTML template
File: src/raki/report/html_report.py
Modify write_html_report() signature to accept optional sparklines: dict[str, SparklineData] | None.
Pass to the template as sparklines context variable.
Task 3: Render sparklines in score cards
File: src/raki/report/templates/report.html.j2
Write failing tests:
test_sparkline_svg_rendered_when_data_exists — sparkline SVG present in HTML
test_sparkline_hidden_when_no_data — no sparkline elements in HTML
test_delta_badge_improving — "improving" badge rendered
test_delta_badge_hidden_below_3_points — <3 data points → no badge
Inside each score card, after the metric value, conditionally render:
{% if sparklines and name in sparklines %}
{% set spark = sparklines[name] %}
<div class="sparkline-container">
<svg class="sparkline-svg" viewBox="0 0 100 28">
{% for val in spark.values %}
<circle cx="{{ loop.index0 * (100 / (spark.values|length - 1)) }}" cy="{{ 26 - (val * 24) }}" r="{{ 2.5 if loop.last else 1.5 }}" fill="{{ 'var(--blue)' if loop.last else 'var(--text-muted)' }}" opacity="{{ 1 if loop.last else 0.4 }}"/>
{% endfor %}
</svg>
{% if spark.direction %}
<span class="delta-badge delta-{{ spark.direction }}">{{ '▲' if spark.direction == 'improving' else '▼' if spark.direction == 'declining' else '=' }} {{ spark.direction }}</span>
{% endif %}
</div>
{% endif %}
Task 4: Add sparkline CSS
Add to template <style>: sparkline-container, sparkline-svg, delta-badge classes (from mockup #281).
Task 5: Wire history loading in CLI
File: src/raki/cli.py
In the run command, after generating the report:
- Load history via
load_history() (already done for history writing)
- Call
build_sparkline_data(entries, manifest_filter=manifest_name)
- Pass to
write_html_report(report, ..., sparklines=sparklines)
In the report re-render command:
- Load history from default path
- Build sparkline data
- Pass to renderer
Task 6: CLI delta in print_summary
File: src/raki/report/cli_summary.py
Modify print_summary() to accept optional sparklines dict. For each metric, if sparkline data exists and has direction, append (▲ vs last run) to the metric line.
Task 7: Towncrier fragment
changes/281.feature: Add inline sparklines and directional delta indicators (▲ improving / ▼ declining / = stable) to HTML report score cards, pulled from evaluation history.
Verification
uv run pytest tests/test_sparkline.py -v
uv run pytest tests/test_report_html.py -v -k "sparkline or delta_badge"
uv run pytest tests/ -v -m "not slow"
Problem
Every
raki runproduces a point-in-time snapshot. The first question is always "did this get better or worse?" but the report has no historical context.Solution
Add inline sparklines and directional deltas to each metric score card in the HTML report, pulled from
.raki/history.jsonlat render time.Acceptance criteria
config_hash)higher_is_betterfromMETRIC_METADATA— for lower-is-better metrics (rework_cycles, cost), a decrease is ▲ improvingprint_summary()shows optional delta per metric:0.38 (▲ vs last run)raki reportre-render also shows sparklines from current historyImplementation Plan
Task 1: Build sparkline data helper
File:
src/raki/report/sparkline.py(new)Write failing tests in
tests/test_sparkline.py:test_build_sparkline_data_returns_values— 10 history entries → list of 10 values per metrictest_build_sparkline_data_filters_by_manifest— only matching manifest entries includedtest_build_sparkline_data_empty_history— returns empty dicttest_build_sparkline_data_caps_at_n— 20 entries, N=10 → last 10 onlytest_build_direction_improving— delta > 2% for higher-is-better metric → "improving"test_build_direction_lower_is_better— decrease in rework_cycles → "improving" (not declining)test_build_direction_declining— delta < -2% → "declining"test_build_direction_stable— delta within ±2% → "stable"test_build_direction_insufficient_data— <3 entries → NoneImplement:
Task 2: Pass sparkline data to HTML template
File:
src/raki/report/html_report.pyModify
write_html_report()signature to accept optionalsparklines: dict[str, SparklineData] | None.Pass to the template as
sparklinescontext variable.Task 3: Render sparklines in score cards
File:
src/raki/report/templates/report.html.j2Write failing tests:
test_sparkline_svg_rendered_when_data_exists— sparkline SVG present in HTMLtest_sparkline_hidden_when_no_data— no sparkline elements in HTMLtest_delta_badge_improving— "improving" badge renderedtest_delta_badge_hidden_below_3_points— <3 data points → no badgeInside each score card, after the metric value, conditionally render:
Task 4: Add sparkline CSS
Add to template
<style>: sparkline-container, sparkline-svg, delta-badge classes (from mockup #281).Task 5: Wire history loading in CLI
File:
src/raki/cli.pyIn the
runcommand, after generating the report:load_history()(already done for history writing)build_sparkline_data(entries, manifest_filter=manifest_name)write_html_report(report, ..., sparklines=sparklines)In the
reportre-render command:Task 6: CLI delta in print_summary
File:
src/raki/report/cli_summary.pyModify
print_summary()to accept optionalsparklinesdict. For each metric, if sparkline data exists and has direction, append(▲ vs last run)to the metric line.Task 7: Towncrier fragment
changes/281.feature:Add inline sparklines and directional delta indicators (▲ improving / ▼ declining / = stable) to HTML report score cards, pulled from evaluation history.Verification