102 lines
3.0 KiB
JavaScript
102 lines
3.0 KiB
JavaScript
function json(data, status = 200) {
|
|
return new Response(JSON.stringify(data), {
|
|
status,
|
|
headers: { "content-type": "application/json; charset=UTF-8" },
|
|
});
|
|
}
|
|
|
|
function toInt(v, d) {
|
|
const n = Number(v);
|
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : d;
|
|
}
|
|
|
|
function ratio(correct, total) {
|
|
if (!total) return 0;
|
|
return Math.round((correct / total) * 10000) / 100;
|
|
}
|
|
|
|
function item(total, correct) {
|
|
const t = Number(total || 0);
|
|
const c = Number(correct || 0);
|
|
return {
|
|
total: t,
|
|
correct: c,
|
|
incorrect: Math.max(0, t - c),
|
|
accuracy: ratio(c, t),
|
|
};
|
|
}
|
|
|
|
export async function onRequestGet(context) {
|
|
const db = context.env.DB;
|
|
if (!db) {
|
|
return json({ success: false, error: "缺少 D1 绑定 DB" }, 500);
|
|
}
|
|
|
|
try {
|
|
const url = new URL(context.request.url);
|
|
const month = (url.searchParams.get("month") || new Date().toISOString().slice(0, 7)).trim();
|
|
const page = toInt(url.searchParams.get("page"), 1);
|
|
const limit = Math.min(100, toInt(url.searchParams.get("limit"), 10));
|
|
const offset = (page - 1) * limit;
|
|
|
|
const baseWhere = "date LIKE ?";
|
|
const monthArg = `${month}%`;
|
|
|
|
const dataSql = `
|
|
SELECT
|
|
date,
|
|
SUM(CASE WHEN actual_result IS NOT NULL AND ensemble_prediction IS NOT NULL THEN 1 ELSE 0 END) AS ens_total,
|
|
SUM(CASE WHEN ensemble_correct = 1 THEN 1 ELSE 0 END) AS ens_correct,
|
|
|
|
SUM(CASE WHEN actual_result IS NOT NULL AND dlinear_prediction IS NOT NULL THEN 1 ELSE 0 END) AS dl_total,
|
|
SUM(CASE WHEN dlinear_correct = 1 THEN 1 ELSE 0 END) AS dl_correct,
|
|
|
|
SUM(CASE WHEN actual_result IS NOT NULL AND nbeats_prediction IS NOT NULL THEN 1 ELSE 0 END) AS nb_total,
|
|
SUM(CASE WHEN nbeats_correct = 1 THEN 1 ELSE 0 END) AS nb_correct,
|
|
|
|
SUM(CASE WHEN actual_result IS NOT NULL AND ttm_prediction IS NOT NULL THEN 1 ELSE 0 END) AS ttm_total,
|
|
SUM(CASE WHEN ttm_correct = 1 THEN 1 ELSE 0 END) AS ttm_correct
|
|
FROM backup_hourly_model_predictions
|
|
WHERE ${baseWhere}
|
|
GROUP BY date
|
|
ORDER BY date DESC
|
|
LIMIT ? OFFSET ?
|
|
`;
|
|
|
|
const countSql = `
|
|
SELECT COUNT(*) AS cnt
|
|
FROM (
|
|
SELECT date
|
|
FROM backup_hourly_model_predictions
|
|
WHERE ${baseWhere}
|
|
GROUP BY date
|
|
) x
|
|
`;
|
|
|
|
const dataResult = await db.prepare(dataSql).bind(monthArg, limit, offset).all();
|
|
const countResult = await db.prepare(countSql).bind(monthArg).first();
|
|
|
|
const rows = (dataResult.results || []).map((r) => ({
|
|
date: r.date,
|
|
Ensemble: item(r.ens_total, r.ens_correct),
|
|
DLinear: item(r.dl_total, r.dl_correct),
|
|
"N-BEATS": item(r.nb_total, r.nb_correct),
|
|
"TTM-R2": item(r.ttm_total, r.ttm_correct),
|
|
}));
|
|
|
|
const total = Number(countResult?.cnt || 0);
|
|
const totalPages = total > 0 ? Math.ceil(total / limit) : 1;
|
|
|
|
return json({
|
|
success: true,
|
|
data: rows,
|
|
page,
|
|
limit,
|
|
total,
|
|
total_pages: totalPages,
|
|
});
|
|
} catch (error) {
|
|
return json({ success: false, error: String(error) }, 500);
|
|
}
|
|
}
|