fix:批量修复了一些问题

This commit is contained in:
shangfangjian
2026-09-19 22:51:35 +08:00
parent 835d7217d0
commit 8e6ad5394e
44 changed files with 4921 additions and 395 deletions
+71 -10
View File
@@ -12,7 +12,7 @@
import { useEffect, useState } from 'react'
import { triggerBacktest, fetchEvalSummary, fetchLeagues } from '../dal'
import type { BacktestRequest, EvalSummary, League } from '../types'
import type { BacktestRequest, BacktestSummary, EvalSummary, League } from '../types'
import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner } from '../components'
import TeamSideTag from '../../components/TeamSideTag'
@@ -34,18 +34,43 @@ interface BacktestResultRow {
}
interface BacktestResponse {
summary: {
total: number
scored: number
accuracy_1x2?: number
avg_score_rmse?: number
avg_subjective_confidence?: number
}
summary: BacktestSummary
results: BacktestResultRow[]
}
const OUTCOME_LABEL: Record<string, string> = { '1': '主胜', X: '平局', '2': '客胜' }
/** 导出回测明细为 CSV(UTF-8 BOM,Excel 可直接打开) */
function exportCsv(rows: BacktestResultRow[]) {
const header = [
"比赛日期", "联赛", "主队", "客队", "实际比分", "实际1X2",
"预测主球", "预测客球", "预测1X2", "主观置信度", "1X2命中",
]
const lines = [header.join(",")]
for (const r of rows) {
lines.push([
fmtDate(r.match_date), r.league_code ?? "",
csvCell(r.home_team_zh || r.home_team), csvCell(r.away_team_zh || r.away_team),
r.actual_score, r.actual_1x2 ?? "",
r.pred_home ?? "", r.pred_away ?? "", r.pred_1x2 ?? "",
r.subjective_confidence != null ? String(Math.round(r.subjective_confidence * 100)) : "",
r.correct_1x2 ? "是" : "否",
].join(","))
}
const blob = new Blob(["" + lines.join("\n")], { type: "text/csv;charset=utf-8" })
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `backtest_${new Date().toISOString().slice(0, 10)}.csv`
a.click()
URL.revokeObjectURL(url)
}
/** CSV 字段转义:含逗号/引号/换行时加引号 */
function csvCell(v: string): string {
return /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v
}
function fmtDate(s?: string | null): string {
if (!s) return '—'
return s.slice(0, 10)
@@ -58,6 +83,7 @@ export default function BacktestPage() {
const [dateTo, setDateTo] = useState('')
const [limit, setLimit] = useState(20)
const [mode, setMode] = useState<'single' | 'multi'>('single')
const [model, setModel] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [result, setResult] = useState<BacktestResponse | null>(null)
@@ -90,6 +116,7 @@ export default function BacktestPage() {
date_to: dateTo || undefined,
limit,
mode,
model: model.trim() || undefined,
}
const res = await triggerBacktest(req as BacktestRequest)
setResult(res as unknown as BacktestResponse)
@@ -177,6 +204,19 @@ export default function BacktestPage() {
</div>
</div>
<div>
<label className="mb-1.5 block text-xs text-ink-500">
(,= <code className="font-mono">gpt-4o</code>)
</label>
<input
type="text"
value={model}
onChange={e => setModel(e.target.value)}
placeholder="如 deepseek-chat / 留空使用默认"
className="field w-full"
/>
</div>
{error && <Alert kind="error" title="回测失败" message={error} onClose={() => setError(null)} />}
<button type="submit" disabled={loading} className="btn btn-solid w-full">
@@ -190,15 +230,31 @@ export default function BacktestPage() {
<div className="space-y-6">
{summary && (
<Card>
<CardHeader title="回测结果" />
<CardHeader
title="回测结果"
description={`模式: ${mode}${model ? ` · 模型: ${model}` : ''} · 限 ${limit}`}
action={
result?.results?.length
? (<button onClick={() => exportCsv(result.results)} className="btn btn-sm"> CSV</button>)
: undefined
}
/>
<CardBody>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
<div>
<div className="font-serif text-2xl font-bold tabular-nums text-ink-900">
{summary.scored}/{summary.total}
</div>
<div className="mt-1 text-2xs text-ink-400"> / </div>
</div>
<div>
<div className="font-serif text-2xl font-bold tabular-nums text-ink-900">
{summary.success}/{summary.degraded}
</div>
<div className="mt-1 text-2xs text-ink-400">
/ <span className="text-ink-300"> (degraded)</span>
</div>
</div>
<div>
<div className="font-serif text-2xl font-bold tabular-nums text-press">
{summary.accuracy_1x2 !== undefined && summary.accuracy_1x2 !== null
@@ -223,6 +279,11 @@ export default function BacktestPage() {
</div>
<div className="mt-1 text-2xs text-ink-400"></div>
</div>
{summary.degraded > 0 && (
<div className="col-span-full border-l-2 border-press bg-press-wash/40 px-3 py-2 text-2xs leading-relaxed text-press-dark">
{summary.degraded} (),
</div>
)}
</div>
</CardBody>
</Card>