feat(card): badge differentiates expected vs unexpected skip
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
The level badge gains a third segment derived from level_cap_intent:
- amber 'gap?' when the climb was capped by an UNDECLARED gap-sensitive N/A
(backup_restore / functional) — a likely-missing test (unexpected skip)
- muted 'expected' when capped by a DECLARED intentional N/A (reviewed, nothing to fix)
- nothing extra for a clean cap, a full climb, or a real failure.
Font-safe text labels (no emoji) so the SVG renders headless/anywhere. Badge
never inflates — it only annotates the cap the level already reflects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -79,10 +79,44 @@ def render_badge_svg(label: str, message: str, color: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def level_badge_svg(level: int, cap_reason: str = "") -> str:
|
# Third-segment colours for the level badge: amber = an UNEXPECTED skip (undeclared gap-sensitive
|
||||||
"""Per-recipe/-run LEVEL badge: 'cc-ci | level N'. Colour by level (R6)."""
|
# N/A — likely missing coverage) capped the climb; muted = an EXPECTED skip (declared intentional
|
||||||
msg = f"level {int(level)}"
|
# N/A — reviewed, nothing to fix). Font-safe text labels (no emoji) so the SVG renders anywhere.
|
||||||
return render_badge_svg("cc-ci", msg, level_color(level))
|
GAP_COLOR = "#d29922"
|
||||||
|
EXPECT_COLOR = "#6e7681"
|
||||||
|
|
||||||
|
|
||||||
|
def level_badge_svg(level: int, cap_reason: str = "", cap_intent: str = "") -> str:
|
||||||
|
"""Per-recipe/-run LEVEL badge: 'cc-ci | level N' coloured by level (R6), with a THIRD segment
|
||||||
|
that differentiates *why* the climb stopped when an N/A capped it:
|
||||||
|
- undeclared gap-sensitive N/A (an UNEXPECTED skip — likely missing coverage): amber 'gap?'.
|
||||||
|
- declared intentional N/A (an EXPECTED skip — reviewed, nothing to fix): muted 'expected'.
|
||||||
|
- clean cap / full climb / a real failure: no third segment (the level + card carry it).
|
||||||
|
Derived from `cap_intent` (results.level_cap_intent) so the badge never inflates — it only
|
||||||
|
annotates the cap the level already reflects."""
|
||||||
|
label, msg = "cc-ci", f"level {int(level)}"
|
||||||
|
lw, mw = _text_width(label), _text_width(msg)
|
||||||
|
third: tuple[str, str] | None = None
|
||||||
|
if cap_intent.startswith("undeclared"):
|
||||||
|
third = ("gap?", GAP_COLOR)
|
||||||
|
elif cap_intent.startswith("intentional"):
|
||||||
|
third = ("expected", EXPECT_COLOR)
|
||||||
|
if third is None:
|
||||||
|
return render_badge_svg(label, msg, level_color(level))
|
||||||
|
txt, tcolor = third
|
||||||
|
tw = _text_width(txt)
|
||||||
|
w = lw + mw + tw
|
||||||
|
return (
|
||||||
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="20" role="img" '
|
||||||
|
f'aria-label="{html.escape(label)}: {html.escape(msg)} ({html.escape(txt)})">'
|
||||||
|
f'<rect width="{lw}" height="20" fill="#555"/>'
|
||||||
|
f'<rect x="{lw}" width="{mw}" height="20" fill="{level_color(level)}"/>'
|
||||||
|
f'<rect x="{lw + mw}" width="{tw}" height="20" fill="{tcolor}"/>'
|
||||||
|
f'<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="11">'
|
||||||
|
f'<text x="6" y="14">{html.escape(label)}</text>'
|
||||||
|
f'<text x="{lw + 6}" y="14">{html.escape(msg)}</text>'
|
||||||
|
f'<text x="{lw + mw + 6}" y="14">{html.escape(txt)}</text></g></svg>'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _stage_rows(stages: list[dict]) -> str:
|
def _stage_rows(stages: list[dict]) -> str:
|
||||||
|
|||||||
@ -1296,7 +1296,13 @@ def main() -> int:
|
|||||||
f.write(card_mod.render_card_html(data, screenshot_rel=data.get("screenshot")))
|
f.write(card_mod.render_card_html(data, screenshot_rel=data.get("screenshot")))
|
||||||
png = card_mod.render_card_png(html_path, os.path.join(run_artifact_dir, "summary.png"))
|
png = card_mod.render_card_png(html_path, os.path.join(run_artifact_dir, "summary.png"))
|
||||||
with open(os.path.join(run_artifact_dir, "badge.svg"), "w", encoding="utf-8") as f:
|
with open(os.path.join(run_artifact_dir, "badge.svg"), "w", encoding="utf-8") as f:
|
||||||
f.write(card_mod.level_badge_svg(data["level"], data.get("level_cap_reason", "")))
|
f.write(
|
||||||
|
card_mod.level_badge_svg(
|
||||||
|
data["level"],
|
||||||
|
data.get("level_cap_reason", ""),
|
||||||
|
data.get("level_cap_intent", ""),
|
||||||
|
)
|
||||||
|
)
|
||||||
print(
|
print(
|
||||||
f"summary card {'rendered ' + png if png else '(PNG render unavailable)'} + "
|
f"summary card {'rendered ' + png if png else '(PNG render unavailable)'} + "
|
||||||
f"badge.svg written into {run_artifact_dir}",
|
f"badge.svg written into {run_artifact_dir}",
|
||||||
|
|||||||
@ -51,6 +51,19 @@ def test_badge_svg_wellformed():
|
|||||||
assert svg.startswith("<svg") and svg.endswith("</svg>")
|
assert svg.startswith("<svg") and svg.endswith("</svg>")
|
||||||
assert "level 4" in svg
|
assert "level 4" in svg
|
||||||
assert C.level_color(4) in svg
|
assert C.level_color(4) in svg
|
||||||
|
# plain cap (no intent) → two-box badge, no third segment
|
||||||
|
assert "expected" not in svg and "gap?" not in svg
|
||||||
|
|
||||||
|
|
||||||
|
def test_badge_svg_differentiates_expected_vs_unexpected_skip():
|
||||||
|
# declared intentional N/A capped the climb → muted "expected" third segment
|
||||||
|
exp = C.level_badge_svg(2, "L3 backup/restore N/A", "intentional · no persistent data")
|
||||||
|
assert "level 2" in exp and "expected" in exp and C.EXPECT_COLOR in exp
|
||||||
|
assert "gap?" not in exp
|
||||||
|
# undeclared gap-sensitive N/A → amber "gap?" third segment (an UNEXPECTED skip)
|
||||||
|
gap = C.level_badge_svg(2, "L3 backup/restore N/A", "undeclared N/A — possible coverage gap")
|
||||||
|
assert "level 2" in gap and "gap?" in gap and C.GAP_COLOR in gap
|
||||||
|
assert "expected" not in gap
|
||||||
|
|
||||||
|
|
||||||
def test_card_html_reports_level_verbatim():
|
def test_card_html_reports_level_verbatim():
|
||||||
|
|||||||
Reference in New Issue
Block a user