SeaWolf-AI commited on
Commit
fc694c4
·
verified ·
1 Parent(s): d15977a

Element tabs: max temperature, min temperature, wind, precipitation

Browse files
Files changed (1) hide show
  1. index.html +24 -13
index.html CHANGED
@@ -33,6 +33,7 @@ body{margin:0;color:var(--ink);font:15px/1.45 "Segoe UI",system-ui,-apple-system
33
  @keyframes mq{from{transform:translateX(0)}to{transform:translateX(-50%)}}
34
  @media(prefers-reduced-motion:reduce){.tick span,.live i{animation:none}}
35
  main{max-width:1180px;margin:0 auto;padding:16px 16px 40px}
 
36
  .ctl{display:flex;flex-wrap:wrap;gap:10px;margin:4px 0 16px}
37
  .ctl label{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);display:flex;flex-direction:column;gap:4px;font-weight:700}
38
  select{font:inherit;font-weight:600;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);max-width:100%}
@@ -70,6 +71,8 @@ svg{width:100%;height:auto;display:block}.lg{display:flex;flex-wrap:wrap;gap:6px
70
  <button type="button" class="mx" id="mclose" aria-label="Close">×</button></div>
71
  <div class="mbody"><div id="lmap"></div><iframe id="wfr" title="Live weather map" hidden></iframe></div>
72
  <div class="mft" id="mft"></div></div></div>
 
 
73
  <main>
74
  <noscript><h1>Global Weather Model Leaderboard</h1><p>Daily accuracy ranking of global weather forecast models, AI and physics: Darwin-Prediction-Weather, ECMWF AIFS, ECMWF IFS, GFS, AIGFS, ICON, JMA, GEM and MET Norway, scored against ERA5 for 24 world cities since 2026-01-01, with a 10-day outlook. 2026 year to date, RMSE of daily maximum temperature in °C at 1, 3, 5 and 7 days ahead: Darwin-Prediction-Weather (VIDRAFT, AI) 0.84, 1.17, 1.59, 2.09. ECMWF AIFS (AI) 1.35, 1.59, 1.90, 2.42. ECMWF IFS (Physics) 1.44, 1.77, 2.21, 2.85. Data: <a href="https://huggingface.co/datasets/FINAL-Bench/Weather-Model-Forecast-Archive">Weather-Model-Forecast-Archive</a>. Contact: vidraft@mail.vidraft.net</p></noscript>
75
  <div class="ctl">
@@ -88,7 +91,7 @@ svg{width:100%;height:auto;display:block}.lg{display:flex;flex-wrap:wrap;gap:6px
88
  <div class="pn"><h2>Error by forecast day</h2><div id="curve"></div><div class="lg" id="lg1"></div></div>
89
  <div class="pn"><h2>Most accurate model by city</h2><div id="map"></div><div class="lg" id="lg2"></div></div>
90
  </div>
91
- <div class="pn" style="margin-top:14px"><h2 id="h4">Results and 10-day outlook</h2>
92
  <div class="ctl" style="margin:0 0 12px"><label>Outlook city<select id="ocity"></select></label></div>
93
  <div class="ol" id="otiles"></div><div class="tw"><table class="ot" id="otab"></table></div><p class="note" id="onote" style="margin-top:10px"></p></div>
94
  <div class="pn" style="margin-top:14px"><h2 id="h3">Monthly champions 2026</h2><div class="mo" id="mo"></div></div>
@@ -96,7 +99,7 @@ svg{width:100%;height:auto;display:block}.lg{display:flex;flex-wrap:wrap;gap:6px
96
  <h3 class="h3">By period and forecast day, 24 cities</h3><div class="tw"><table class="wt" id="wper"></table></div>
97
  <h3 class="h3" style="margin-top:16px">By city and forecast day, 2026 year to date</h3><div class="tw"><table class="wt" id="wcity"></table></div><div class="lg" id="lg3"></div></div>
98
  <div class="pn" style="margin-top:14px"><h2>How it is scored</h2>
99
- <p class="note">Every day each model's forecast is recorded for up to 10 days ahead. When the observed value for a day is published, every forecast made for that day is scored and the cumulative record is updated, once a day. A forecast issued N days earlier is compared with the ERA5 daily maximum 2 m temperature for the same UTC day at each city. Lower RMSE ranks higher. Models are compared on the same cases: days and cities where every listed model issued a forecast. Type shows how a model works: <b>AI</b> models learn the forecast from data, <b>Physics (Supercomputer)</b> models solve the equations of the atmosphere on supercomputers. ERA5 is published about five days after the fact, so the latest verification date trails today. Forecasts 1 to 7 days ahead are scored from 2026-01-01; forecasts 8 to 10 days ahead are archived from 2026-11-13 and enter the ranking as their target days are verified. <b>Darwin-Prediction-Weather</b> is VIDRAFT's fusion engine: local bias correction plus skill-weighted fusion of the public models, fitted only on days before the one being scored. It is ranked on exactly the same cases as the public models. In the 10-day outlook the fusion for days beyond the longest scored lead uses that lead's calibration.</p></div>
100
  <footer>Full daily record since 2026-01-01 (every model forecast and the ERA5 value): <a href="https://huggingface.co/datasets/FINAL-Bench/Weather-Model-Forecast-Archive">Weather-Model-Forecast-Archive</a>. Weather data by <a href="https://open-meteo.com/">Open-Meteo.com</a> (CC BY 4.0). Verification: ERA5, Copernicus Climate Change Service. Model sources: ECMWF, NOAA NCEP, DWD, JMA, ECCC, MET Norway. Engine: <a href="https://huggingface.co/FINAL-Bench/Darwin-Prediction-Weather">Darwin-Prediction-Weather</a>. Leaderboard and compiled record: CC BY-NC 4.0, non-commercial use only. Contact: <a href="mailto:vidraft@mail.vidraft.net">vidraft@mail.vidraft.net</a></footer>
101
  </main><script>
102
  const CITIES={"Seoul":[37.57,126.98],"Busan":[35.18,129.08],"Tokyo":[35.68,139.69],"Beijing":[39.90,116.40],"Singapore":[1.35,103.82],"Delhi":[28.61,77.21],"Dubai":[25.20,55.27],"Moscow":[55.76,37.62],"Berlin":[52.52,13.40],"London":[51.51,-0.13],"Paris":[48.86,2.35],"Madrid":[40.42,-3.70],"Cairo":[30.04,31.24],"Nairobi":[-1.29,36.82],"Johannesburg":[-26.20,28.05],"New York":[40.71,-74.01],"Chicago":[41.88,-87.63],"Los Angeles":[34.05,-118.24],"Mexico City":[19.43,-99.13],"Sao Paulo":[-23.55,-46.63],"Buenos Aires":[-34.60,-58.38],"Sydney":[-33.87,151.21],"Anchorage":[61.22,-149.90],"Reykjavik":[64.15,-21.94]};
@@ -113,7 +116,15 @@ const end=new Date(Date.now()-LAG*864e5),start=new Date(end-(DAYS-1)*864e5);
113
  const lat=NAMES.map(c=>CITIES[c][0]).join(","),lon=NAMES.map(c=>CITIES[c][1]).join(",");
114
  async function get(u){for(let k=0;k<3;k++){try{const r=await fetch(u);if(r.ok)return await r.json();}catch(e){}await new Promise(r=>setTimeout(r,1500*(k+1)));}throw new Error("fetch failed")}
115
  function dmax(h){const o=[];for(let d=0;d+24<=h.length;d+=24){let m=-1e9,n=0;for(let i=d;i<d+24;i++){const v=h[i];if(v!==null&&v!==undefined){n++;if(v>m)m=v}}o.push(n>=20?m:null)}return o}
116
- let O,F={},dates=[],gen="",OL=null;
 
 
 
 
 
 
 
 
117
  async function load(){
118
  try{const r=await fetch("data.json?d="+iso(new Date()));if(r.ok){const j=await r.json();if(j.dates&&j.dates.length>MIN+1){O=j.O;F=j.F;dates=j.dates;gen=j.generated||"";if(j.leads)LEADS=j.leads;try{const o=await fetch("outlook.json?d="+iso(new Date()));if(o.ok)OL=await o.json()}catch(e){}return}}}catch(e){}
119
  const q=`latitude=${lat}&longitude=${lon}&start_date=${iso(start)}&end_date=${iso(end)}&timezone=UTC`;
@@ -182,24 +193,24 @@ function render(){
182
  const D=+$("lead").value,li=LEADS.indexOf(D),per=$("period").value,ci=+$("city").value,t1=$("date").value==="latest"?dates.length-1:dates.indexOf($("date").value),n=per==="day"?1:(per==="ytd"?9999:+per),t0=Math.max(MIN,t1-n+1);
183
  const R=run(li,t0,t1,ci),rows=R.rows,best=R.pub.length?R.pub[0].r:NaN,where=ci<0?"24 cities":NAMES[ci],span=t0===t1?dates[t1]:dates[t0]+" to "+dates[t1];
184
  $("stamp").innerHTML=`Verified through <b>${dates[dates.length-1]}</b> (ERA5)${gen?"<br>Updated "+gen:""}`;
185
- $("h1").textContent=`Most accurate model: ${D}-day forecast, ${where}`;$("h2").textContent=`Accuracy board: ${D}-day forecast, ${span}, ${where}`;
186
  if(!rows.length){const e="<p class='note'>No verified cases yet for this selection.</p>";$("status").textContent="";$("champ").innerHTML=e;$("dwc").innerHTML=e;$("bars").innerHTML=e;$("tick").textContent=`${D}-day forecasts are being archived and will appear here as their target days are verified.`}
187
  else{
188
  $("status").textContent=`All models scored as issued, on the cases every listed model covers.${R.OUT.length?" Not listed for this selection (insufficient coverage): "+R.OUT.map(m=>MODELS[m][0]).join(", ")+".":""}${rows[0].n<30?" Small sample: results for a single day or city vary widely.":""}`;
189
  const c=rows[0],M=inf(c.m);
190
- $("champ").innerHTML=`<div class="champ"><div class="medal">1</div><div><div class="cn">${lk(c.m)}</div><div class="sub" style="margin:6px 0">${flag(M[3],M[4])}${M[1]} &nbsp; ${tyb(c.m)}</div><div class="big">${c.r.toFixed(2)}<small>°C RMSE</small></div>${rows[1]&&c.r<rows[1].r?`<div class="gain" style="margin:8px 0 6px">${((1-c.r/rows[1].r)*100).toFixed(1)}% lower error than no. 2</div>`:""}<div class="sub">${c.n.toLocaleString()} verified forecasts, ${span}</div></div></div>`+
191
- `<div class="run">${rows.slice(1,3).map(x=>`<div class="rn"><div class="k">NO. ${x.rk}</div><div class="v">${lk(x.m)}</div><div class="sub">${flag(inf(x.m)[3],inf(x.m)[4])}${x.r.toFixed(2)} °C</div></div>`).join("")}</div>`;
192
- const bAI=rows.find(x=>inf(x.m)[2]==="AI"),bPH=rows.find(x=>inf(x.m)[2]!=="AI"),vs=(x,lab)=>x?`<div class="rn" style="margin-bottom:10px"><div class="k">BEST ${lab} · NO. ${x.rk} OVERALL</div><div class="v">${lk(x.m)}</div><div class="sub">${flag(inf(x.m)[3],inf(x.m)[4])}${inf(x.m)[1]} &nbsp; <b style="color:#fff;font-size:18px">${x.r.toFixed(2)} °C</b></div></div>`:"";
193
  $("dwc").innerHTML=vs(bAI,"AI")+vs(bPH,"PHYSICS (SUPERCOMPUTER)")+(bAI&&bPH?`<div class="gain">${bAI.r<bPH.r?"AI leads by "+((1-bAI.r/bPH.r)*100).toFixed(1)+"%":"Physics leads by "+((1-bPH.r/bAI.r)*100).toFixed(1)+"%"}</div><div class="sub" style="margin-top:10px">${rows.filter(x=>inf(x.m)[2]==="AI").length} AI models and ${rows.filter(x=>inf(x.m)[2]!=="AI").length} physics models on this board</div>`:"");
194
  const mx=Math.max(...rows.map(x=>x.r));let h="";
195
  rows.forEach(x=>{const M=inf(x.m),d=x.m==="darwin";h+=`<div class="bar ${x.rk===1?"f":""} ${d?"dwr":""}"><div class="rk">${x.rk}</div><div class="nm">${lk(x.m)}<span class="d">${flag(M[3],M[4])}${M[1]} &nbsp; ${tyb(x.m)}${d?" &nbsp; fusion":""}</span></div><div class="trk"><i style="width:${x.r/mx*100}%;background:${d?"linear-gradient(90deg,#35d0ff,#9be9ff)":M[5]}"></i></div><div class="val">${x.r.toFixed(2)}</div></div>`});
196
  $("bars").innerHTML=h;
197
- const tk=`<b>${D}-DAY FORECAST</b> · ${span} · ${where} &nbsp;&nbsp;`+rows.map(x=>x.m==="darwin"?`<em>#${x.rk}</em> <b>DARWIN-PREDICTION-WEATHER ${x.r.toFixed(2)}°C</b>`:`<em>#${x.rk}</em> ${MODELS[x.m][0]} ${x.r.toFixed(2)}°C`).join(" &nbsp;•&nbsp; ")+" &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
198
  $("tick").innerHTML=tk+tk;
199
  }
200
  // champions at a glance (independent of the period and city selectors)
201
  const last=dates.length-1,SH={darwin:"Darwin",ecmwf_ifs025:"ECMWF IFS",ecmwf_aifs025_single:"ECMWF AIFS",gfs_seamless:"GFS",ncep_aigfs025:"AIGFS",icon_seamless:"ICON",jma_seamless:"JMA",gem_seamless:"GEM",metno_seamless:"MET Nordic"},seen={};
202
- const wc=(m,v)=>{if(!m)return '<td class="e">–</td>';seen[m]=1;const M=inf(m);return `<td class="w ${m==="darwin"?"dwn":""}" style="border-left-color:${M[5]}"><div class="wn">${flag(M[3],M[4])}${lk(m,SH[m])}</div><div class="wm"><span class="ty ${M[2]==="AI"?"ai":"ph"}">${M[2]==="AI"?"AI":"Physics"}</span><b>${v.toFixed(2)}°</b></div></td>`},LA=LEADS.map((L,i)=>i).filter(i=>MK.some(m=>F[m]&&F[m][i]&&F[m][i][0].some(v=>v!==null&&v!==undefined)));
203
  const hd="<tr><th></th>"+LA.map(i=>`<th>${LEADS[i]}-day</th>`).join("")+"</tr>";
204
  $("wper").innerHTML=hd+[["Latest day",1],["Last 7 days",7],["Last 30 days",30],["Last 90 days",90],["2026 year to date",9999]].map(([nm,n])=>`<tr><td>${nm}</td>${LA.map(i=>{const r=run(i,Math.max(MIN,last-n+1),last,-1).rows[0];return r?wc(r.m,r.r):wc(null)}).join("")}</tr>`).join("");
205
  $("wcity").innerHTML=hd+NAMES.map((c,ci2)=>`<tr><td>${c}</td>${LA.map(i=>{const Y=run(i,MIN,last,-1);let bm=null,bv=1e9;Y.rows.forEach(x=>{const v=grp(Y.A,x.m,"c"+ci2,30);if(fin(v)&&v<bv){bv=v;bm=x.m}});return wc(bm,bv)}).join("")}</tr>`).join("");
@@ -218,12 +229,12 @@ function render(){
218
  for(let l=-150;l<=150;l+=30)ms+=`<line x1="${px(l)}" x2="${px(l)}" y1="0" y2="${mh}" stroke="#16335f"/>`;for(let l=60;l>=-30;l-=30)ms+=`<line x1="0" x2="${mw}" y1="${py(l)}" y2="${py(l)}" stroke="${l===0?"#2a5596":"#16335f"}"/>`;
219
  ms+=`<text x="4" y="${py(0)-4}" fill="#5f7fb3" font-size="9">EQUATOR</text>`;const used={};
220
  NAMES.forEach((c,i)=>{let bm=null,bv=1e9;RA.rows.forEach(x=>{const v=grp(RA.A,x.m,"c"+i,Math.min(5,t1-t0+1));if(fin(v)&&v<bv){bv=v;bm=x.m}});if(!bm)return;used[bm]=1;const x=px(CITIES[c][1]),y=py(CITIES[c][0]),left=x>mw-90;
221
- ms+=`<circle cx="${x}" cy="${y}" r="6.5" fill="${inf(bm)[5]}" stroke="#061226" stroke-width="1.5"><title>${c}: ${inf(bm)[0]} ${bv.toFixed(2)} °C</title></circle><text x="${left?x-9:x+9}" y="${y+3.5}" fill="#dbe7fb" font-size="9.5" font-weight="600" text-anchor="${left?"end":"start"}">${c}</text>`});
222
  $("map").innerHTML=ms+"</svg>";$("lg2").innerHTML=["darwin"].concat(MK).filter(m=>used[m]).map(m=>`<span><i style="background:${inf(m)[5]}"></i>${inf(m)[0]}</span>`).join("")||"No verified cases yet.";
223
  // monthly champions over the whole record for this lead
224
  const RY=run(li,MIN,dates.length-1,-1),months=[...new Set(dates.slice(MIN).map(d=>d.slice(0,7)))];$("h3").textContent=`Monthly champions 2026: ${D}-day forecast, 24 cities`;
225
  $("mo").innerHTML=months.map(mo=>{let bm=null,bv=1e9;RY.rows.forEach(x=>{const v=grp(RY.A,x.m,mo,100);if(fin(v)&&v<bv){bv=v;bm=x.m}});const nm=new Date(mo+"-15").toLocaleString("en",{month:"short"}).toUpperCase();
226
- return bm?`<div class="mc" style="border-top-color:${inf(bm)[5]}"><div class="m">${nm}</div><div class="w">${inf(bm)[0]}</div><div class="e">${bv.toFixed(2)} °C</div></div>`:""}).join("")||"<p class='note'>No verified months yet for this lead.</p>";
227
  }
228
  // LIVE EARTH: satellite (NASA GIBS) + rain radar (RainViewer) + city pins on our own map, and live wind / rain / temperature maps
229
  let LM=null;const G="https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/";
@@ -234,7 +245,7 @@ async function liveMap(){if(LM){setTimeout(()=>LM.invalidateSize(),60);$("mft").
234
  const li=LEADS.indexOf(+$("lead").value),li2=li>=0&&li<7?li:0,Y=run(li2,MIN,dates.length-1,-1);
235
  NAMES.forEach((c,i)=>{let bm=null,bv=1e9;Y.rows.forEach(x=>{const v=grp(Y.A,x.m,"c"+i,30);if(fin(v)&&v<bv){bv=v;bm=x.m}});const o=OL&&OL.models?ocalc(i):null,col=bm?inf(bm)[5]:"#35d0ff",M=bm?inf(bm):null;
236
  const mk=L.marker(CITIES[c],{icon:L.divIcon({className:"",html:`<div class="cpin2" style="border-color:${col}"><b>${c}</b>${M?`<span style="color:${col}">${SHN[bm]}</span>`:""}</div>`,iconSize:[0,0]})}).addTo(LM);
237
- mk.bindPopup(`<b style="font-size:15px">${c}</b>${M?`<br>No. 1 in 2026: <b>${M[0]}</b> (${bv.toFixed(2)} °C, ${LEADS[li2]}-day forecast)`:""}${o?`<br>Darwin forecast, next days: ${o.fus.slice(0,5).map((v,k)=>fin(v)?`<b>${v.toFixed(0)}°</b>`:"–").join(" · ")}`:""}`)});
238
  LM._ft=`The most accurate model in each city in 2026, over live rain radar${LM._rv?" ("+LM._rv+")":""}. Click a city for its forecast. We acknowledge the use of imagery provided by services from NASA's Global Imagery Browse Services (GIBS), part of NASA's Earth Science Data and Information System (ESDIS). Weather data by <a href="https://www.rainviewer.com/" target="_blank" rel="noopener">RainViewer</a>.`;$("mft").innerHTML=LM._ft}
239
  function liveTab(t){document.querySelectorAll(".mt").forEach(b=>b.classList.toggle("on",b.dataset.t===t));const f=$("wfr");
240
  if(t==="sat"){f.hidden=true;$("lmap").hidden=false;liveMap()}
@@ -242,7 +253,7 @@ function liveTab(t){document.querySelectorAll(".mt").forEach(b=>b.classList.togg
242
  $("mft").innerHTML='Live map by <a href="https://www.windy.com/" target="_blank" rel="noopener">Windy.com</a>.'}}
243
  function liveOpen(){$("mdl").hidden=false;document.body.style.overflow="hidden";liveTab("wind")}
244
  function liveClose(){$("mdl").hidden=true;document.body.style.overflow="";$("wfr").src="about:blank"}
245
- load().then(()=>{$("lebtn").onclick=liveOpen;$("mclose").onclick=liveClose;$("mdl").onclick=e=>{if(e.target===$("mdl"))liveClose()};document.addEventListener("keydown",e=>{if(e.key==="Escape"&&!$("mdl").hidden)liveClose()});document.querySelectorAll(".mt").forEach(b=>b.onclick=()=>liveTab(b.dataset.t));LEADS.forEach(d=>$("lead").insertAdjacentHTML("beforeend",`<option value="${d}"${d===7?" selected":""}>${d} day${d>1?"s":""} ahead</option>`));NAMES.forEach((c,i)=>$("city").insertAdjacentHTML("beforeend",`<option value="${i}">${c}</option>`));
246
  $("date").insertAdjacentHTML("beforeend",`<option value="latest" selected>Today · latest (${dates[dates.length-1]})</option>`);for(let i=dates.length-1;i>=MIN;i--)$("date").insertAdjacentHTML("beforeend",`<option>${dates[i]}</option>`);
247
  $("ocity").insertAdjacentHTML("beforeend",`<option value="-1" selected>All 24 cities</option>`);NAMES.forEach((c,i)=>$("ocity").insertAdjacentHTML("beforeend",`<option value="${i}">${c}</option>`));$("ocity").onchange=outlook;
248
  ["lead","period","date","city"].forEach(id=>$(id).onchange=()=>{render();if(id==="city")$("ocity").value=$("city").value;outlook()});render();outlook()}).catch(e=>{$("status").textContent="Could not load data. Please reload in a moment.";console.error(e)});
 
33
  @keyframes mq{from{transform:translateX(0)}to{transform:translateX(-50%)}}
34
  @media(prefers-reduced-motion:reduce){.tick span,.live i{animation:none}}
35
  main{max-width:1180px;margin:0 auto;padding:16px 16px 40px}
36
+ .etabs{display:flex;gap:0;background:#07162f;border-bottom:1px solid var(--line);padding:0 12px;overflow-x:auto}.et{font:inherit;font-weight:800;font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--mut);background:none;border:0;border-bottom:3px solid transparent;padding:12px 18px;cursor:pointer;white-space:nowrap}.et:hover{color:var(--ink)}.et.on{color:var(--cy);border-bottom-color:var(--cy)}.et small{display:block;font-size:9px;letter-spacing:.08em;color:var(--mut);font-weight:700}
37
  .ctl{display:flex;flex-wrap:wrap;gap:10px;margin:4px 0 16px}
38
  .ctl label{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);display:flex;flex-direction:column;gap:4px;font-weight:700}
39
  select{font:inherit;font-weight:600;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel);color:var(--ink);max-width:100%}
 
71
  <button type="button" class="mx" id="mclose" aria-label="Close">×</button></div>
72
  <div class="mbody"><div id="lmap"></div><iframe id="wfr" title="Live weather map" hidden></iframe></div>
73
  <div class="mft" id="mft"></div></div></div>
74
+ <div class="etabs" id="etabs" role="tablist" aria-label="Weather element">
75
+ <button type="button" class="et on" data-e="tmax">Max temperature</button><button type="button" class="et" data-e="tmin">Min temperature</button><button type="button" class="et" data-e="wind">Wind</button><button type="button" class="et" data-e="precip">Precipitation</button></div>
76
  <main>
77
  <noscript><h1>Global Weather Model Leaderboard</h1><p>Daily accuracy ranking of global weather forecast models, AI and physics: Darwin-Prediction-Weather, ECMWF AIFS, ECMWF IFS, GFS, AIGFS, ICON, JMA, GEM and MET Norway, scored against ERA5 for 24 world cities since 2026-01-01, with a 10-day outlook. 2026 year to date, RMSE of daily maximum temperature in °C at 1, 3, 5 and 7 days ahead: Darwin-Prediction-Weather (VIDRAFT, AI) 0.84, 1.17, 1.59, 2.09. ECMWF AIFS (AI) 1.35, 1.59, 1.90, 2.42. ECMWF IFS (Physics) 1.44, 1.77, 2.21, 2.85. Data: <a href="https://huggingface.co/datasets/FINAL-Bench/Weather-Model-Forecast-Archive">Weather-Model-Forecast-Archive</a>. Contact: vidraft@mail.vidraft.net</p></noscript>
78
  <div class="ctl">
 
91
  <div class="pn"><h2>Error by forecast day</h2><div id="curve"></div><div class="lg" id="lg1"></div></div>
92
  <div class="pn"><h2>Most accurate model by city</h2><div id="map"></div><div class="lg" id="lg2"></div></div>
93
  </div>
94
+ <div class="pn" style="margin-top:14px" id="opanel"><h2 id="h4">Results and 10-day outlook</h2>
95
  <div class="ctl" style="margin:0 0 12px"><label>Outlook city<select id="ocity"></select></label></div>
96
  <div class="ol" id="otiles"></div><div class="tw"><table class="ot" id="otab"></table></div><p class="note" id="onote" style="margin-top:10px"></p></div>
97
  <div class="pn" style="margin-top:14px"><h2 id="h3">Monthly champions 2026</h2><div class="mo" id="mo"></div></div>
 
99
  <h3 class="h3">By period and forecast day, 24 cities</h3><div class="tw"><table class="wt" id="wper"></table></div>
100
  <h3 class="h3" style="margin-top:16px">By city and forecast day, 2026 year to date</h3><div class="tw"><table class="wt" id="wcity"></table></div><div class="lg" id="lg3"></div></div>
101
  <div class="pn" style="margin-top:14px"><h2>How it is scored</h2>
102
+ <p class="note">Every day each model's forecast is recorded for up to 10 days ahead. When the observed value for a day is published, every forecast made for that day is scored and the cumulative record is updated, once a day. A forecast issued N days earlier is compared with the ERA5 value for the same UTC day at each city. Each element has its own tab and its own ranking: daily maximum temperature, daily minimum temperature, daily maximum wind speed and daily precipitation total. Lower RMSE ranks higher. Models are compared on the same cases: days and cities where every listed model issued a forecast. Type shows how a model works: <b>AI</b> models learn the forecast from data, <b>Physics (Supercomputer)</b> models solve the equations of the atmosphere on supercomputers. ERA5 is published about five days after the fact, so the latest verification date trails today. Forecasts 1 to 7 days ahead are scored from 2026-01-01; forecasts 8 to 10 days ahead are archived from 2026-11-13 and enter the ranking as their target days are verified. <b>Darwin-Prediction-Weather</b> is VIDRAFT's fusion engine: local bias correction plus skill-weighted fusion of the public models, fitted only on days before the one being scored. It is ranked on exactly the same cases as the public models. In the 10-day outlook the fusion for days beyond the longest scored lead uses that lead's calibration.</p></div>
103
  <footer>Full daily record since 2026-01-01 (every model forecast and the ERA5 value): <a href="https://huggingface.co/datasets/FINAL-Bench/Weather-Model-Forecast-Archive">Weather-Model-Forecast-Archive</a>. Weather data by <a href="https://open-meteo.com/">Open-Meteo.com</a> (CC BY 4.0). Verification: ERA5, Copernicus Climate Change Service. Model sources: ECMWF, NOAA NCEP, DWD, JMA, ECCC, MET Norway. Engine: <a href="https://huggingface.co/FINAL-Bench/Darwin-Prediction-Weather">Darwin-Prediction-Weather</a>. Leaderboard and compiled record: CC BY-NC 4.0, non-commercial use only. Contact: <a href="mailto:vidraft@mail.vidraft.net">vidraft@mail.vidraft.net</a></footer>
104
  </main><script>
105
  const CITIES={"Seoul":[37.57,126.98],"Busan":[35.18,129.08],"Tokyo":[35.68,139.69],"Beijing":[39.90,116.40],"Singapore":[1.35,103.82],"Delhi":[28.61,77.21],"Dubai":[25.20,55.27],"Moscow":[55.76,37.62],"Berlin":[52.52,13.40],"London":[51.51,-0.13],"Paris":[48.86,2.35],"Madrid":[40.42,-3.70],"Cairo":[30.04,31.24],"Nairobi":[-1.29,36.82],"Johannesburg":[-26.20,28.05],"New York":[40.71,-74.01],"Chicago":[41.88,-87.63],"Los Angeles":[34.05,-118.24],"Mexico City":[19.43,-99.13],"Sao Paulo":[-23.55,-46.63],"Buenos Aires":[-34.60,-58.38],"Sydney":[-33.87,151.21],"Anchorage":[61.22,-149.90],"Reykjavik":[64.15,-21.94]};
 
116
  const lat=NAMES.map(c=>CITIES[c][0]).join(","),lon=NAMES.map(c=>CITIES[c][1]).join(",");
117
  async function get(u){for(let k=0;k<3;k++){try{const r=await fetch(u);if(r.ok)return await r.json();}catch(e){}await new Promise(r=>setTimeout(r,1500*(k+1)));}throw new Error("fetch failed")}
118
  function dmax(h){const o=[];for(let d=0;d+24<=h.length;d+=24){let m=-1e9,n=0;for(let i=d;i<d+24;i++){const v=h[i];if(v!==null&&v!==undefined){n++;if(v>m)m=v}}o.push(n>=20?m:null)}return o}
119
+ let O,F={},dates=[],gen="",OL=null,U="°C",EK="tmax";
120
+ const ELS={tmax:["data.json","daily maximum 2 m temperature","°C"],tmin:["data_tmin.json","daily minimum 2 m temperature","°C"],wind:["data_wind.json","daily maximum 10 m wind speed","km/h"],precip:["data_precip.json","daily precipitation total","mm"]},ECACHE={};
121
+ async function setEl(k){const b=[...document.querySelectorAll(".et")];let j=ECACHE[k];
122
+ if(!j){try{const r=await fetch(ELS[k][0]+"?d="+iso(new Date()));if(r.ok)j=await r.json()}catch(e){}if(j&&j.dates&&j.dates.length>MIN+1)ECACHE[k]=j;else j=null}
123
+ if(!j){const e=b.find(x=>x.dataset.e===k);if(e&&!e.querySelector("small"))e.insertAdjacentHTML("beforeend","<small>record being built</small>");$("status").textContent=`The ${ELS[k][1]} record since 2026-01-01 is being built. Its ranking opens as soon as the record is complete.`;return}
124
+ EK=k;U=ELS[k][2];O=j.O;F=j.F;dates=j.dates;gen=j.generated||"";if(j.leads)LEADS=j.leads;for(const x in memo)delete memo[x];
125
+ b.forEach(x=>x.classList.toggle("on",x.dataset.e===k));$("opanel").hidden=k!=="tmax";
126
+ const dsel=$("date");dsel.innerHTML=`<option value="latest" selected>Today · latest (${dates[dates.length-1]})</option>`;for(let i=dates.length-1;i>=MIN;i--)dsel.insertAdjacentHTML("beforeend",`<option>${dates[i]}</option>`);
127
+ render();if(k==="tmax")outlook()}
128
  async function load(){
129
  try{const r=await fetch("data.json?d="+iso(new Date()));if(r.ok){const j=await r.json();if(j.dates&&j.dates.length>MIN+1){O=j.O;F=j.F;dates=j.dates;gen=j.generated||"";if(j.leads)LEADS=j.leads;try{const o=await fetch("outlook.json?d="+iso(new Date()));if(o.ok)OL=await o.json()}catch(e){}return}}}catch(e){}
130
  const q=`latitude=${lat}&longitude=${lon}&start_date=${iso(start)}&end_date=${iso(end)}&timezone=UTC`;
 
193
  const D=+$("lead").value,li=LEADS.indexOf(D),per=$("period").value,ci=+$("city").value,t1=$("date").value==="latest"?dates.length-1:dates.indexOf($("date").value),n=per==="day"?1:(per==="ytd"?9999:+per),t0=Math.max(MIN,t1-n+1);
194
  const R=run(li,t0,t1,ci),rows=R.rows,best=R.pub.length?R.pub[0].r:NaN,where=ci<0?"24 cities":NAMES[ci],span=t0===t1?dates[t1]:dates[t0]+" to "+dates[t1];
195
  $("stamp").innerHTML=`Verified through <b>${dates[dates.length-1]}</b> (ERA5)${gen?"<br>Updated "+gen:""}`;
196
+ $("h1").textContent=`Most accurate model: ${ELS[EK][1]}, ${D}-day forecast, ${where}`;$("h2").textContent=`Accuracy board: ${D}-day forecast, ${span}, ${where}`;
197
  if(!rows.length){const e="<p class='note'>No verified cases yet for this selection.</p>";$("status").textContent="";$("champ").innerHTML=e;$("dwc").innerHTML=e;$("bars").innerHTML=e;$("tick").textContent=`${D}-day forecasts are being archived and will appear here as their target days are verified.`}
198
  else{
199
  $("status").textContent=`All models scored as issued, on the cases every listed model covers.${R.OUT.length?" Not listed for this selection (insufficient coverage): "+R.OUT.map(m=>MODELS[m][0]).join(", ")+".":""}${rows[0].n<30?" Small sample: results for a single day or city vary widely.":""}`;
200
  const c=rows[0],M=inf(c.m);
201
+ $("champ").innerHTML=`<div class="champ"><div class="medal">1</div><div><div class="cn">${lk(c.m)}</div><div class="sub" style="margin:6px 0">${flag(M[3],M[4])}${M[1]} &nbsp; ${tyb(c.m)}</div><div class="big">${c.r.toFixed(2)}<small>${U} RMSE</small></div>${rows[1]&&c.r<rows[1].r?`<div class="gain" style="margin:8px 0 6px">${((1-c.r/rows[1].r)*100).toFixed(1)}% lower error than no. 2</div>`:""}<div class="sub">${c.n.toLocaleString()} verified forecasts, ${span}</div></div></div>`+
202
+ `<div class="run">${rows.slice(1,3).map(x=>`<div class="rn"><div class="k">NO. ${x.rk}</div><div class="v">${lk(x.m)}</div><div class="sub">${flag(inf(x.m)[3],inf(x.m)[4])}${x.r.toFixed(2)} ${U}</div></div>`).join("")}</div>`;
203
+ const bAI=rows.find(x=>inf(x.m)[2]==="AI"),bPH=rows.find(x=>inf(x.m)[2]!=="AI"),vs=(x,lab)=>x?`<div class="rn" style="margin-bottom:10px"><div class="k">BEST ${lab} · NO. ${x.rk} OVERALL</div><div class="v">${lk(x.m)}</div><div class="sub">${flag(inf(x.m)[3],inf(x.m)[4])}${inf(x.m)[1]} &nbsp; <b style="color:#fff;font-size:18px">${x.r.toFixed(2)} ${U}</b></div></div>`:"";
204
  $("dwc").innerHTML=vs(bAI,"AI")+vs(bPH,"PHYSICS (SUPERCOMPUTER)")+(bAI&&bPH?`<div class="gain">${bAI.r<bPH.r?"AI leads by "+((1-bAI.r/bPH.r)*100).toFixed(1)+"%":"Physics leads by "+((1-bPH.r/bAI.r)*100).toFixed(1)+"%"}</div><div class="sub" style="margin-top:10px">${rows.filter(x=>inf(x.m)[2]==="AI").length} AI models and ${rows.filter(x=>inf(x.m)[2]!=="AI").length} physics models on this board</div>`:"");
205
  const mx=Math.max(...rows.map(x=>x.r));let h="";
206
  rows.forEach(x=>{const M=inf(x.m),d=x.m==="darwin";h+=`<div class="bar ${x.rk===1?"f":""} ${d?"dwr":""}"><div class="rk">${x.rk}</div><div class="nm">${lk(x.m)}<span class="d">${flag(M[3],M[4])}${M[1]} &nbsp; ${tyb(x.m)}${d?" &nbsp; fusion":""}</span></div><div class="trk"><i style="width:${x.r/mx*100}%;background:${d?"linear-gradient(90deg,#35d0ff,#9be9ff)":M[5]}"></i></div><div class="val">${x.r.toFixed(2)}</div></div>`});
207
  $("bars").innerHTML=h;
208
+ const tk=`<b>${D}-DAY FORECAST</b> · ${span} · ${where} &nbsp;&nbsp;`+rows.map(x=>x.m==="darwin"?`<em>#${x.rk}</em> <b>DARWIN-PREDICTION-WEATHER ${x.r.toFixed(2)} ${U}</b>`:`<em>#${x.rk}</em> ${MODELS[x.m][0]} ${x.r.toFixed(2)} ${U}`).join(" &nbsp;•&nbsp; ")+" &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
209
  $("tick").innerHTML=tk+tk;
210
  }
211
  // champions at a glance (independent of the period and city selectors)
212
  const last=dates.length-1,SH={darwin:"Darwin",ecmwf_ifs025:"ECMWF IFS",ecmwf_aifs025_single:"ECMWF AIFS",gfs_seamless:"GFS",ncep_aigfs025:"AIGFS",icon_seamless:"ICON",jma_seamless:"JMA",gem_seamless:"GEM",metno_seamless:"MET Nordic"},seen={};
213
+ const wc=(m,v)=>{if(!m)return '<td class="e">–</td>';seen[m]=1;const M=inf(m);return `<td class="w ${m==="darwin"?"dwn":""}" style="border-left-color:${M[5]}"><div class="wn">${flag(M[3],M[4])}${lk(m,SH[m])}</div><div class="wm"><span class="ty ${M[2]==="AI"?"ai":"ph"}">${M[2]==="AI"?"AI":"Physics"}</span><b>${v.toFixed(2)}${U==="°C"?"°":" "+U}</b></div></td>`},LA=LEADS.map((L,i)=>i).filter(i=>MK.some(m=>F[m]&&F[m][i]&&F[m][i][0].some(v=>v!==null&&v!==undefined)));
214
  const hd="<tr><th></th>"+LA.map(i=>`<th>${LEADS[i]}-day</th>`).join("")+"</tr>";
215
  $("wper").innerHTML=hd+[["Latest day",1],["Last 7 days",7],["Last 30 days",30],["Last 90 days",90],["2026 year to date",9999]].map(([nm,n])=>`<tr><td>${nm}</td>${LA.map(i=>{const r=run(i,Math.max(MIN,last-n+1),last,-1).rows[0];return r?wc(r.m,r.r):wc(null)}).join("")}</tr>`).join("");
216
  $("wcity").innerHTML=hd+NAMES.map((c,ci2)=>`<tr><td>${c}</td>${LA.map(i=>{const Y=run(i,MIN,last,-1);let bm=null,bv=1e9;Y.rows.forEach(x=>{const v=grp(Y.A,x.m,"c"+ci2,30);if(fin(v)&&v<bv){bv=v;bm=x.m}});return wc(bm,bv)}).join("")}</tr>`).join("");
 
229
  for(let l=-150;l<=150;l+=30)ms+=`<line x1="${px(l)}" x2="${px(l)}" y1="0" y2="${mh}" stroke="#16335f"/>`;for(let l=60;l>=-30;l-=30)ms+=`<line x1="0" x2="${mw}" y1="${py(l)}" y2="${py(l)}" stroke="${l===0?"#2a5596":"#16335f"}"/>`;
230
  ms+=`<text x="4" y="${py(0)-4}" fill="#5f7fb3" font-size="9">EQUATOR</text>`;const used={};
231
  NAMES.forEach((c,i)=>{let bm=null,bv=1e9;RA.rows.forEach(x=>{const v=grp(RA.A,x.m,"c"+i,Math.min(5,t1-t0+1));if(fin(v)&&v<bv){bv=v;bm=x.m}});if(!bm)return;used[bm]=1;const x=px(CITIES[c][1]),y=py(CITIES[c][0]),left=x>mw-90;
232
+ ms+=`<circle cx="${x}" cy="${y}" r="6.5" fill="${inf(bm)[5]}" stroke="#061226" stroke-width="1.5"><title>${c}: ${inf(bm)[0]} ${bv.toFixed(2)} ${U}</title></circle><text x="${left?x-9:x+9}" y="${y+3.5}" fill="#dbe7fb" font-size="9.5" font-weight="600" text-anchor="${left?"end":"start"}">${c}</text>`});
233
  $("map").innerHTML=ms+"</svg>";$("lg2").innerHTML=["darwin"].concat(MK).filter(m=>used[m]).map(m=>`<span><i style="background:${inf(m)[5]}"></i>${inf(m)[0]}</span>`).join("")||"No verified cases yet.";
234
  // monthly champions over the whole record for this lead
235
  const RY=run(li,MIN,dates.length-1,-1),months=[...new Set(dates.slice(MIN).map(d=>d.slice(0,7)))];$("h3").textContent=`Monthly champions 2026: ${D}-day forecast, 24 cities`;
236
  $("mo").innerHTML=months.map(mo=>{let bm=null,bv=1e9;RY.rows.forEach(x=>{const v=grp(RY.A,x.m,mo,100);if(fin(v)&&v<bv){bv=v;bm=x.m}});const nm=new Date(mo+"-15").toLocaleString("en",{month:"short"}).toUpperCase();
237
+ return bm?`<div class="mc" style="border-top-color:${inf(bm)[5]}"><div class="m">${nm}</div><div class="w">${inf(bm)[0]}</div><div class="e">${bv.toFixed(2)} ${U}</div></div>`:""}).join("")||"<p class='note'>No verified months yet for this lead.</p>";
238
  }
239
  // LIVE EARTH: satellite (NASA GIBS) + rain radar (RainViewer) + city pins on our own map, and live wind / rain / temperature maps
240
  let LM=null;const G="https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/";
 
245
  const li=LEADS.indexOf(+$("lead").value),li2=li>=0&&li<7?li:0,Y=run(li2,MIN,dates.length-1,-1);
246
  NAMES.forEach((c,i)=>{let bm=null,bv=1e9;Y.rows.forEach(x=>{const v=grp(Y.A,x.m,"c"+i,30);if(fin(v)&&v<bv){bv=v;bm=x.m}});const o=OL&&OL.models?ocalc(i):null,col=bm?inf(bm)[5]:"#35d0ff",M=bm?inf(bm):null;
247
  const mk=L.marker(CITIES[c],{icon:L.divIcon({className:"",html:`<div class="cpin2" style="border-color:${col}"><b>${c}</b>${M?`<span style="color:${col}">${SHN[bm]}</span>`:""}</div>`,iconSize:[0,0]})}).addTo(LM);
248
+ mk.bindPopup(`<b style="font-size:15px">${c}</b>${M?`<br>No. 1 in 2026: <b>${M[0]}</b> (${bv.toFixed(2)} ${U}, ${LEADS[li2]}-day forecast)`:""}${o?`<br>Darwin forecast, next days: ${o.fus.slice(0,5).map((v,k)=>fin(v)?`<b>${v.toFixed(0)}°</b>`:"–").join(" · ")}`:""}`)});
249
  LM._ft=`The most accurate model in each city in 2026, over live rain radar${LM._rv?" ("+LM._rv+")":""}. Click a city for its forecast. We acknowledge the use of imagery provided by services from NASA's Global Imagery Browse Services (GIBS), part of NASA's Earth Science Data and Information System (ESDIS). Weather data by <a href="https://www.rainviewer.com/" target="_blank" rel="noopener">RainViewer</a>.`;$("mft").innerHTML=LM._ft}
250
  function liveTab(t){document.querySelectorAll(".mt").forEach(b=>b.classList.toggle("on",b.dataset.t===t));const f=$("wfr");
251
  if(t==="sat"){f.hidden=true;$("lmap").hidden=false;liveMap()}
 
253
  $("mft").innerHTML='Live map by <a href="https://www.windy.com/" target="_blank" rel="noopener">Windy.com</a>.'}}
254
  function liveOpen(){$("mdl").hidden=false;document.body.style.overflow="hidden";liveTab("wind")}
255
  function liveClose(){$("mdl").hidden=true;document.body.style.overflow="";$("wfr").src="about:blank"}
256
+ load().then(()=>{ECACHE.tmax={O,F,dates,generated:gen,leads:LEADS};document.querySelectorAll(".et").forEach(b=>b.onclick=()=>setEl(b.dataset.e));$("lebtn").onclick=liveOpen;$("mclose").onclick=liveClose;$("mdl").onclick=e=>{if(e.target===$("mdl"))liveClose()};document.addEventListener("keydown",e=>{if(e.key==="Escape"&&!$("mdl").hidden)liveClose()});document.querySelectorAll(".mt").forEach(b=>b.onclick=()=>liveTab(b.dataset.t));LEADS.forEach(d=>$("lead").insertAdjacentHTML("beforeend",`<option value="${d}"${d===7?" selected":""}>${d} day${d>1?"s":""} ahead</option>`));NAMES.forEach((c,i)=>$("city").insertAdjacentHTML("beforeend",`<option value="${i}">${c}</option>`));
257
  $("date").insertAdjacentHTML("beforeend",`<option value="latest" selected>Today · latest (${dates[dates.length-1]})</option>`);for(let i=dates.length-1;i>=MIN;i--)$("date").insertAdjacentHTML("beforeend",`<option>${dates[i]}</option>`);
258
  $("ocity").insertAdjacentHTML("beforeend",`<option value="-1" selected>All 24 cities</option>`);NAMES.forEach((c,i)=>$("ocity").insertAdjacentHTML("beforeend",`<option value="${i}">${c}</option>`));$("ocity").onchange=outlook;
259
  ["lead","period","date","city"].forEach(id=>$(id).onchange=()=>{render();if(id==="city")$("ocity").value=$("city").value;outlook()});render();outlook()}).catch(e=>{$("status").textContent="Could not load data. Please reload in a moment.";console.error(e)});