| import json |
| from typing import Any, Dict, List, Optional |
|
|
|
|
| def _short(x: Any, n: int = 160) -> str: |
| try: |
| s = json.dumps(x, ensure_ascii=False) |
| except Exception: |
| s = str(x) |
| if len(s) > n: |
| return s[:n] + "…" |
| return s |
|
|
|
|
| def _fmt_reward(x: Optional[float]) -> str: |
| if x is None: |
| return "n/a" |
| |
| return f"{x:.6g}" |
|
|
|
|
| def render_report_markdown(diff: Dict[str, Any]) -> str: |
| s = diff.get("summary", {}) |
| counts = diff.get("class_counts", {}) |
| first = s.get("first_divergence_index") |
|
|
| |
| line_bits: List[str] = [] |
| if first is None: |
| line_bits.append("Timelines identical") |
| else: |
| line_bits.append(f"Identical until index {first}") |
| line_bits.append(f"{s.get('diff_event_count', 0)} events differ") |
| if s.get("missing_event_count", 0): |
| line_bits.append(f"{s.get('missing_event_count')} missing") |
| if s.get("final_reward_delta") is not None: |
| line_bits.append(f"Final reward delta: {s.get('final_reward_delta'):.6g}") |
| one_liner = " · ".join(line_bits) |
|
|
| lines: List[str] = [] |
| lines.append("# DRP Differential Report") |
| lines.append("") |
| lines.append(f"**{one_liner}**") |
| lines.append("") |
| lines.append("## Run identity") |
| lines.append(f"- Run A: `{s.get('run_a')}`") |
| lines.append(f"- Run B: `{s.get('run_b')}`") |
| lines.append(f"- Framework A/B: `{s.get('framework_a')}` / `{s.get('framework_b')}`") |
| lines.append(f"- Model A/B: `{s.get('model_a')}` / `{s.get('model_b')}`") |
| lines.append(f"- Events A/B: `{s.get('events_a')}` / `{s.get('events_b')}`") |
| lines.append("") |
|
|
| if s.get("run_link_a") or s.get("run_link_b"): |
| lines.append("## Viewer links (if provided)") |
| if s.get("run_link_a"): |
| lines.append(f"- Run A viewer: {s.get('run_link_a')}") |
| if s.get("run_link_b"): |
| lines.append(f"- Run B viewer: {s.get('run_link_b')}") |
| lines.append("") |
|
|
| lines.append("## Summary stats") |
| lines.append(f"- First divergence index: `{s.get('first_divergence_index')}`") |
| lines.append(f"- Diff events: `{s.get('diff_event_count')}`") |
| lines.append(f"- Missing events: `{s.get('missing_event_count')}`") |
| 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'))}`") |
| lines.append("") |
|
|
| lines.append("## Divergence classes") |
| if counts: |
| for k in sorted(counts.keys()): |
| lines.append(f"- **{k}**: {counts[k]}") |
| else: |
| lines.append("- (no diffs)") |
| lines.append("") |
|
|
| |
| lines.append("## First divergence detail") |
| if first is None: |
| lines.append("- No divergence found.") |
| return "\n".join(lines) |
|
|
| first_item = None |
| for item in diff.get("differences", []): |
| if item.get("i") == first: |
| first_item = item |
| break |
|
|
| if not first_item: |
| |
| lines.append("- Divergence is a length mismatch or occurs where one side is missing.") |
| return "\n".join(lines) |
|
|
| lines.append(f"- Index: `{first_item.get('i')}`") |
| lines.append(f"- Class: `{first_item.get('class')}`") |
| lines.append(f"- Kind A/B: `{first_item.get('kind_a')}` / `{first_item.get('kind_b')}`") |
| lines.append(f"- Step A/B: `{first_item.get('step_a')}` / `{first_item.get('step_b')}`") |
| if first_item.get("link_a") or first_item.get("link_b"): |
| lines.append("") |
| lines.append("### Jump to replay (if provided)") |
| if first_item.get("link_a"): |
| lines.append(f"- Open A @ i={first}: {first_item.get('link_a')}") |
| if first_item.get("link_b"): |
| lines.append(f"- Open B @ i={first}: {first_item.get('link_b')}") |
| lines.append("") |
| lines.append("### Key diffs (first 25)") |
| for d in (first_item.get("diffs") or [])[:25]: |
| lines.append(f"- `{d.get('path')}` ({d.get('kind')}): A={_short(d.get('a'))} | B={_short(d.get('b'))}") |
|
|
| if "text_unified_diff" in first_item: |
| lines.append("") |
| lines.append("### Text unified diff (truncated)") |
| lines.append("```diff") |
| lines.append(first_item["text_unified_diff"][:8000]) |
| lines.append("```") |
|
|
| return "\n".join(lines) |