"""Deliverable 1: inventory of every minimax-rp run in production D1 -> out/runs.csv, out/runs.md.
Inputs: raw/runs_meta.json, raw/runs_detail.json (queries in raw/*.sql), raw/export/sessions.jsonl."""
import sys; sys.path.insert(0, '${RPB_AUDIT_DIR}/scripts')
from common import *

runs = load_runs()
sess = defaultdict(list)
for s in load_jsonl('sessions'):
    s['result'] = json.loads(s['result_json']) if s['result_json'] else None; sess[s['run_id']].append(s)

def group(r):
    c = r['config']; p = panel(r)
    if r['origin'] == 'minimax': return 'e'
    if r['origin'] == 'calibration' or c.get('mode') == 'calibration': return 'b'
    if c.get('experiment'): return 'd'
    if p == 'official' and c.get('mode') == 'full': return 'a'
    if p == 'screening': return 'c'
    return 'd'

GROUP_NAME = {'a': '(a) official-panel full-protocol platform runs', 'b': '(b) calibration runs on MiniMax released dialogues',
              'c': '(c) screening-panel runs', 'd': '(d) experiments / protocol-development runs (pre-v5 quick runs, trials)', 'e': '(e) imported MiniMax data'}
rows = []
for rid, r in sorted(runs.items(), key=lambda kv: (kv[1]['created_at'], kv[0])):
    c, res, ss = r['config'], r['result'], sess[rid]
    done = [s for s in ss if s['status'] == 'done' and s['result']]
    scored = [s for s in done if not s['result'].get('excluded')]
    pen = [s for s in scored if s['result'].get('penalty')]
    sub = [s for s in scored if s['result'].get('substituted')]
    seeds = sorted({s['seed_id'] for s in ss}); langs = sorted({s['lang'] for s in ss})
    spp = Counter(Counter(s['seed_id'] for s in ss).values())
    us = r['usersim']
    assign = 'n/a' if r['origin'] == 'minimax' else ('n/a (calibration: no simulator)' if c.get('mode') == 'calibration' else (us.get('assignment') or 'hash'))
    # recomputed overall from stored session results (combined, i.e. after the session rule)
    rec = {}
    for view in ['all', 'zh', 'en']:
        v = [s['result']['combined'] for s in scored if view == 'all' or s['lang'] == view]
        rec[view] = agg(v)[1] if v else None
    stored = {view: ((res or {}).get('perLang') or {}).get(view, {}).get('overall') for view in ['all', 'zh', 'en']}
    elig = ((res or {}).get('eligibility') or {})
    rows.append({
        'group': group(r), 'run_id': rid, 'created_utc': utc(r['created_at']), 'finished_utc': utc(r['finished_at']), 'status': r['status'],
        'display_name': r['display_name'], 'target_model': r['target_model'], 'origin': r['origin'], 'mode': c.get('mode'),
        'experiment': json.dumps(c['experiment'], ensure_ascii=False) if c.get('experiment') else '',
        'protocol_version': r['pv'] or '', 'panel': panel(r), 'judges': judges_label(r),
        'judge_max_tokens': ';'.join(f"{j['key']}={j.get('maxTokens')}" for j in r['judges']) if r['origin'] != 'minimax' else '',
        'judging_mode': ('fastJudging(sync)' if c.get('fastJudging') else ('batch' if any(j.get('batch') for j in r['judges']) else 'sync')) + (f"; directJudging from {utc(c['directJudging']['at'])}" if c.get('directJudging') else ''),
        'sim_assignment': assign, 'sim_models': ' / '.join(m['model'] for m in us.get('models', [])) if us.get('models') else (us.get('model') or ''),
        'length_policy': us.get('lengthPolicy') or '', 'langs': ','.join(langs), 'n_seeds': len(seeds),
        'samples_per_seed_cfg': c.get('samplesPerSeed'), 'sessions_per_seed_obs': ';'.join(f'{k}x{v}' for k, v in sorted(spp.items())),
        'turns': c.get('turns'), 'sessions_done': len(done), 'sessions_total': len(ss), 'excluded': len(done) - len(scored),
        'penalised': len(pen), 'substituted': len(sub), 'valid': r['valid'], 'eligible': elig.get('eligible', ''), 'published': r['published'],
        'overall_all_stored': stored['all'], 'overall_zh_stored': stored['zh'], 'overall_en_stored': stored['en'],
        'overall_all_recomputed': round(rec['all'], 2) if rec['all'] is not None else None,
        'overall_zh_recomputed': round(rec['zh'], 2) if rec['zh'] is not None else None, 'overall_en_recomputed': round(rec['en'], 2) if rec['en'] is not None else None,
    })
with open(f'{OUT}/runs.csv', 'w', newline='') as f:
    w = csv.DictWriter(f, fieldnames=list(rows[0])); w.writeheader(); w.writerows(rows)
md = ['# Role-Play Bench (minimax-rp) runs in production D1', '',
      f'Source: production D1 `rp-bench-db` (read-only SELECTs, raw/runs_meta.sql, raw/runs_detail.sql, raw/export/queries.sql), exported {utc(max(s["updated_at"] for v in sess.values() for s in v))} UTC (latest session update).',
      'Overall = stored `result_json.perLang[view].overall`; recomputed = mean of session `combined` dims (after the wrong-language rule) through the published weights. Running/paused runs have no stored result.', '']
for g in 'abcde':
    gr = [x for x in rows if x['group'] == g]
    md += [f'## {GROUP_NAME[g]} — {len(gr)} runs', '']
    md.append(md_table(['run', 'created UTC', 'finished UTC', 'status', 'name / target', 'mode', 'protocol', 'panel', 'sim assign', 'langs', 'seeds', 'smp/seed', 'done/total', 'excl', 'pen', 'subst', 'valid', 'publ', 'overall all (zh / en)'],
        [[x['run_id'][:10], x['created_utc'][5:16], x['finished_utc'][5:16], x['status'], f"{x['display_name']} / {x['target_model']}", x['mode'], x['protocol_version'], f"{x['panel']}: {x['judges']}",
          x['sim_assignment'], x['langs'], x['n_seeds'], x['samples_per_seed_cfg'], f"{x['sessions_done']}/{x['sessions_total']}", x['excluded'], x['penalised'], x['substituted'], x['valid'], x['published'],
          f"{f2(x['overall_all_stored'])} ({f2(x['overall_zh_stored'])} / {f2(x['overall_en_stored'])})" if x['overall_all_stored'] is not None else f"[partial, recomputed] {f2(x['overall_all_recomputed'])} ({f2(x['overall_zh_recomputed'])} / {f2(x['overall_en_recomputed'])})"] for x in gr]))
    md.append('')
mismatch = [x for x in rows if x['overall_all_stored'] is not None and x['overall_all_recomputed'] is not None and abs(x['overall_all_stored'] - x['overall_all_recomputed']) > 0.011]
md += ['## Check: stored vs recomputed overall', '', f'{len(mismatch)} runs differ by more than 0.01: ' + ', '.join(f"{x['run_id'][:10]} stored {x['overall_all_stored']} recomputed {x['overall_all_recomputed']}" for x in mismatch), '']
open(f'{OUT}/runs.md', 'w').write('\n'.join(md))
print('\n'.join(md))
