@@ -18,21 +18,6 @@ const runs = forecastData
1818 }))
1919 .sort ((a , b ) => String (b .created_at ).localeCompare (String (a .created_at )));
2020
21- const allBacktests = forecastData .flatMap ((data : any ) => data .backtest ?? []);
22-
23- const leaderboardMap = new Map <string , { total: number ; count: number }>();
24- for (const row of allBacktests ) {
25- const model = row .model ;
26- const smape = Number (row .smape );
27- if (! model || Number .isNaN (smape )) continue ;
28- const current = leaderboardMap .get (model ) ?? { total: 0 , count: 0 };
29- leaderboardMap .set (model , { total: current .total + smape , count: current .count + 1 });
30- }
31-
32- const leaderboard = Array .from (leaderboardMap .entries ())
33- .map (([model , stats ]) => ({ model , avgSmape: stats .total / stats .count , samples: stats .count }))
34- .sort ((a , b ) => a .avgSmape - b .avgSmape );
35-
3621
3722const defaultPayload = JSON .stringify ({
3823 start_datetime: ' 2026-01-01T00:00:00' ,
@@ -106,30 +91,16 @@ const defaultPayload = JSON.stringify({
10691 </div >
10792 </div >
10893 </section >
109-
110- { leaderboard .length > 0 && (
111- <section class = " mt-4 rounded-xl border border-slate-700 bg-slate-900/70 p-4 overflow-x-auto" >
112- <h2 class = " text-lg font-semibold mb-2" >Leaderboard (avg SMAPE)</h2 >
113- <table class = " min-w-[420px] text-sm w-full" >
114- <thead ><tr class = " text-slate-300" ><th class = " text-left p-2" >Rank</th ><th class = " text-left p-2" >Model</th ><th class = " text-left p-2" >Avg SMAPE</th ><th class = " text-left p-2" >Samples</th ></tr ></thead >
115- <tbody >
116- { leaderboard .map ((row , idx ) => (
117- <tr >
118- <td class = " p-2" >{ idx + 1 } </td ><td class = { ` p-2 ${idx === 0 ? ' text-emerald-400 font-semibold' : ' ' } ` } >{ row .model } </td ><td class = " p-2" >{ row .avgSmape .toFixed (4 )} </td ><td class = " p-2" >{ row .samples } </td >
119- </tr >
120- ))}
121- </tbody >
122- </table >
123- </section >
124- )}
125-
126- <section class =" mt-4 grid gap-3" >
127- { runs .map ((run ) => (
128- <article class = " rounded-xl border border-slate-700 bg-slate-900/70 p-4" >
129- <h3 class = " font-semibold text-sky-300" ><a href = { run .path } >{ run .slug ?? run .title } </a ></h3 >
130- <p class = " text-sm text-slate-400 mt-1" >{ run .backend } · { run .granularity } · horizon { run .horizon } · history { run .history_points } · forecast { run .forecast_points } </p >
131- </article >
132- ))}
94+ <section class =" mt-4 rounded-xl border border-slate-700 bg-slate-900/70 p-4" >
95+ <h2 class =" text-lg font-semibold mb-3" >All Runs</h2 >
96+ <div class =" grid gap-3" >
97+ { runs .map ((run ) => (
98+ <article class = " rounded-xl border border-slate-700 bg-slate-900/50 p-4" >
99+ <h3 class = " font-semibold text-sky-300" ><a href = { run .path } >{ run .slug ?? run .title } </a ></h3 >
100+ <p class = " text-sm text-slate-400 mt-1" >{ run .backend } · { run .granularity } · horizon { run .horizon } · history { run .history_points } · forecast { run .forecast_points } </p >
101+ </article >
102+ ))}
103+ </div >
133104 </section >
134105
135106 <script type =" module" >
@@ -159,7 +130,11 @@ const defaultPayload = JSON.stringify({
159130 statusListEl.innerHTML = runs.map((r) => {
160131 const emoji = r.status === 'completed' ? (r.conclusion === 'success' ? '✅' : '❌') : '⏳';
161132 const statusText = r.status === 'completed' ? (r.conclusion || 'completed') : (r.status || 'queued');
133+ const forecastHref = `/forecasts/${r.slug}`;
162134 const href = r.html_url || r.actions_url || 'https://github.com/bryanwhiting/weatherman/actions/workflows/forecast-request.yml';
135+ if (r.status === 'completed' && r.conclusion === 'success') {
136+ return `<div class="rounded border border-slate-700 bg-slate-900/50 p-2 text-xs text-slate-300">✅ <code>${r.slug}</code> — success · Your forecast is ready: <a href="${forecastHref}" class="text-emerald-300 underline">${forecastHref}</a>, or refresh this browser · <a href="${href}" target="_blank" rel="noreferrer" class="text-sky-300 underline">run</a></div>`;
137+ }
163138 return `<div class="rounded border border-slate-700 bg-slate-900/50 p-2 text-xs text-slate-300">${emoji} <code>${r.slug}</code> — ${statusText} · <a href="${href}" target="_blank" rel="noreferrer" class="text-sky-300 underline">open</a></div>`;
164139 }).join('');
165140 };
@@ -192,6 +167,9 @@ const defaultPayload = JSON.stringify({
192167 updated_at: rj.updated_at,
193168 });
194169 if (rj.status === 'completed') {
170+ if (rj.conclusion === 'success') {
171+ statusEl.innerHTML = `✅ Your forecast is ready: <a href="/forecasts/${slug}" class="text-emerald-300 underline">/forecasts/${slug}</a>, or refresh this browser`;
172+ }
195173 clearInterval(timer);
196174 activeTimers.delete(slug);
197175 }
0 commit comments