import json, re, html as H from collections import Counter from datetime import datetime, timezone NOW = datetime(2026, 10, 21, tzinfo=timezone.utc) age = lambda s: (NOW - datetime.fromisoformat(s.replace('Z', '+00:00'))).days D = json.load(open('land.json')) MODELS = set(open('models_main.txt').read().split()) HF = {'MEMBER', 'OWNER', 'COLLABORATOR'} BOTS = re.compile(r'(?i)bot|dependabot|github-actions|hf-security') AREA_RULES = [ # (regex on path, area) — first match per file (r'^src/transformers/models/[^/]+/(image_processing|video_processing|feature_extraction|processing)_', 'processors (image/video/audio)'), (r'^src/transformers/models/', 'models'), (r'^src/transformers/generation/continuous_batching', 'serving / continuous batching'), (r'^src/transformers/generation/', 'generation'), (r'^src/transformers/(trainer|training_args|optimization)', 'trainer'), (r'tokenization', 'tokenization'), (r'^src/transformers/(quantizers/|integrations/(bitsandbytes|gptq|awq|hqq|quanto|torchao|fp_quant|compressed|fbgemm|mxfp4|eetq|higgs|spqr|vptq|bitnet|finegrained))', 'quantization'), (r'^src/transformers/integrations/(gguf|ggml)|gguf', 'gguf'), (r'^src/transformers/(integrations/(flash|sdpa|flex|hub_kernels|npu)|kernels/|modeling_flash)', 'attention / kernels'), (r'^src/transformers/pipelines/', 'pipelines'), (r'^src/transformers/(cli|commands)/', 'serving / continuous batching'), (r'^src/transformers/(image_processing|video_processing|feature_extraction|processing_utils|audio_utils|image_utils|video_utils)', 'processors (image/video/audio)'), (r'^src/transformers/(cache_utils|masking_utils)', 'cache / masks'), (r'^src/transformers/(modeling_utils|core_model_loading|conversion_mapping|configuration_utils|safetensors|modeling_rope)', 'core / loading'), (r'^src/transformers/(distributed|integrations/(tensor_parallel|fsdp|deepspeed|accelerate|moe))', 'distributed'), (r'^src/transformers/exporters|executorch|onnx', 'exporters'), (r'^src/transformers/layer_execution', 'layer execution'), (r'^src/transformers/loss/', 'trainer'), (r'^src/transformers/(testing_utils)', 'tests'), (r'^src/transformers/(utils/|__init__|hf_argparser|activations|pytorch_utils|modeling_outputs|dependency_versions)', 'core / utils'), (r'^src/transformers/image_transforms', 'processors (image/video/audio)'), (r'^src/transformers/integrations/', 'integrations (other)'), (r'^docs/', 'docs'), (r'^tests/', 'tests'), (r'^(\.github|\.circleci|docker|utils|benchmark|Makefile|setup|pyproject)', 'CI / tooling'), (r'^examples/', 'examples'), (r'.', 'other')] ISSUE_KW = [(r'(?i)\bgguf\b', 'gguf'), (r'(?i)export|onnx|executorch|torch\.compile|compile', 'exporters'), (r'(?i)For(Sequence|Token)Classification|QuestionAnswering|classification head|detection head|\bheads?\b', 'models'), (r'(?i)\bloss\b|label smoothing|muon|save_strategy|optimi[sz]er', 'trainer'), (r'(?i)typing|FloatTensor|RopeParameters|import_utils', 'core / utils'), (r'(?i)quantiz|bitsandbytes|bnb|awq|gptq|fp8|int4|int8|torchao|mxfp4|compressed.tensors', 'quantization'), (r'(?i)tokeni[sz]|chat.template|special token|vocab', 'tokenization'), (r'(?i)trainer|training|fine.?tun|deepspeed|fsdp|gradient|optimizer|lr sched', 'trainer'), (r'(?i)generat|beam|sampling|logits|stopping|streamer|speculat|assist', 'generation'), (r'(?i)\bcache\b|kv.cache|past_key|mask', 'cache / masks'), (r'(?i)flash.?attn|flash.?attention|sdpa|flex.?attention|kernel|attention impl', 'attention / kernels'), (r'(?i)tensor.parallel|\btp\b|expert.parallel|distributed|multi.?gpu|device_map|accelerate', 'distributed'), (r'(?i)pipeline', 'pipelines'), (r'(?i)serve|continuous.batch|generate_batch|\bcli\b', 'serving / continuous batching'), (r'(?i)image.?processor|processor|feature.extract|video|audio|vision', 'processors (image/video/audio)'), (r'(?i)\bdocs?\b|documentation|translat|typo', 'docs'), (r'(?i)from_pretrained|save_pretrained|load|safetensors|checkpoint|hub', 'core / loading'), (r'(?i)\btests?\b|\bci\b|flaky', 'tests')] def pr_area(p): files = [f['path'] for f in (p.get('files') or {}).get('nodes', [])] c = Counter() for f in files: for rx, a in AREA_RULES: if re.search(rx, f): c[a] += 1; break if not c: return 'other', files top = c.most_common(1)[0][0] if top == 'tests' and len(c) > 1: top = [a for a, _ in c.most_common() if a != 'tests'][0] return top, files def models_in(files, title): s = {m.group(1) for f in files for m in [re.match(r'src/transformers/models/([^/]+)/', f)] if m} sq = re.sub(r'[^a-z0-9]', '', title.lower()) s |= {m for m in MODELS if len(m.replace('_','')) > 3 and re.search(r'(? 4 and '_' in m and m.replace('_','') in sq} return s - {'auto', 'deprecated'} rows = [] for p in D['prs']: area, files = pr_area(p); t = p['title'] newdirs = {m.group(1) for f in files for m in [re.match(r'src/transformers/models/([^/]+)/modeling_', f)] if m and m.group(1) not in MODELS} kind = 'new model' if newdirs or re.search(r'(?i)^\[?add(ing)? (support for )?[A-Z]|new model', t) and area == 'models' else \ 'fix' if re.search(r'(?i)\bfix|bug|error|crash|wrong|broken|regress', t) else \ 'docs' if area == 'docs' else 'CI / tooling' if area in ('CI / tooling', 'tests') else 'feature / refactor' rd = p.get('reviewDecision') status = 'draft' if p['isDraft'] else 'approved' if rd == 'APPROVED' else 'changes requested' if rd == 'CHANGES_REQUESTED' else \ 'conflicting' if p.get('mergeable') == 'CONFLICTING' else 'awaiting review' au = (p['author'] or {}).get('login') or '' who = 'bot' if BOTS.search(au) else 'HF' if p['authorAssociation'] in HF else 'first-timer' if p['authorAssociation'] in ('FIRST_TIME_CONTRIBUTOR', 'FIRST_TIMER', 'NONE') else 'contributor' rows.append(dict(k='PR', n=p['number'], t=t, area=area, kind=kind, status=status, who=who, au=au, age=age(p['createdAt']), idle=age(p['updatedAt']), size=p['additions'] + p['deletions'], models=sorted(models_in(files, t)), lab=[l['name'] for l in p['labels']['nodes']], react=p['reactions']['totalCount'])) for i in D['issues']: t = i['title']; lab = [l['name'] for l in i['labels']['nodes']] area = 'models' if 'New model' in lab else next((a for rx, a in ISSUE_KW if re.search(rx, t + ' ' + ' '.join(lab))), 'models' if models_in([], t) else 'other') kind = 'new model' if 'New model' in lab else 'feature' if 'Feature request' in lab else 'bug' if ('bug' in lab or re.search(r'(?i)error|bug|fail|crash|wrong|broken|not work|issue with|incorrect|mismatch|regression|nan\b|oom', t)) else \ 'docs' if area == 'docs' else 'tracker / RFC' if re.search(r'(?i)rfc|tracker|roadmap|\[.*(call|community).*\]|meta', t) else 'question / other' au = (i['author'] or {}).get('login') or '' who = 'bot' if BOTS.search(au) else 'HF' if i['authorAssociation'] in HF else 'community' status = 'active <14d' if age(i['updatedAt']) < 14 else 'idle 14–90d' if age(i['updatedAt']) < 90 else 'idle >90d' rows.append(dict(k='issue', n=i['number'], t=t, area=area, kind=kind, status=status, who=who, au=au, age=age(i['createdAt']), idle=age(i['updatedAt']), size=i['comments']['totalCount'], models=sorted(models_in([], t)), lab=lab, react=i['reactions']['totalCount'])) json.dump(rows, open('land_rows.json', 'w')) P = [r for r in rows if r['k'] == 'PR']; I = [r for r in rows if r['k'] == 'issue'] bucket = lambda d: '<30d' if d < 30 else '30–90d' if d < 90 else '90–180d' if d < 180 else '180d–1y' if d < 365 else '>1y' AGE = ['<30d', '30–90d', '90–180d', '180d–1y', '>1y'] def bars(title, cnt, order=None, total=None, key=None): items = [(k, cnt.get(k, 0)) for k in order] if order else cnt.most_common() mx = max([v for _, v in items] or [1]); total = total or sum(cnt.values()) li = ''.join(f"{H.escape(k)}" f"{v}{100*v/total:.0f}%" for k, v in items if v) return f"

{title}

{li}
" mc = Counter(m for r in rows for m in r['models']); mcp = Counter(m for r in P for m in r['models']); mci = Counter(m for r in I for m in r['models']) topm = ''.join(f"{m}{v}{mcp[m]} PR · {mci[m]} iss" for m, v in mc.most_common(20)) tiles = (f"
{len(I)}open issues
{len(P)}open PRs
" f"
{sum(r['status']=='awaiting review' for r in P)}PRs awaiting review
" f"
{sum(r['status']=='approved' for r in P)}PRs approved, not merged
" f"
{sum(r['kind']=='bug' for r in I)}bug reports
" f"
{sum(r['idle']<14 for r in rows)}active in last 14d
") SEC = (tiles + "

Pull requests

" + bars('By area (from files touched)', Counter(r['area'] for r in P), key='area') + bars('By status', Counter(r['status'] for r in P), ['awaiting review','approved','changes requested','conflicting','draft'], key='status') + bars('By kind', Counter(r['kind'] for r in P), key='kind') + bars('Author', Counter(r['who'] for r in P), ['HF','contributor','first-timer','bot'], key='who') + bars('Age (opened)', Counter(bucket(r['age']) for r in P), AGE) + "
" + "

Issues

" + bars('By area (labels + title)', Counter(r['area'] for r in I), key='area') + bars('By kind', Counter(r['kind'] for r in I), key='kind') + bars('Activity', Counter(r['status'] for r in I), ['active <14d','idle 14–90d','idle >90d'], key='status') + bars('Author', Counter(r['who'] for r in I), ['HF','community','bot'], key='who') + bars('Age (opened)', Counter(bucket(r['age']) for r in I), AGE) + "
" + "

Most-touched models (PRs + issues)

" + topm + "
") AREAS = sorted({r['area'] for r in rows}) page = """Open Backlog Map

transformers open backlog — what's left

as of 2026-10-21 · after the Oct 20–21 cleanup · click any bar to filter the table below
__SEC__

Every open item

type#titleareakindstatusauthorage didle dmodels
""" page = page.replace('__SEC__', SEC).replace('__AREAS__', ''.join(f'' for a in AREAS)).replace('__ROWS__', json.dumps(rows).replace('