File size: 4,360 Bytes
c71f147
f337b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c71f147
 
 
f337b2b
c71f147
f337b2b
 
 
 
 
 
 
 
 
 
 
 
 
 
c71f147
 
 
 
f337b2b
 
 
c71f147
 
 
 
 
f337b2b
 
 
 
 
 
 
 
 
 
 
c71f147
f337b2b
 
 
c71f147
 
 
 
 
 
 
 
 
 
f337b2b
 
 
 
 
 
c71f147
f337b2b
 
 
 
c71f147
 
f337b2b
 
c71f147
 
 
 
 
 
f337b2b
 
 
 
 
 
 
c71f147
 
 
 
 
 
 
 
 
 
 
 
f337b2b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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"
    # stable, readable
    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")

    # punchy one-liner
    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("")

    # First divergence detail
    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:
        # likely a length mismatch only
        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)