Update drp/report.py
Browse files- drp/report.py +70 -23
drp/report.py
CHANGED
|
@@ -1,21 +1,68 @@
|
|
| 1 |
import json
|
| 2 |
-
from typing import Any, Dict, List
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
def render_report_markdown(diff: Dict[str, Any]) -> str:
|
| 6 |
-
s = diff
|
| 7 |
counts = diff.get("class_counts", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
lines: List[str] = []
|
| 10 |
lines.append("# DRP Differential Report")
|
| 11 |
lines.append("")
|
| 12 |
-
lines.append("
|
|
|
|
|
|
|
| 13 |
lines.append(f"- Run A: `{s.get('run_a')}`")
|
| 14 |
lines.append(f"- Run B: `{s.get('run_b')}`")
|
| 15 |
lines.append(f"- Framework A/B: `{s.get('framework_a')}` / `{s.get('framework_b')}`")
|
| 16 |
lines.append(f"- Model A/B: `{s.get('model_a')}` / `{s.get('model_b')}`")
|
| 17 |
lines.append(f"- Events A/B: `{s.get('events_a')}` / `{s.get('events_b')}`")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
lines.append(f"- First divergence index: `{s.get('first_divergence_index')}`")
|
|
|
|
|
|
|
|
|
|
| 19 |
lines.append("")
|
| 20 |
|
| 21 |
lines.append("## Divergence classes")
|
|
@@ -26,24 +73,34 @@ def render_report_markdown(diff: Dict[str, Any]) -> str:
|
|
| 26 |
lines.append("- (no diffs)")
|
| 27 |
lines.append("")
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
first_item = None
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
break
|
| 38 |
|
| 39 |
if not first_item:
|
| 40 |
-
|
|
|
|
| 41 |
return "\n".join(lines)
|
| 42 |
|
| 43 |
lines.append(f"- Index: `{first_item.get('i')}`")
|
| 44 |
lines.append(f"- Class: `{first_item.get('class')}`")
|
| 45 |
lines.append(f"- Kind A/B: `{first_item.get('kind_a')}` / `{first_item.get('kind_b')}`")
|
| 46 |
lines.append(f"- Step A/B: `{first_item.get('step_a')}` / `{first_item.get('step_b')}`")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
lines.append("")
|
| 48 |
lines.append("### Key diffs (first 25)")
|
| 49 |
for d in (first_item.get("diffs") or [])[:25]:
|
|
@@ -56,14 +113,4 @@ def render_report_markdown(diff: Dict[str, Any]) -> str:
|
|
| 56 |
lines.append(first_item["text_unified_diff"][:8000])
|
| 57 |
lines.append("```")
|
| 58 |
|
| 59 |
-
return "\n".join(lines)
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
def _short(x: Any, n: int = 180) -> str:
|
| 63 |
-
try:
|
| 64 |
-
s = json.dumps(x, ensure_ascii=False)
|
| 65 |
-
except Exception:
|
| 66 |
-
s = str(x)
|
| 67 |
-
if len(s) > n:
|
| 68 |
-
return s[:n] + "…"
|
| 69 |
-
return s
|
|
|
|
| 1 |
import json
|
| 2 |
+
from typing import Any, Dict, List, Optional
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def _short(x: Any, n: int = 160) -> str:
|
| 6 |
+
try:
|
| 7 |
+
s = json.dumps(x, ensure_ascii=False)
|
| 8 |
+
except Exception:
|
| 9 |
+
s = str(x)
|
| 10 |
+
if len(s) > n:
|
| 11 |
+
return s[:n] + "…"
|
| 12 |
+
return s
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _fmt_reward(x: Optional[float]) -> str:
|
| 16 |
+
if x is None:
|
| 17 |
+
return "n/a"
|
| 18 |
+
# stable, readable
|
| 19 |
+
return f"{x:.6g}"
|
| 20 |
|
| 21 |
|
| 22 |
def render_report_markdown(diff: Dict[str, Any]) -> str:
|
| 23 |
+
s = diff.get("summary", {})
|
| 24 |
counts = diff.get("class_counts", {})
|
| 25 |
+
first = s.get("first_divergence_index")
|
| 26 |
+
|
| 27 |
+
# punchy one-liner
|
| 28 |
+
line_bits: List[str] = []
|
| 29 |
+
if first is None:
|
| 30 |
+
line_bits.append("Timelines identical")
|
| 31 |
+
else:
|
| 32 |
+
line_bits.append(f"Identical until index {first}")
|
| 33 |
+
line_bits.append(f"{s.get('diff_event_count', 0)} events differ")
|
| 34 |
+
if s.get("missing_event_count", 0):
|
| 35 |
+
line_bits.append(f"{s.get('missing_event_count')} missing")
|
| 36 |
+
if s.get("final_reward_delta") is not None:
|
| 37 |
+
line_bits.append(f"Final reward delta: {s.get('final_reward_delta'):.6g}")
|
| 38 |
+
one_liner = " · ".join(line_bits)
|
| 39 |
|
| 40 |
lines: List[str] = []
|
| 41 |
lines.append("# DRP Differential Report")
|
| 42 |
lines.append("")
|
| 43 |
+
lines.append(f"**{one_liner}**")
|
| 44 |
+
lines.append("")
|
| 45 |
+
lines.append("## Run identity")
|
| 46 |
lines.append(f"- Run A: `{s.get('run_a')}`")
|
| 47 |
lines.append(f"- Run B: `{s.get('run_b')}`")
|
| 48 |
lines.append(f"- Framework A/B: `{s.get('framework_a')}` / `{s.get('framework_b')}`")
|
| 49 |
lines.append(f"- Model A/B: `{s.get('model_a')}` / `{s.get('model_b')}`")
|
| 50 |
lines.append(f"- Events A/B: `{s.get('events_a')}` / `{s.get('events_b')}`")
|
| 51 |
+
lines.append("")
|
| 52 |
+
|
| 53 |
+
if s.get("run_link_a") or s.get("run_link_b"):
|
| 54 |
+
lines.append("## Viewer links (if provided)")
|
| 55 |
+
if s.get("run_link_a"):
|
| 56 |
+
lines.append(f"- Run A viewer: {s.get('run_link_a')}")
|
| 57 |
+
if s.get("run_link_b"):
|
| 58 |
+
lines.append(f"- Run B viewer: {s.get('run_link_b')}")
|
| 59 |
+
lines.append("")
|
| 60 |
+
|
| 61 |
+
lines.append("## Summary stats")
|
| 62 |
lines.append(f"- First divergence index: `{s.get('first_divergence_index')}`")
|
| 63 |
+
lines.append(f"- Diff events: `{s.get('diff_event_count')}`")
|
| 64 |
+
lines.append(f"- Missing events: `{s.get('missing_event_count')}`")
|
| 65 |
+
lines.append(f"- Final reward A/B/Δ: `{_fmt_reward(s.get('final_reward_a'))}` / `{_fmt_reward(s.get('final_reward_b'))}` / `{_fmt_reward(s.get('final_reward_delta'))}`")
|
| 66 |
lines.append("")
|
| 67 |
|
| 68 |
lines.append("## Divergence classes")
|
|
|
|
| 73 |
lines.append("- (no diffs)")
|
| 74 |
lines.append("")
|
| 75 |
|
| 76 |
+
# First divergence detail
|
| 77 |
+
lines.append("## First divergence detail")
|
| 78 |
+
if first is None:
|
| 79 |
+
lines.append("- No divergence found.")
|
| 80 |
+
return "\n".join(lines)
|
| 81 |
+
|
| 82 |
first_item = None
|
| 83 |
+
for item in diff.get("differences", []):
|
| 84 |
+
if item.get("i") == first:
|
| 85 |
+
first_item = item
|
| 86 |
+
break
|
|
|
|
| 87 |
|
| 88 |
if not first_item:
|
| 89 |
+
# likely a length mismatch only
|
| 90 |
+
lines.append("- Divergence is a length mismatch or occurs where one side is missing.")
|
| 91 |
return "\n".join(lines)
|
| 92 |
|
| 93 |
lines.append(f"- Index: `{first_item.get('i')}`")
|
| 94 |
lines.append(f"- Class: `{first_item.get('class')}`")
|
| 95 |
lines.append(f"- Kind A/B: `{first_item.get('kind_a')}` / `{first_item.get('kind_b')}`")
|
| 96 |
lines.append(f"- Step A/B: `{first_item.get('step_a')}` / `{first_item.get('step_b')}`")
|
| 97 |
+
if first_item.get("link_a") or first_item.get("link_b"):
|
| 98 |
+
lines.append("")
|
| 99 |
+
lines.append("### Jump to replay (if provided)")
|
| 100 |
+
if first_item.get("link_a"):
|
| 101 |
+
lines.append(f"- Open A @ i={first}: {first_item.get('link_a')}")
|
| 102 |
+
if first_item.get("link_b"):
|
| 103 |
+
lines.append(f"- Open B @ i={first}: {first_item.get('link_b')}")
|
| 104 |
lines.append("")
|
| 105 |
lines.append("### Key diffs (first 25)")
|
| 106 |
for d in (first_item.get("diffs") or [])[:25]:
|
|
|
|
| 113 |
lines.append(first_item["text_unified_diff"][:8000])
|
| 114 |
lines.append("```")
|
| 115 |
|
| 116 |
+
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|