From 87c477d232bd596212ecdeeaf0db041bae9b8a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20K=C3=B6lnberger?= <159939812+ProfRandom92@users.noreply.github.com> Date: Tue, 19 May 2026 09:53:34 -0700 Subject: [PATCH] Add deterministic multi-family admissibility SVG renderer --- .../multi_family_admissibility_curves.svg | 34 +++++ package.json | 3 +- .../render_multi_family_admissibility_svg.py | 125 ++++++++++++++++++ tests/test_multi_family_svg_renderer.py | 47 +++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 artifacts/multi_family_admissibility_curves.svg create mode 100644 scripts/render_multi_family_admissibility_svg.py create mode 100644 tests/test_multi_family_svg_renderer.py diff --git a/artifacts/multi_family_admissibility_curves.svg b/artifacts/multi_family_admissibility_curves.svg new file mode 100644 index 0000000..28b70a1 --- /dev/null +++ b/artifacts/multi_family_admissibility_curves.svg @@ -0,0 +1,34 @@ + + + Multi-Family Admissibility Degradation Curves + + + + 0.000 + + 0.500 + + 1.000 + baseline + mild + moderate + severe + degradation level + overall_admissibility_score + + Families + + + + + + + coding_workflow_pr_review + + + + + + + incident_response_page_triage + diff --git a/package.json b/package.json index 9132b95..25894f4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "layout": "python scripts/check_repo_layout.py", "check": "npm run layout && npm run typecheck && npm run validate && npm run build && npm run test", "generate:layered-admissibility": "python scripts/generate_layered_admissibility_artifact.py", - "generate:multi-family-admissibility": "python scripts/generate_multi_family_admissibility_artifact.py" + "generate:multi-family-admissibility": "python scripts/generate_multi_family_admissibility_artifact.py", + "generate:multi-family-svg": "python scripts/render_multi_family_admissibility_svg.py" } } diff --git a/scripts/render_multi_family_admissibility_svg.py b/scripts/render_multi_family_admissibility_svg.py new file mode 100644 index 0000000..e7f28b1 --- /dev/null +++ b/scripts/render_multi_family_admissibility_svg.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import json +from pathlib import Path + +INPUT_PATH = Path("artifacts/multi_family_admissibility_results.json") +OUTPUT_PATH = Path("artifacts/multi_family_admissibility_curves.svg") + +WIDTH = 1000 +HEIGHT = 560 +MARGIN_LEFT = 90 +MARGIN_RIGHT = 40 +MARGIN_TOP = 70 +MARGIN_BOTTOM = 120 + +TITLE = "Multi-Family Admissibility Degradation Curves" +X_LABEL = "degradation level" +Y_LABEL = "overall_admissibility_score" +LEVELS: tuple[str, ...] = ("baseline", "mild", "moderate", "severe") +PALETTE: tuple[str, ...] = ( + "#0055aa", + "#aa5500", + "#117733", + "#882255", + "#44aa99", + "#cc6677", +) + + +def _fmt(value: float) -> str: + return f"{value:.3f}" + + +def _level_from_fixture_id(fixture_id: str) -> str: + if "_mild_" in fixture_id: + return "mild" + if "_moderate_" in fixture_id: + return "moderate" + if "_degraded_" in fixture_id: + return "severe" + return "baseline" + + +def render_svg(payload: dict) -> str: + families = sorted(payload["families"], key=lambda family: family["family"]) + + plot_width = WIDTH - MARGIN_LEFT - MARGIN_RIGHT + plot_height = HEIGHT - MARGIN_TOP - MARGIN_BOTTOM + plot_right = WIDTH - MARGIN_RIGHT + plot_bottom = HEIGHT - MARGIN_BOTTOM + + x_by_level = { + level: MARGIN_LEFT + (plot_width * idx / (len(LEVELS) - 1)) + for idx, level in enumerate(LEVELS) + } + + elements: list[str] = [ + f'', + f' ', + f' {TITLE}', + f' ', + f' ', + ] + + for tick_score in (0.0, 0.5, 1.0): + y = MARGIN_TOP + ((1.0 - tick_score) * plot_height) + elements.append( + f' ' + ) + elements.append( + f' {_fmt(tick_score)}' + ) + + for level in LEVELS: + x = x_by_level[level] + elements.append( + f' {level}' + ) + + elements.append( + f' {X_LABEL}' + ) + elements.append( + f' {Y_LABEL}' + ) + + legend_x = 690 + legend_y = 84 + legend_height = 30 + 18 * len(families) + elements.append(f' ') + elements.append(f' Families') + + for idx, family in enumerate(families): + family_name = family["family"] + color = PALETTE[idx % len(PALETTE)] + points_by_level = { + _level_from_fixture_id(point["fixture_id"]): point + for point in family["curve"]["points"] + } + + polyline = " ".join( + f"{_fmt(x_by_level[level])},{_fmt(MARGIN_TOP + ((1.0 - float(points_by_level[level]['overall_admissibility_score'])) * plot_height))}" + for level in LEVELS + ) + elements.append(f' ') + + for level in LEVELS: + score = float(points_by_level[level]["overall_admissibility_score"]) + x = x_by_level[level] + y = MARGIN_TOP + ((1.0 - score) * plot_height) + elements.append(f' ') + + ly = legend_y + 38 + idx * 18 + elements.append(f' ') + elements.append(f' {family_name}') + + elements.append("") + return "\n".join(elements) + "\n" + + +if __name__ == "__main__": + payload = json.loads(INPUT_PATH.read_text(encoding="utf-8")) + svg = render_svg(payload) + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + OUTPUT_PATH.write_text(svg, encoding="utf-8") diff --git a/tests/test_multi_family_svg_renderer.py b/tests/test_multi_family_svg_renderer.py new file mode 100644 index 0000000..d2c1f0c --- /dev/null +++ b/tests/test_multi_family_svg_renderer.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from scripts.render_multi_family_admissibility_svg import render_svg + +INPUT_PATH = Path("artifacts/multi_family_admissibility_results.json") +SVG_PATH = Path("artifacts/multi_family_admissibility_curves.svg") + + + +def _render() -> str: + payload = json.loads(INPUT_PATH.read_text(encoding="utf-8")) + return render_svg(payload) + + + +def test_multi_family_svg_render_is_deterministic() -> None: + assert _render() == _render() + + + +def test_rendered_svg_matches_committed_artifact() -> None: + assert _render() == SVG_PATH.read_text(encoding="utf-8") + + + +def test_svg_contains_current_families() -> None: + output = _render() + assert "coding_workflow_pr_review" in output + assert "incident_response_page_triage" in output + + + +def test_svg_contains_degradation_levels() -> None: + output = _render() + for level in ("baseline", "mild", "moderate", "severe"): + assert f">{level}<" in output + + + +def test_svg_has_no_nondeterministic_fields() -> None: + output = _render().lower() + banned_tokens = ("timestamp", "date", "time", "random", "uuid", "id=") + for token in banned_tokens: + assert token not in output