/** * v2.0 AEO / GEO 분리 점수 패널 (초안). * 두 축 각각 100점. 게이트(크롤러 접근·본문 렌더링)는 점수가 아니라 상한으로 표시한다. * 항목 점수는 v1.0 실측에서 파생하거나(v1 파생) 사람 판정(v2Results)에서 온다. 미검증은 분모 제외. */ import { ScoreRing } from '../report/ui/ScoreRing'; import { SectionWrapper } from '../report/ui/SectionWrapper'; import type { AxisScoreV2, ItemScoreV2, OverallScoreV2, RubricV2 } from '../../types/discovery'; const BASIS_LABEL: Record = { derived: 'v1.0 실측 파생', judged: '사람 판정', unverified: '미검증' }; const METHOD_LABEL = { auto: '자동', semi: '반자동', manual: '사람' } as const; function ItemRow({ it }: { it: ItemScoreV2; key?: string }) { const pct = it.pct === null ? null : Math.round(it.pct * 100); const color = pct === null ? '#CBD5E1' : pct <= 40 ? '#C084CF' : pct <= 60 ? '#8B9CF7' : pct <= 80 ? '#7C6DD8' : '#6C5CE7'; return (
{it.item.id}

{it.item.name}

{it.item.weight}점 · {METHOD_LABEL[it.item.method]}

{it.item.question}

{it.evidence &&

{it.evidence}

}

{BASIS_LABEL[it.basis]} {it.signals.length > 0 && ` · ${it.signals.map((s) => `${s.criterionId}=${s.level === 'unverified' ? '미검증' : s.level}`).join(' · ')}`}

{pct === null ? '미검증' : `${it.earned} / ${it.item.weight}`}
); } function AxisCard({ ax }: { ax: AxisScoreV2 }) { return (

{ax.axis.code} · {ax.axis.nameEn}

{ax.axis.name}

{ax.axis.question}

검증 {ax.verifiedCount}/{ax.items.length} · {ax.earned} / {ax.possible}점

{ax.cappedBy.length > 0 && (

게이트 상한 {Math.min(...ax.cappedBy.map((g) => g.gate.cap))} (원점수 {ax.rawScore})

)}

{ax.grade}

{ax.axis.description}

{ax.items.map((it) => )}
); } export function AeoGeoV2Panel({ rubric, score }: { rubric: RubricV2; score: OverallScoreV2 }) { return (
{[score.aeo, score.geo].map((ax) => (

{ax.axis.code} · {ax.axis.name}

{ax.axis.question}

등급 {ax.grade} · 검증 {ax.verifiedCount}/{ax.items.length} {ax.cappedBy.length > 0 && ` · 게이트 상한 적용 (원점수 ${ax.rawScore})`}

))}

게이트 (점수가 아니라 상한)

    {score.gates.map((g) => (
  • {g.gate.id} {g.gate.name} · {g.passed ? '통과' : `실패 (${g.failedBy.join(', ')}) → 상한 ${g.gate.cap}`} {g.gate.reason}
  • ))}
); }