33 lines
3.5 KiB
TypeScript
33 lines
3.5 KiB
TypeScript
import Link from 'next/link';
|
||
|
||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||
|
||
type MarketBucket = { market_type: string; settled_count?: number; hit_count?: number; hit_rate_percent?: number };
|
||
type PerformanceResponse = { settled_recommendation_count?: number; hit_count?: number; miss_count?: number; hit_rate_percent?: number; summary?: string; by_market_type?: MarketBucket[]; improvement_actions?: string[] };
|
||
|
||
async function getPerformance(): Promise<PerformanceResponse> {
|
||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/recommendation-performance`, { cache: 'no-store' });
|
||
if (!response.ok) throw new Error('賽後校準 API 暫時無法回應');
|
||
return response.json() as Promise<PerformanceResponse>;
|
||
}
|
||
|
||
export default async function RecommendationPerformancePage() {
|
||
let data: PerformanceResponse | null = null;
|
||
let error = '';
|
||
try { data = await getPerformance(); } catch (err) { error = err instanceof Error ? err.message : '賽後校準暫時無法讀取'; }
|
||
const buckets = data?.by_market_type ?? [];
|
||
const actions = data?.improvement_actions ?? [];
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<section className="rounded-[2rem] border border-[#e7c89b] bg-[#fff8e6]/95 p-6 md:p-8"><p className="dot-matrix text-sm font-bold text-[#b83822]">賽後校準室</p><h1 className="mt-3 text-4xl font-black text-[#3f2f25]">推薦要跟實際賽果對帳</h1><p className="mt-3 max-w-3xl text-sm leading-7 text-[#6f4f3c]">每場結束後,系統會把推薦玩法與實際結果比對,追蹤命中率、未中原因與下一輪模型調整方向。</p></section>
|
||
{error ? <p className="rounded-2xl border border-[#e7a49a] bg-[#fff0e8] p-4 text-sm font-bold text-[#b83822]">{error}</p> : null}
|
||
<section className="grid gap-4 md:grid-cols-4">{[['已結算', data?.settled_recommendation_count ?? '-', '可判定推薦'], ['命中', data?.hit_count ?? '-', '命中筆數'], ['未中', data?.miss_count ?? '-', '未中筆數'], ['命中率', typeof data?.hit_rate_percent === 'number' ? `${data.hit_rate_percent.toFixed(2)}%` : '-', '近端表現']].map(([label, value, helper]) => <article key={label} className="panel-glow rounded-2xl p-5"><p className="text-xs font-semibold tracking-[0.2em] text-[#8a6b58]">{helper}</p><p className="mt-3 text-3xl font-black text-[#7d2a15]">{value}</p><p className="mt-1 text-sm text-[#6f4f3c]">{label}</p></article>)}</section>
|
||
<p className="rounded-3xl border border-[#eadcb9] bg-white/75 p-5 text-sm leading-7 text-[#6f4f3c]">{data?.summary ?? '尚無摘要。'}</p>
|
||
<section className="rounded-3xl border border-[#eadcb9] bg-white/75 p-5"><p className="dot-matrix text-sm font-bold text-[#7d2a15]">各玩法表現</p><div className="mt-3 grid gap-3 md:grid-cols-2 lg:grid-cols-3">{buckets.map((item) => <article key={item.market_type} className="rounded-2xl bg-[#fff8e6] p-4 text-sm text-[#7a5b46]"><b className="text-[#3f2f25]">{item.market_type}</b><br />結算 {item.settled_count ?? 0}|命中 {item.hit_count ?? 0}|命中率 {item.hit_rate_percent ?? 0}%</article>)}</div></section>
|
||
<section className="rounded-3xl border border-[#eadcb9] bg-white/75 p-5"><p className="dot-matrix text-sm font-bold text-[#7d2a15]">模型調整方向</p><ul className="mt-3 space-y-2 text-sm leading-6 text-[#7a5b46]">{actions.map((item) => <li key={item}>・{item}</li>)}</ul></section>
|
||
<Link href="/proof-of-yield" className="inline-flex rounded-full bg-[#d1432d] px-5 py-3 text-sm font-black text-white">看公開收益帳本</Link>
|
||
</div>
|
||
);
|
||
}
|