Spaces:
Running
Running
Download code/rq_build.py from ArthurZ/transformers-triage: direct link, hf CLI and curl.
- Browser
- Download file 12.7 kB
-
https://huggingface.co/spaces/ArthurZ/transformers-triage/resolve/main/code/rq_build.py
- Command line
-
hf download hf://spaces/ArthurZ/transformers-triage/code/rq_build.py
-
curl -L -o rq_build.py https://huggingface.co/spaces/ArthurZ/transformers-triage/resolve/main/code/rq_build.py
12.7 kB
| import json, subprocess, html as H, re | |
| from collections import Counter, defaultdict | |
| from datetime import datetime, timezone | |
| NOW = datetime(2026, 10, 21, tzinfo=timezone.utc) | |
| days = lambda s: (NOW - datetime.fromisoformat(s.replace('Z', '+00:00'))).days | |
| STAFF = {'gante','Narsil','qubvel','kashif','XciD','asartran','ydshieh','amyeroberts','ArthurZucker','Rocketknight1','zucchini-nlp','stevhliu','SunMarc','MekkCyber','vasqu','molbap','Cyrilvallez','McPatate','eustlb','yonigozlan','LysandreJik','NielsRogge','itazap','remi-or','3outeille','IlyasMoutawwakil','ivarflakstad','manueldeprada','ebezzam','guarin','danieldk','drbh','merveenoyan','BenjaminBossan','qgallouedec','tarekziade','Wauplin','hanouticelina'} | |
| CORE = {'ArthurZucker', 'Cyrilvallez', 'LysandreJik', 'vasqu'} | |
| HFA = {'MEMBER', 'OWNER', 'COLLABORATOR'} | |
| isstaff = lambda login, assoc: bool(login) and (login in STAFF or assoc in HFA) and not re.search(r'(?i)bot', login) | |
| D = {int(k): v for k, v in json.load(open('rq.json')).items()} | |
| G = ['git', '-C', '/Users/arthurzucker/Work/transformers'] | |
| wait, appr = [], [] | |
| for n, p in D.items(): | |
| au = (p['author'] or {}).get('login') or '' | |
| revs = [r for r in p['reviews']['nodes'] if r['author'] and r['author']['login'] != au] | |
| srevs = [r for r in revs if isstaff(r['author']['login'], r['authorAssociation'])] | |
| scom = [c for c in p['comments']['nodes'] if c['author'] and c['author']['login'] != au and isstaff(c['author']['login'], c['authorAssociation']) and not (c['author']['login'] == 'ArthurZucker' and c['createdAt'] >= '2026-10-20')] # ponytail: today's triage pings aren't reviews | |
| acom = [c['createdAt'] for c in p['comments']['nodes'] if c['author'] and c['author']['login'] == au] | |
| commit = p['commits']['nodes'][0]['commit'] if p['commits']['nodes'] else {} | |
| ci = ((commit.get('statusCheckRollup') or {}).get('state')) or 'none' | |
| last_author = max([commit.get('committedDate') or p['createdAt']] + acom) | |
| last_staff = max([r['submittedAt'] for r in srevs if r['submittedAt']] + [c['createdAt'] for c in scom] or ['']) | |
| ready = max([e['createdAt'] for e in p['timelineItems']['nodes'] if e['__typename'] == 'ReadyForReviewEvent'] or [p['createdAt']]) | |
| reqs = [(r['requestedReviewer'] or {}).get('login') or (r['requestedReviewer'] or {}).get('name') for r in p['reviewRequests']['nodes']] | |
| reqs = [r for r in reqs if r] | |
| req_at = {e['requestedReviewer']['login']: e['createdAt'] for e in p['timelineItems']['nodes'] if e['__typename'] == 'ReviewRequestedEvent' and e.get('requestedReviewer')} | |
| conflict = p['mergeable'] == 'CONFLICTING' | |
| flags = (['conflicts'] if conflict else []) + (['CI red'] if ci in ('FAILURE', 'ERROR') else []) | |
| base = dict(n=n, t=p['title'], au=au, hf=isstaff(au, p['authorAssociation']), ci=ci, flags=flags, age=days(p['createdAt'])) | |
| approvals = [r for r in srevs if r['state'] == 'APPROVED'] | |
| if p['reviewDecision'] == 'APPROVED' or approvals: | |
| a = max(approvals, key=lambda r: r['submittedAt']) if approvals else None | |
| when = a['submittedAt'] if a else p['updatedAt']; who = a['author']['login'] if a else '?' | |
| core_ok = any(r['author']['login'] in CORE for r in approvals) | |
| after = (commit.get('committedDate') or '') > when | |
| files = [f['path'] for f in p['files']['nodes']] | |
| churn = int(subprocess.run(G + ['rev-list', '--count', f'--since={when}', 'origin/main', '--'] + files, capture_output=True, text=True).stdout.strip() or 0) if files else 0 | |
| gone = sum(1 for f in files if subprocess.run(G + ['cat-file', '-e', f'origin/main:{f}'], capture_output=True).returncode) if files else 0 | |
| why = [] | |
| if conflict: why.append('needs rebase (conflicts)') | |
| if ci in ('FAILURE', 'ERROR'): why.append('CI red') | |
| if after: why.append('pushed after approval → needs re-approval') | |
| if not core_ok: why.append('no core-maintainer approval yet' + (f' (approved by @{who})' if who != '?' else '')) | |
| if gone and gone >= len(files) / 2: why.append(f'likely superseded/outdated: {gone}/{len(files)} files gone from main') | |
| elif churn >= 15: why.append(f'likely outdated: main touched its files {churn}× since approval') | |
| if not why: why.append('nothing blocking → probably forgotten, ready to merge') | |
| if p['baseRefName'] != 'main': why.insert(0, f"targets `{p['baseRefName']}`, not main") | |
| appr.append({**base, 'who': who, 'when': when[:10], 'd': days(when), 'why': why, 'primary': why[0].split(' (')[0].split(':')[0], 'churn': churn}) | |
| continue | |
| if p['reviewDecision'] == 'CHANGES_REQUESTED' or (last_staff and last_staff > last_author): | |
| since = last_staff; who_last = (max(srevs + [{'submittedAt': c['createdAt'], 'author': c['author']} for c in scom], key=lambda r: r['submittedAt'] or '')['author']['login']) if (srevs or scom) else '?' | |
| wait.append({**base, 'bucket': 'waiting on author', 'since': since[:10], 'd': days(since), 'why': f'@{who_last} replied last ({since[:10]}); ball is with the author', 'who': who_last}); continue | |
| if not srevs and not scom: | |
| since = ready | |
| if reqs: | |
| r0 = reqs[0]; ra = req_at.get(r0, since) | |
| why = f'never reviewed · review requested from @{", @".join(reqs)} {days(ra)}d ago, no response'; bucket = 'assigned, no response' | |
| else: | |
| why = 'never reviewed · no reviewer assigned'; bucket = 'no reviewer assigned' | |
| else: | |
| since = last_author | |
| who_last = max(srevs + [{'submittedAt': c['createdAt'], 'author': c['author']} for c in scom], key=lambda r: r['submittedAt'] or '')['author']['login'] | |
| why = f'author updated after @{who_last}\'s review → needs re-review' + (f' (requested: @{", @".join(reqs)})' if reqs else ''); bucket = 'needs re-review' | |
| if base['hf']: bucket = 'HF-authored, no review'; why = 'maintainer PR · ' + why | |
| wait.append({**base, 'bucket': bucket, 'since': since[:10], 'd': days(since), 'why': why, 'who': ', '.join(reqs)}) | |
| QA = json.load(open('qa_verdicts.json')) | |
| def tag(r): | |
| q = QA.get(str(r['n'])) | |
| if not q: return '' | |
| return '✓ audited: still valid' if q['verdict'] == 'valid' else f"⚠ audit: {q['verdict']}" + (f" (#{q['by']})" if q.get('by') else '') | |
| for r in appr: r['why'] = r['why'] + ([tag(r)] if tag(r) else []) | |
| for r in wait: | |
| if tag(r): r['why'] = r['why'] + ' · ' + tag(r) | |
| appr = [r for r in appr if QA.get(str(r['n']), {}).get('state', 'OPEN') == 'OPEN'] | |
| wait = [r for r in wait if QA.get(str(r['n']), {}).get('state', 'OPEN') == 'OPEN'] | |
| json.dump({'wait': wait, 'appr': appr}, open('rq_rows.json', 'w')) | |
| DB = lambda d: '<7d' if d < 7 else '7–30d' if d < 30 else '30–90d' if d < 90 else '90–180d' if d < 180 else '>180d' | |
| DUR = ['<7d', '7–30d', '30–90d', '90–180d', '>180d'] | |
| def matrix(rows, keyf, keys, title): | |
| m = Counter((keyf(r), DB(r['d'])) for r in rows); tot = Counter(keyf(r) for r in rows) | |
| head = ''.join(f'<th>{d}</th>' for d in DUR) | |
| body = ''.join(f"<tr><td class='l'>{H.escape(k)}</td>" + ''.join(f"<td class='c' style='--a:{min(1, m[(k, d)] / 25):.2f}' title='{H.escape(k)} · {d}: {m[(k, d)]}'>{m[(k, d)] or ''}</td>" for d in DUR) + f"<td><b>{tot[k]}</b></td></tr>" for k in keys if tot[k]) | |
| return f"<section class='card'><h3>{title}</h3><table class='mx'><tr><th></th>{head}<th>total</th></tr>{body}</table></section>" | |
| def table(rows, cols, sid): | |
| th = ''.join(f'<th>{c}</th>' for c, _ in cols) | |
| tr = ''.join('<tr>' + ''.join(f'<td>{f(r)}</td>' for _, f in cols) + '</tr>' for r in rows) | |
| return f"<table class='list' id='{sid}'><thead><tr>{th}</tr></thead><tbody>{tr}</tbody></table>" | |
| link = lambda r: f"<a href='https://github.com/huggingface/transformers/pull/{r['n']}' target='_blank'>#{r['n']}</a>" | |
| esc = lambda s: H.escape(str(s)) | |
| WB = ['no reviewer assigned', 'assigned, no response', 'needs re-review', 'HF-authored, no review', 'waiting on author'] | |
| ours = [r for r in wait if r['bucket'] != 'waiting on author'] | |
| wsec = '' | |
| for b in WB: | |
| rs = sorted([r for r in wait if r['bucket'] == b], key=lambda r: -r['d']) | |
| if not rs: continue | |
| wsec += f"<details {'open' if b != 'waiting on author' else ''}><summary><b>{b}</b> — {len(rs)} PRs · median wait {sorted(r['d'] for r in rs)[len(rs)//2]}d</summary>" + table(rs, [('PR', link), ('title', lambda r: esc(r['t'][:90])), ('author', lambda r: esc('@' + r['au'])), ('waiting', lambda r: f"{r['d']}d <span class='mut'>since {r['since']}</span>"), ('why', lambda r: esc(r['why'])), ('flags', lambda r: esc(', '.join(r['flags'])))], 'w' + b[:4]) + '</details>' | |
| AB = ['nothing blocking → probably forgotten, ready to merge', 'no core-maintainer approval yet', 'pushed after approval → needs re-approval', 'needs rebase', 'CI red', 'likely outdated', 'likely superseded/outdated', 'targets `'] | |
| asec = '' | |
| for b in sorted({r['primary'] for r in appr}, key=lambda x: (0 if x.startswith('nothing') else 1, x)): | |
| rs = sorted([r for r in appr if r['primary'] == b], key=lambda r: -r['d']) | |
| asec += f"<details open><summary><b>{esc(b)}</b> — {len(rs)} PRs</summary>" + table(rs, [('PR', link), ('title', lambda r: esc(r['t'][:90])), ('author', lambda r: esc('@' + r['au'])), ('approved', lambda r: f"{r['when']} by @{esc(r['who'])} <span class='mut'>({r['d']}d ago)</span>"), ('why not merged', lambda r: '<br>'.join(esc(w) for w in r['why']))], 'a' + str(len(asec))) + '</details>' | |
| tiles = (f"<div class='tiles'><div class='tile'><b>{len(ours)}</b>waiting on a maintainer</div><div class='tile'><b>{sum(r['d']>=90 for r in ours)}</b>of those waiting ≥ 90 days</div>" | |
| f"<div class='tile'><b>{sum(r['bucket']=='no reviewer assigned' for r in wait)}</b>never reviewed, nobody assigned</div><div class='tile'><b>{len(appr)}</b>approved, not merged</div>" | |
| f"<div class='tile'><b>{sum(r['primary'].startswith('nothing') for r in appr)}</b>approved & nothing blocking</div><div class='tile'><b>{sum(r['bucket']=='waiting on author' for r in wait)}</b>waiting on the author</div></div>") | |
| page = f"""<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Review Queue</title><style> | |
| :root{{--bg:#fafaf9;--fg:#1c1917;--mut:#78716c;--card:#fff;--bd:#e7e5e4;--acc:#2563eb;--heat:37,99,235}} | |
| @media (prefers-color-scheme:dark){{:root:not([data-theme="light"]){{--bg:#1c1917;--fg:#f5f5f4;--mut:#a8a29e;--card:#292524;--bd:#44403c;--acc:#60a5fa;--heat:96,165,250}}}} | |
| :root[data-theme="dark"]{{--bg:#1c1917;--fg:#f5f5f4;--mut:#a8a29e;--card:#292524;--bd:#44403c;--acc:#60a5fa;--heat:96,165,250}} | |
| body{{background:var(--bg);color:var(--fg);font:14px/1.45 system-ui,sans-serif;margin:0 auto;padding:16px;max-width:1250px}}h1{{font-size:20px;margin:4px 0}}h2{{font-size:16px;margin:26px 0 8px}}h3{{font-size:13px;color:var(--mut);margin:0 0 6px}} | |
| .mut{{color:var(--mut)}}a{{color:var(--acc)}}.tiles{{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;margin:12px 0}}.tile{{background:var(--card);border:1px solid var(--bd);border-radius:8px;padding:10px;color:var(--mut);font-size:12px}}.tile b{{display:block;font-size:26px;color:var(--fg)}} | |
| .grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:10px}}.card{{background:var(--card);border:1px solid var(--bd);border-radius:8px;padding:10px 12px;overflow-x:auto}} | |
| .mx{{border-collapse:collapse;font-size:12px;width:100%}}.mx th,.mx td{{border:1px solid var(--bd);padding:3px 6px;text-align:right}}.mx td.l{{text-align:left}}.mx td.c{{background:rgba(var(--heat),calc(var(--a)*.55))}} | |
| details{{background:var(--card);border:1px solid var(--bd);border-radius:8px;margin:8px 0;padding:6px 10px}}summary{{cursor:pointer}} | |
| .list{{width:100%;border-collapse:collapse;font-size:12px;margin-top:6px}}.list th,.list td{{border-bottom:1px solid var(--bd);padding:3px 6px;text-align:left;vertical-align:top}}.list th{{color:var(--mut)}} | |
| </style></head><body><h1>Review queue — open PRs waiting on us</h1><div class="mut">as of 2026-10-21 · {len(D)} non-draft open PRs · core = @ArthurZucker, @Cyrilvallez, @LysandreJik, @vasqu</div> | |
| {tiles}<div class="grid">{matrix(wait, lambda r: r['bucket'], WB, 'Waiting for review: why × how long')}{matrix(appr, lambda r: r['primary'], sorted({r['primary'] for r in appr}), 'Approved, not merged: blocker × time since approval')}</div> | |
| <h2>Approved but not merged ({len(appr)})</h2>{asec}<h2>Waiting for review ({len(wait)})</h2>{wsec}</body></html>""" | |
| open('review_queue.html', 'w').write(page) | |
| print('wait', Counter(r['bucket'] for r in wait), '| approved', len(appr), Counter(r['primary'] for r in appr)) | |
| print('ours >=90d:', sum(r['d'] >= 90 for r in ours), 'of', len(ours)) | |