feat(evolver): Evolver 페이지 + LottoActivityTimeline + EvolverActions + 라우터

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 02:19:07 +09:00
parent b99d720179
commit 2543dc335d
5 changed files with 266 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import React from 'react';
import './Evolver.css';
import { useEvolverApi } from './evolver/useEvolverApi';
import WinnerCard from './evolver/WinnerCard';
import TrialsGrid from './evolver/TrialsGrid';
import BaseDiff from './evolver/BaseDiff';
import BaseHistory from './evolver/BaseHistory';
import LottoActivityTimeline from './evolver/LottoActivityTimeline';
import EvolverActions from './evolver/EvolverActions';
export default function Evolver() {
const { status, history, activity, loading, error, refetch } = useEvolverApi({ days: 7, weeks: 12 });
if (loading) return <div className="evolver"><p>로딩 ...</p></div>;
if (error) return <div className="evolver"><p>에러: {String(error)}</p></div>;
const latestBase = (history.items || [])[0];
const previousBase = (history.items || [])[1]?.weight || status?.current_base || [0.2, 0.2, 0.2, 0.2, 0.2];
const newBase = latestBase?.weight || status?.current_base;
const trials = status?.trials || [];
const winnerTrialId = latestBase?.source_trial_id;
const winnerTrial = trials.find(t => t.id === winnerTrialId);
const winnerInfo = winnerTrial ? {
day_of_week: winnerTrial.day_of_week,
weight: winnerTrial.weight,
avg_score: latestBase?.winner_score,
max_correct: latestBase?.winner_max_correct,
n_picks: (winnerTrial.picks || []).length,
} : null;
const perDay = trials.map(t => ({
day_of_week: t.day_of_week,
trial_id: t.id,
avg_score: (t.picks || []).reduce((s, p) => s + (p.meta_score || 0), 0) / Math.max(1, (t.picks || []).length),
max_correct: Math.max(0, ...(t.picks || []).map(p => p.correct || 0)),
}));
const hasBase = (history.items || []).length > 0;
return (
<div className="evolver">
<header className="evolver-header">
<div>
<p className="evolver-kicker">Lotto · Weight Evolver</p>
<h1>자율 학습 루프</h1>
<p className="evolver-sub">
매주 6가지 가중치를 시도해서 best 조합을 다음주 base로 학습합니다.
{status?.latest_draw && ` 마지막 회차: ${status.latest_draw}회.`}
</p>
</div>
<button className="refresh-btn" onClick={refetch}> 새로고침</button>
</header>
{!hasBase ? (
<div className="evolver-card empty-state">
<h2>아직 학습 시작 </h2>
<p>다음 월요일 09:00 자동 시작 또는 수동 트리거 사용.</p>
<EvolverActions onChange={refetch} />
</div>
) : (
<>
<WinnerCard
winner={winnerInfo}
previousBase={previousBase}
updateReason={latestBase?.update_reason}
drawNo={status?.latest_draw}
/>
<TrialsGrid trials={trials} perDay={perDay} winnerTrialId={winnerTrialId} />
<BaseDiff
previousBase={previousBase}
newBase={newBase}
updateReason={latestBase?.update_reason}
/>
<BaseHistory history={history.items || []} />
<LottoActivityTimeline activity={activity} days={7} />
<EvolverActions onChange={refetch} />
</>
)}
</div>
);
}