diff --git a/static/js/weather-chart.js b/static/js/weather-chart.js index 3a144e0..c27d9da 100644 --- a/static/js/weather-chart.js +++ b/static/js/weather-chart.js @@ -45,7 +45,10 @@ const WeatherChart = { const bucket = byTime.get(fp.date_time); const h = typeof fp.lead_hours === 'number' ? fp.lead_hours : null; if (h !== null && h >= 0 && h <= 3) { - bucket[h] = fp; + // 保留同一 forecast_time+lead 的最新版本(查询结果已按 issued_at DESC 排序) + if (bucket[h] == null) { + bucket[h] = fp; + } } }); @@ -81,6 +84,76 @@ const WeatherChart = { if (this.chart) this.chart.destroy(); + // 计算降水分类准确率(+1h/+2h/+3h) + const updateAccuracyPanel = () => { + // 仅在有历史数据(实际)时计算 + const usedIdx = historyRainfalls + .map((v, idx) => ({ v, idx })) + .filter(x => x.v !== null) + .map(x => x.idx); + const totalHours = usedIdx.length; + const bucketOf = (mm) => { + if (mm === null || mm === undefined || isNaN(Number(mm))) return null; + const v = Math.max(0, Number(mm)); + if (v < 5) return 0; + if (v < 10) return 1; + return 2; + }; + const calcFor = (arrFcst) => { + let correct = 0; + usedIdx.forEach(i => { + const a = historyRainfalls[i]; + const f = arrFcst[i]; + const ba = bucketOf(a); + const bf = bucketOf(f); + if (ba !== null && bf !== null && ba === bf) correct += 1; + }); + return { correct, total: totalHours }; + }; + const fmt = (n) => `${n.toFixed(1)}%`; + const elPanel = document.getElementById('accuracyPanel'); + const elH1 = document.getElementById('accH1'); + const elH2 = document.getElementById('accH2'); + const elH3 = document.getElementById('accH3'); + if (!elPanel || !elH1 || !elH2 || !elH3) return; + if (forecastData.length === 0 || totalHours === 0) { + elPanel.style.display = 'none'; + return; + } + // 详细计算过程日志 + try { + console.groupCollapsed('[准确率] 降水分档 (+1h/+2h/+3h) 计算详情'); + console.log('时间段总小时(有实测):', totalHours); + const nameOf = (b) => (b===0?'[0,5)':(b===1?'[5,10)':'[10,∞)')); + const fv = (v) => (v===null||v===undefined||isNaN(Number(v)) ? 'NULL' : Number(v).toFixed(2)); + usedIdx.forEach(i => { + const label = allLabels[i]; + const a = historyRainfalls[i]; + const bA = bucketOf(a); + const f1 = forecastRainfallsH1[i]; const b1 = bucketOf(f1); + const f2 = forecastRainfallsH2[i]; const b2 = bucketOf(f2); + const f3 = forecastRainfallsH3[i]; const b3 = bucketOf(f3); + const m1 = (bA!==null && b1!==null && bA===b1) ? '√' : '×'; + const m2 = (bA!==null && b2!==null && bA===b2) ? '√' : '×'; + const m3 = (bA!==null && b3!==null && bA===b3) ? '√' : '×'; + console.log( + `${label} | 实测 ${fv(a)}mm (${bA===null?'--':nameOf(bA)}) | +1h ${fv(f1)} (${b1===null?'--':nameOf(b1)}) ${m1} | +2h ${fv(f2)} (${b2===null?'--':nameOf(b2)}) ${m2} | +3h ${fv(f3)} (${b3===null?'--':nameOf(b3)}) ${m3}` + ); + }); + } catch (e) { console.warn('准确率计算日志输出失败', e); } + const r1 = calcFor(forecastRainfallsH1); + const r2 = calcFor(forecastRainfallsH2); + const r3 = calcFor(forecastRainfallsH3); + console.log(`+1h: ${r1.correct}/${r1.total}`); + console.log(`+2h: ${r2.correct}/${r2.total}`); + console.log(`+3h: ${r3.correct}/${r3.total}`); + console.groupEnd(); + elH1.textContent = r1.total > 0 ? fmt((r1.correct / r1.total) * 100) : '--'; + elH2.textContent = r2.total > 0 ? fmt((r2.correct / r2.total) * 100) : '--'; + elH3.textContent = r3.total > 0 ? fmt((r3.correct / r3.total) * 100) : '--'; + elPanel.style.display = 'block'; + }; + const datasets = [ { label: '温度 (°C) - 实测', @@ -274,6 +347,9 @@ const WeatherChart = { this.chart = new Chart(ctx, chartConfig); + // 更新准确率面板 + updateAccuracyPanel(); + const mode = document.getElementById('legendMode')?.value || 'combo_standard'; this.applyLegendMode(mode); } @@ -339,4 +415,4 @@ WeatherChart.updateAxesVisibility = function() { }); }; -window.WeatherChart = WeatherChart; \ No newline at end of file +window.WeatherChart = WeatherChart; diff --git a/templates/index.html b/templates/index.html index 90ea427..8edeb5e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -196,6 +196,18 @@ animation: slideDown 0.3s ease; } + .accuracy-panel { + display: none; + font-size: 12px; + color: #374151; /* 灰色文字 */ + white-space: nowrap; + text-align: right; + margin-top: -6px; + } + .accuracy-panel .item { margin-left: 8px; } + .accuracy-panel .label { color: #6b7280; margin-right: 4px; } + .accuracy-panel .value { font-weight: 600; color: #111827; } + .chart-wrapper { height: 500px; margin-bottom: 30px; @@ -520,7 +532,6 @@ -