document.addEventListener("DOMContentLoaded", () => { const dataElement = document.getElementById("simulations-data"); let simulationScenarios = []; let initialRuns = []; if (dataElement) { try { const parsed = JSON.parse(dataElement.textContent || "{}"); if (parsed && typeof parsed === "object") { if (Array.isArray(parsed.scenarios)) { simulationScenarios = parsed.scenarios; } if (Array.isArray(parsed.runs)) { initialRuns = parsed.runs; } } } catch (error) { console.error("Unable to parse simulations data", error); } } const SUMMARY_FIELDS = [ { key: "count", label: "Iterations", decimals: 0 }, { key: "mean", label: "Mean Result", decimals: 2 }, { key: "median", label: "Median Result", decimals: 2 }, { key: "min", label: "Minimum", decimals: 2 }, { key: "max", label: "Maximum", decimals: 2 }, { key: "variance", label: "Variance", decimals: 2 }, { key: "std_dev", label: "Standard Deviation", decimals: 2 }, { key: "percentile_5", label: "Percentile 5", decimals: 2 }, { key: "percentile_95", label: "Percentile 95", decimals: 2 }, { key: "value_at_risk_95", label: "Value at Risk (95%)", decimals: 2 }, { key: "expected_shortfall_95", label: "Expected Shortfall (95%)", decimals: 2, }, ]; const SAMPLE_RESULT_LIMIT = 20; const filterSelect = document.getElementById("simulations-scenario-filter"); const overviewWrapper = document.getElementById( "simulations-overview-wrapper" ); const overviewBody = document.getElementById("simulations-overview-body"); const overviewEmpty = document.getElementById("simulations-overview-empty"); const emptyState = document.getElementById("simulations-empty"); const summaryWrapper = document.getElementById("simulations-summary-wrapper"); const summaryBody = document.getElementById("simulations-summary-body"); const summaryEmpty = document.getElementById("simulations-summary-empty"); const resultsWrapper = document.getElementById("simulations-results-wrapper"); const resultsBody = document.getElementById("simulations-results-body"); const resultsEmpty = document.getElementById("simulations-results-empty"); const simulationForm = document.getElementById("simulation-run-form"); const simulationFeedback = document.getElementById("simulation-feedback"); const formScenarioSelect = document.getElementById( "simulation-form-scenario" ); const simulationRunsMap = Object.create(null); const getScenarioName = (id) => { const match = simulationScenarios.find( (scenario) => String(scenario.id) === String(id) ); return match ? match.name : `Scenario ${id}`; }; const formatNumber = (value, decimals = 2) => { if (value === null || value === undefined || Number.isNaN(Number(value))) { return "—"; } return Number(value).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, }); }; const showFeedback = (element, message, type = "success") => { if (!element) { return; } element.textContent = message; element.classList.remove("hidden", "success", "error"); element.classList.add(type); }; const hideFeedback = (element) => { if (!element) { return; } element.classList.add("hidden"); element.textContent = ""; }; const initializeRunsMap = () => { simulationScenarios.forEach((scenario) => { const key = String(scenario.id); simulationRunsMap[key] = { scenario_id: scenario.id, scenario_name: scenario.name, iterations: 0, summary: null, sample_results: [], }; }); initialRuns.forEach((run) => { const key = String(run.scenario_id); simulationRunsMap[key] = { scenario_id: run.scenario_id, scenario_name: run.scenario_name || getScenarioName(key), iterations: run.iterations || 0, summary: run.summary || null, sample_results: Array.isArray(run.sample_results) ? run.sample_results : [], }; }); }; const renderOverviewTable = () => { if (!overviewBody) { return; } overviewBody.innerHTML = ""; if (!simulationScenarios.length) { if (overviewWrapper) { overviewWrapper.classList.add("hidden"); } if (overviewEmpty) { overviewEmpty.classList.remove("hidden"); } return; } if (overviewWrapper) { overviewWrapper.classList.remove("hidden"); } if (overviewEmpty) { overviewEmpty.classList.add("hidden"); } simulationScenarios.forEach((scenario) => { const key = String(scenario.id); const run = simulationRunsMap[key]; const iterations = run && run.iterations ? run.iterations : 0; const meanValue = iterations && run && run.summary ? run.summary.mean : null; const row = document.createElement("tr"); row.innerHTML = ` ${scenario.name} ${iterations || 0} ${iterations ? formatNumber(meanValue) : "—"} `; overviewBody.appendChild(row); }); }; const renderScenarioDetails = (scenarioId) => { if (!scenarioId) { if (emptyState) { emptyState.classList.remove("hidden"); } if (summaryWrapper) { summaryWrapper.classList.add("hidden"); } if (summaryEmpty) { summaryEmpty.classList.add("hidden"); } if (resultsWrapper) { resultsWrapper.classList.add("hidden"); } if (resultsEmpty) { resultsEmpty.classList.add("hidden"); } return; } if (emptyState) { emptyState.classList.add("hidden"); } const run = simulationRunsMap[String(scenarioId)]; const summary = run ? run.summary : null; const samples = run ? run.sample_results || [] : []; if (!summary) { if (summaryWrapper) { summaryWrapper.classList.add("hidden"); } if (summaryEmpty) { summaryEmpty.classList.remove("hidden"); } } else { if (summaryWrapper) { summaryWrapper.classList.remove("hidden"); } if (summaryEmpty) { summaryEmpty.classList.add("hidden"); } if (summaryBody) { summaryBody.innerHTML = ""; SUMMARY_FIELDS.forEach((field) => { const row = document.createElement("tr"); row.innerHTML = ` ${field.label} ${formatNumber(summary[field.key], field.decimals)} `; summaryBody.appendChild(row); }); } } if (!samples.length) { if (resultsWrapper) { resultsWrapper.classList.add("hidden"); } if (resultsEmpty) { resultsEmpty.classList.remove("hidden"); } } else { if (resultsWrapper) { resultsWrapper.classList.remove("hidden"); } if (resultsEmpty) { resultsEmpty.classList.add("hidden"); } if (resultsBody) { resultsBody.innerHTML = ""; samples.slice(0, SAMPLE_RESULT_LIMIT).forEach((item, index) => { const row = document.createElement("tr"); row.innerHTML = ` ${index + 1} ${formatNumber(item)} `; resultsBody.appendChild(row); }); } } }; const runSimulation = async (event) => { event.preventDefault(); hideFeedback(simulationFeedback); if (!simulationForm) { return; } const formData = new FormData(simulationForm); const scenarioId = formData.get("scenario_id"); const payload = { scenario_id: scenarioId ? Number(scenarioId) : null, iterations: Number(formData.get("iterations")), seed: formData.get("seed") ? Number(formData.get("seed")) : null, }; if (!payload.scenario_id) { showFeedback( simulationFeedback, "Select a scenario before running a simulation.", "error" ); return; } try { const response = await fetch("/api/simulations/", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { const errorDetail = await response.json().catch(() => ({})); throw new Error(errorDetail.detail || "Unable to run simulation."); } const result = await response.json(); const mapKey = String(result.scenario_id); const summary = result.summary && typeof result.summary === "object" ? result.summary : null; const iterations = summary && typeof summary.count === "number" ? summary.count : payload.iterations || 0; simulationRunsMap[mapKey] = { scenario_id: result.scenario_id, scenario_name: getScenarioName(mapKey), iterations, summary, sample_results: Array.isArray(result.sample_results) ? result.sample_results : [], }; renderOverviewTable(); renderScenarioDetails(mapKey); if (filterSelect) { filterSelect.value = mapKey; } if (formScenarioSelect) { formScenarioSelect.value = mapKey; } simulationForm.reset(); showFeedback(simulationFeedback, "Simulation completed.", "success"); } catch (error) { showFeedback( simulationFeedback, error.message || "An unexpected error occurred.", "error" ); } }; initializeRunsMap(); renderOverviewTable(); if (filterSelect) { filterSelect.addEventListener("change", (event) => { const value = event.target.value; renderScenarioDetails(value); }); } if (formScenarioSelect) { formScenarioSelect.addEventListener("change", (event) => { const value = event.target.value; if (filterSelect) { filterSelect.value = value; } renderScenarioDetails(value); }); } if (simulationForm) { simulationForm.addEventListener("submit", runSimulation); } if (filterSelect && filterSelect.value) { renderScenarioDetails(filterSelect.value); } });