Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 96 additions & 75 deletions apps/web/app/(pages)/evaluation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,109 @@ import { Badge } from "@/components/shared/badge";
import { formatPct, formatScore } from "@/lib/format";

export default async function EvaluationPage() {
const data = await api.evaluation();
try {
const data = await api.evaluation();

return (
<div className="space-y-6">
<Card>
<CardHeader
eyebrow="Evaluation Center"
title="롤링 OOS 성과와 프롬프트 리더보드"
description="테마 적중률, 주도주 적중률, 랭킹 품질, 오탐 비율, 갭페이드 실패를 체계적으로 추적하고 승격 후보를 심사합니다."
/>
<div className="metric-grid">
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">테마 적중률</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.themeHitRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">주도주 적중률</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.leaderHitRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">오탐 비율</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.falsePositiveRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">갭페이드 실패</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.gapFadeMissRate * 100)}</p>
</Card>
</div>
</Card>

<div className="grid gap-6 xl:grid-cols-2">
return (
<div className="space-y-6">
<Card>
<CardHeader eyebrow="Prompt Leaderboard" title="프롬프트 / 워크플로우 버전" />
<div className="space-y-3">
{data.promptLeaderboard.map((item) => (
<div
key={item.version}
className="rounded-[18px] border border-[color:var(--border)] bg-[color:var(--surface-white)] px-4 py-4"
>
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-[15px] font-semibold">{item.version}</p>
<p className="keep-korean text-[13px] leading-6 text-[color:var(--text-muted)]">
{item.description}
</p>
</div>
<Badge variant={item.promotable ? "ok" : "warn"}>
{item.promotable ? "승격 가능" : "보류"}
</Badge>
</div>
<div className="mt-3 grid gap-2 text-[13px] text-[color:var(--text-muted)]">
<p>종합 점수 {formatScore(item.score)}</p>
<p>테마 적중률 {formatPct(item.themeHitRate * 100)}</p>
<p>주도주 적중률 {formatPct(item.leaderHitRate * 100)}</p>
</div>
</div>
))}
<CardHeader
eyebrow="Evaluation Center"
title="롤링 OOS 성과와 프롬프트 리더보드"
description="테마 적중률, 주도주 적중률, 오탐 비율, 갭 페이드 실패율을 추적하고 현재 기준 설정을 비교합니다."
/>
<div className="metric-grid">
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">테마 적중률</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.themeHitRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">주도주 적중률</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.leaderHitRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">오탐 비율</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.falsePositiveRate * 100)}</p>
</Card>
<Card className="rounded-[18px] bg-[color:var(--bg-deep)] p-4">
<p className="text-[14px] font-semibold">갭 페이드 실패</p>
<p className="mt-3 text-[24px] font-bold">{formatPct(data.rollingMetrics.gapFadeMissRate * 100)}</p>
</Card>
</div>
</Card>
<Card>
<CardHeader eyebrow="Model Roles" title="역할별 모델 라우팅 성능" />
<div className="space-y-3">
{data.modelRoleLeaderboard.map((item) => (
<div
key={`${item.role}-${item.model}`}
className="rounded-[18px] border border-[color:var(--border)] bg-[color:var(--surface-white)] px-4 py-4"
>
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-[15px] font-semibold">{item.role}</p>
<p className="text-[13px] text-[color:var(--text-muted)]">{item.model}</p>

<div className="grid gap-6 xl:grid-cols-2">
<Card>
<CardHeader eyebrow="Prompt Leaderboard" title="프롬프트 / 워크플로우 버전" />
<div className="space-y-3">
{data.promptLeaderboard.map((item) => (
<div
key={item.version}
className="rounded-[18px] border border-[color:var(--border)] bg-[color:var(--surface-white)] px-4 py-4"
>
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-[15px] font-semibold">{item.version}</p>
<p className="keep-korean text-[13px] leading-6 text-[color:var(--text-muted)]">
{item.description}
</p>
</div>
<Badge variant={item.promotable ? "ok" : "warn"}>
{item.promotable ? "승격 가능" : "보류"}
</Badge>
</div>
<Badge>{formatScore(item.score)}</Badge>
<div className="mt-3 grid gap-2 text-[13px] text-[color:var(--text-muted)]">
<p>종합 점수 {formatScore(item.score)}</p>
<p>테마 적중률 {formatPct(item.themeHitRate * 100)}</p>
<p>주도주 적중률 {formatPct(item.leaderHitRate * 100)}</p>
</div>
</div>
))}
</div>
</Card>

<Card>
<CardHeader eyebrow="Model Roles" title="역할별 모델 라우팅 성능" />
<div className="space-y-3">
{data.modelRoleLeaderboard.map((item) => (
<div
key={`${item.role}-${item.model}`}
className="rounded-[18px] border border-[color:var(--border)] bg-[color:var(--surface-white)] px-4 py-4"
>
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-[15px] font-semibold">{item.role}</p>
<p className="text-[13px] text-[color:var(--text-muted)]">{item.model}</p>
</div>
<Badge>{formatScore(item.score)}</Badge>
</div>
<p className="keep-korean mt-2 text-[13px] leading-6 text-[color:var(--text-muted)]">
평균 지연 {item.avgLatencyMs}ms | 평균 비용 ${item.avgCostUsd.toFixed(4)}
</p>
</div>
<p className="keep-korean mt-2 text-[13px] leading-6 text-[color:var(--text-muted)]">
평균 지연 {item.avgLatencyMs}ms | 평균 비용 ${item.avgCostUsd.toFixed(4)}
</p>
</div>
))}
))}
</div>
</Card>
</div>
</div>
);
} catch (error) {
const message = error instanceof Error ? error.message : "알 수 없는 오류";

return (
<div className="space-y-6">
<Card>
<CardHeader
eyebrow="Evaluation Center"
title="평가 센터를 불러오지 못했습니다"
description="평가 요약 데이터를 요청하는 중 오류가 발생했습니다. API 서버와 DB 연결 상태를 확인해 주세요."
/>
<div className="rounded-[20px] border border-dashed border-[color:var(--border)] bg-[color:var(--bg-muted)] px-5 py-5 text-[14px] text-[color:var(--text-muted)]">
<p className="font-semibold text-white">오류 메시지</p>
<p className="keep-korean mt-2 leading-6">{message}</p>
</div>
</Card>
</div>
</div>
);
);
}
}
Loading