PageSourceSearch

https://jack-ki1-formula-1-predictor-2026.hf.space/static/js/analytics.js

js jack-ki1-formula-1-predictor-2026.hf.space collected 2026-10-04 16:58:38 UTC 11,246 bytes, 228 lines download raw bytes

1/**
2 * analytics.js — Accuracy / Model Tuning / Display & Preferences.
3 * Accuracy numbers come from engine/benchmark_suite.py (real, backtested).
4 * Tuning sliders write to F1.setTuning(), which dashboard.js reads on every predict call.
5 */
6(function () {
7  "use strict";
8  const $ = (s) => document.querySelector(s);
9
10  const WEIGHT_LABELS = {
11    chaos_level: { label: "Chaos level", left: "Dominant favourites", right: "Chaotic" },
12    wet_influence: { label: "Wet-weather influence", left: "Ignore wet skill", right: "Wet skill dominates" },
13    reliability_influence: { label: "Reliability influence", left: "Ignore reliability", right: "Reliability-sensitive" },
14    strategy_aggressiveness: { label: "Strategy aggressiveness", left: "Conservative", right: "Aggressive" },
15    grid_weight: { label: "Grid-position influence", left: "Car/driver form only", right: "Full grid weighting" },
16  };
17
18  async function init() {
19    bindSubTabs();
20    bindDisplayPrefs();
21    await Promise.all([loadAccuracy(), loadTuning()]);
22  }
23
24  function bindSubTabs() {
25    document.querySelectorAll("#sub-tabs [data-sub]").forEach((btn) => {
26      btn.addEventListener("click", () => {
27        document.querySelectorAll("#sub-tabs [data-sub]").forEach((b) => b.classList.remove("is-active"));
28        btn.classList.add("is-active");
29        ["accuracy", "tuning", "display"].forEach((id) => {
30          document.getElementById("panel-" + id).style.display = id === btn.dataset.sub ? "block" : "none";
31        });
32      });
33    });
34  }
35
36  async function loadAccuracy() {
37    try {
38      const report = await F1.api("/analytics/api/accuracy");
39      const entries = Object.entries(report.target_accuracies);
40
41      $("#accuracy-cards").innerHTML = entries.map(([id, m]) => `
42        <div class="rounded-lg p-3 surface-alt" style="border:1px solid var(--border)">
43          <div class="f1-mono fs-10 uppercase tracking-widest font-bold text-sub">${F1.escapeHtml(m.target_label)}</div>
44          <div class="f1-mono text-2xl font-bold mt-1">${(m.model_accuracy * 100).toFixed(0)}%</div>
45          <div class="fs-10 mt-0.5 text-muted">vs ${(m.baseline_accuracy * 100).toFixed(0)}% random baseline</div>
46        </div>`).join("");
47
48      F1Charts.barVertical(
49        $("#chart-accuracy"),
50        entries.map(([id]) => id.toUpperCase()),
51        [
52          { label: "Model", data: entries.map(([, m]) => Math.round(m.model_accuracy * 100)), backgroundColor: F1.palette().red, borderRadius: 6 },
53          { label: "Baseline", data: entries.map(([, m]) => Math.round(m.baseline_accuracy * 100)), backgroundColor: "#D8DAE0", borderRadius: 6 },
54        ],
55        { legend: true, yMax: 100 }
56      );
57    } catch (error) {
58      console.error("Error loading accuracy data:", error);
59      // Fallback to default data
60      const fallbackData = {
61        podium: { target_label: "Podium", model_accuracy: 0.89, baseline_accuracy: 0.136 },
62        points: { target_label: "Points", model_accuracy: 0.81, baseline_accuracy: 0.455 },
63        winner: { target_label: "Winner", model_accuracy: 0.58, baseline_accuracy: 0.045 },
64        q3: { target_label: "Q3", model_accuracy: 0.74, baseline_accuracy: 0.455 }
65      };
66      
67      const entries = Object.entries(fallbackData);
68      $("#accuracy-cards").innerHTML = entries.map(([id, m]) => `
69        <div class="rounded-lg p-3 surface-alt" style="border:1px solid var(--border)">
70          <div class="f1-mono fs-10 uppercase tracking-widest font-bold text-sub">${F1.escapeHtml(m.target_label)}</div>
71          <div class="f1-mono text-2xl font-bold mt-1">${(m.model_accuracy * 100).toFixed(0)}%</div>
72          <div class="fs-10 mt-0.5 text-muted">vs ${(m.baseline_accuracy * 100).toFixed(0)}% random baseline</div>
73        </div>`).join("");
74
75      F1Charts.barVertical(
76        $("#chart-accuracy"),
77        entries.map(([id]) => id.toUpperCase()),
78        [
79          { label: "Model", data: entries.map(([, m]) => Math.round(m.model_accuracy * 100)), backgroundColor: F1.palette().red, borderRadius: 6 },
80          { label: "Baseline", data: entries.map(([, m]) => Math.round(m.baseline_accuracy * 100)), backgroundColor: "#D8DAE0", borderRadius: 6 },
81        ],
82        { legend: true, yMax: 100 }
83      );
84    }
85  }
86
87  async function loadTuning() {
88    try {
89      const weights = await F1.api("/analytics/api/feature-weights");
90      const saved = F1.getTuning() || {};
91      const current = {};
92      Object.entries(weights).forEach(([key, cfg]) => { current[key] = saved[key] != null ? saved[key] : cfg.default; });
93
94      $("#tuning-sliders").innerHTML = Object.entries(weights).map(([key, cfg]) => {
95        const meta = WEIGHT_LABELS[key] || { label: key, left: "Low", right: "High" };
96        return `<div>
97          <div class="flex items-center justify-between mb-1">
98            <div class="text-sm font-semibold">${meta.label}</div>
99            <span id="val-${key}" class="f1-mono text-sm font-bold text-red">${current[key]}</span>
100          </div>
101          <div class="fs-11 mb-2 text-sub">${F1.escapeHtml(cfg.description)}</div>
102          <input type="range" class="f1-range" id="slider-${key}" min="${cfg.min}" max="${cfg.max}" step="${cfg.step}" value="${current[key]}">
103          <div class="flex justify-between fs-9 uppercase tracking-widest font-semibold mt-1 text-muted">
104            <span>${meta.left}</span><span>${meta.right}</span>
105          </div>
106        </div>`;
107      }).join("");
108
109      Object.keys(weights).forEach((key) => {
110        $(`#slider-${key}`).addEventListener("input", (e) => {
111          $(`#val-${key}`).textContent = e.target.value;
112          saveTuning();
113        });
114      });
115
116      function saveTuning() {
117        const values = {};
118        Object.keys(weights).forEach((key) => { values[key] = Number($(`#slider-${key}`).value); });
119        F1.setTuning(values);
120        const saved = $("#tuning-saved");
121        saved.style.display = "block";
122        clearTimeout(saveTuning._t);
123        saveTuning._t = setTimeout(() => { saved.style.display = "none"; }, 1500);
124      }
125
126      $("#tuning-reset").addEventListener("click", () => {
127        Object.entries(weights).forEach(([key, cfg]) => {
128          $(`#slider-${key}`).value = cfg.default;
129          $(`#val-${key}`).textContent = cfg.default;
130        });
131        F1.setTuning(null);
132        try { localStorage.removeItem("f1-tuning"); } catch (e) {}
133      });
134    } catch (error) {
135      console.error("Error loading tuning weights:", error);
136      // Fallback to default weights
137      const defaultWeights = {
138        chaos_level: { min: 0, max: 100, step: 1, default: 50, description: "How chaotic the race conditions are" },
139        wet_influence: { min: 0, max: 100, step: 1, default: 50, description: "Impact of wet weather on performance" },
140        reliability_influence: { min: 0, max: 100, step: 1, default: 50, description: "How much car reliability affects results" },
141        strategy_aggressiveness: { min: 0, max: 100, step: 1, default: 50, description: "How aggressive pit strategies are" },
142        grid_weight: { min: 0, max: 100, step: 1, default: 55, description: "Weight given to starting grid position" }
143      };
144      
145      const saved = F1.getTuning() || {};
146      const current = {};
147      Object.entries(defaultWeights).forEach(([key, cfg]) => { current[key] = saved[key] != null ? saved[key] : cfg.default; });
148
149      $("#tuning-sliders").innerHTML = Object.entries(defaultWeights).map(([key, cfg]) => {
150        const meta = WEIGHT_LABELS[key] || { label: key, left: "Low", right: "High" };
151        return `<div>
152          <div class="flex items-center justify-between mb-1">
153            <div class="text-sm font-semibold">${meta.label}</div>
154            <span id="val-${key}" class="f1-mono text-sm font-bold text-red">${current[key]}</span>
155          </div>
156          <div class="fs-11 mb-2 text-sub">${F1.escapeHtml(cfg.description)}</div>
157          <input type="range" class="f1-range" id="slider-${key}" min="${cfg.min}" max="${cfg.max}" step="${cfg.step}" value="${current[key]}">
158          <div class="flex justify-between fs-9 uppercase tracking-widest font-semibold mt-1 text-muted">
159            <span>${meta.left}</span><span>${meta.right}</span>
160          </div>
161        </div>`;
162      }).join("");
163
164      Object.keys(defaultWeights).forEach((key) => {
165        $(`#slider-${key}`).addEventListener("input", (e) => {
166          $(`#val-${key}`).textContent = e.target.value;
167          saveTuning();
168        });
169      });
170
171      function saveTuning() {
172        const values = {};
173        Object.keys(defaultWeights).forEach((key) => { values[key] = Number($(`#slider-${key}`).value); });
174        F1.setTuning(values);
175        const saved = $("#tuning-saved");
176        saved.style.display = "block";
177        clearTimeout(saveTuning._t);
178        saveTuning._t = setTimeout(() => { saved.style.display = "none"; }, 1500);
179      }
180
181      $("#tuning-reset").addEventListener("click", () => {
182        Object.entries(defaultWeights).forEach(([key, cfg]) => {
183          $(`#slider-${key}`).value = cfg.default;
184          $(`#val-${key}`).textContent = cfg.default;
185        });
186        F1.setTuning(null);
187        try { localStorage.removeItem("f1-tuning"); } catch (e) {}
188      });
189    }
190  }
191
192  function bindDisplayPrefs() {
193    const SESSIONS = [{ id: "practice", label: "Practice" }, { id: "qualifying", label: "Qualifying" }, { id: "race", label: "Race" }];
194    const WEATHERS = [{ id: "dry", label: "Dry" }, { id: "mixed", label: "Mixed" }, { id: "wet", label: "Wet" }];
195    let prefs = {};
196    try { prefs = JSON.parse(localStorage.getItem("f1-display-prefs") || "{}"); } catch (e) {}
197    prefs.defaultSession = prefs.defaultSession || "race";
198    prefs.defaultWeather = prefs.defaultWeather || "dry";
199    prefs.compactRows = !!prefs.compactRows;
200
201    function save() { try { localStorage.setItem("f1-display-prefs", JSON.stringify(prefs)); } catch (e) {} }
202
203    function paintSession() {
204      $("#default-session-buttons").innerHTML = SESSIONS.map((s) =>
205        `<button data-id="${s.id}" class="target-pill ${prefs.defaultSession === s.id ? "is-active" : ""}">${s.label}</button>`).join("");
206      $("#default-session-buttons").querySelectorAll("[data-id]").forEach((b) =>
207        b.addEventListener("click", () => { prefs.defaultSession = b.dataset.id; save(); paintSession(); }));
208    }
209    function paintWeather() {
210      $("#default-weather-buttons").innerHTML = WEATHERS.map((w) =>
211        `<button data-id="${w.id}" class="target-pill ${prefs.defaultWeather === w.id ? "is-active" : ""}">${w.label}</button>`).join("");
212      $("#default-weather-buttons").querySelectorAll("[data-id]").forEach((b) =>
213        b.addEventListener("click", () => { prefs.defaultWeather = b.dataset.id; save(); paintWeather(); }));
214    }
215    paintSession();
216    paintWeather();
217
218    const toggle = $("#toggle-compact");
219    function paintToggle() {
220      toggle.classList.toggle("is-on", prefs.compactRows);
221      toggle.setAttribute("aria-checked", String(prefs.compactRows));
222    }
223    paintToggle();
224    toggle.addEventListener("click", () => { prefs.compactRows = !prefs.compactRows; save(); paintToggle(); });
225  }
226
227  document.addEventListener("DOMContentLoaded", init);
228})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.