Element tabs: max temperature, min temperature, wind, precipitation
Browse files- 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
|
| 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]} ${tyb(c.m)}</div><div class="big">${c.r.toFixed(2)}<small>
|
| 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)}
|
| 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]} <b style="color:#fff;font-size:18px">${x.r.toFixed(2)}
|
| 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]} ${tyb(x.m)}${d?" 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} `+rows.map(x=>x.m==="darwin"?`<em>#${x.rk}</em> <b>DARWIN-PREDICTION-WEATHER ${x.r.toFixed(2)}
|
| 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)}
|
| 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)}
|
| 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)}
|
| 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]} ${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]} <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]} ${tyb(x.m)}${d?" 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} `+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(" • ")+" ";
|
| 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)});
|