feat: 前端新增雨量预测准确率
This commit is contained in:
parent
582270ce95
commit
0caa1da229
@ -45,7 +45,10 @@ const WeatherChart = {
|
|||||||
const bucket = byTime.get(fp.date_time);
|
const bucket = byTime.get(fp.date_time);
|
||||||
const h = typeof fp.lead_hours === 'number' ? fp.lead_hours : null;
|
const h = typeof fp.lead_hours === 'number' ? fp.lead_hours : null;
|
||||||
if (h !== null && h >= 0 && h <= 3) {
|
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();
|
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 = [
|
const datasets = [
|
||||||
{
|
{
|
||||||
label: '温度 (°C) - 实测',
|
label: '温度 (°C) - 实测',
|
||||||
@ -274,6 +347,9 @@ const WeatherChart = {
|
|||||||
|
|
||||||
this.chart = new Chart(ctx, chartConfig);
|
this.chart = new Chart(ctx, chartConfig);
|
||||||
|
|
||||||
|
// 更新准确率面板
|
||||||
|
updateAccuracyPanel();
|
||||||
|
|
||||||
const mode = document.getElementById('legendMode')?.value || 'combo_standard';
|
const mode = document.getElementById('legendMode')?.value || 'combo_standard';
|
||||||
this.applyLegendMode(mode);
|
this.applyLegendMode(mode);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,6 +196,18 @@
|
|||||||
animation: slideDown 0.3s ease;
|
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 {
|
.chart-wrapper {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
@ -520,7 +532,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 在时间范围下方增加瓦片联动控制 -->
|
|
||||||
<div class="control-row flex items-center gap-3 flex-wrap">
|
<div class="control-row flex items-center gap-3 flex-wrap">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="text-sm text-gray-600">叠加显示:</label>
|
<label class="text-sm text-gray-600">叠加显示:</label>
|
||||||
@ -552,6 +563,11 @@
|
|||||||
|
|
||||||
<div class="chart-container" id="chartContainer">
|
<div class="chart-container" id="chartContainer">
|
||||||
<div id="stationInfoTitle" class="station-info-title"></div>
|
<div id="stationInfoTitle" class="station-info-title"></div>
|
||||||
|
<div id="accuracyPanel" class="accuracy-panel">
|
||||||
|
<span class="item"><span class="label">+1h</span><span id="accH1" class="value">--</span></span>
|
||||||
|
<span class="item"><span class="label">+2h</span><span id="accH2" class="value">--</span></span>
|
||||||
|
<span class="item"><span class="label">+3h</span><span id="accH3" class="value">--</span></span>
|
||||||
|
</div>
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
<canvas id="combinedChart"></canvas>
|
<canvas id="combinedChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user