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); } }