masquerade-note.tsx
1,470 bytes
| 1 | 'use client'; |
|---|---|
| 2 | |
| 3 | import { VenetianMask } from 'lucide-react'; |
| 4 | import { useMemo } from 'react'; |
| 5 | import { buildMasqueradeReport } from '@/core/masquerade'; |
| 6 | import type { CritiqueRecord, Participant } from '@/core/types'; |
| 7 | |
| 8 | /** |
| 9 | * One-line anonymity audit: how often reviewers correctly guessed who wrote |
| 10 | * an answer, versus chance. Near chance means the masks are holding; well |
| 11 | * above means models are stylistically fingerprintable in this debate. |
| 12 | */ |
| 13 | export function MasqueradeNote({ |
| 14 | participants, |
| 15 | critiques, |
| 16 | }: { |
| 17 | participants: Participant[]; |
| 18 | critiques: CritiqueRecord[]; |
| 19 | }) { |
| 20 | const report = useMemo(() => buildMasqueradeReport(participants, critiques), [participants, critiques]); |
| 21 | if (report.verdict === 'insufficient' || report.hitRate === null || report.chanceRate === null) return null; |
| 22 | |
| 23 | const leaking = report.verdict === 'leaking'; |
| 24 | return ( |
| 25 | <p |
| 26 | className={`flex items-center gap-1.5 text-xs ${ |
| 27 | leaking ? 'text-amber-600 dark:text-amber-400' : 'text-muted-foreground' |
| 28 | }`} |
| 29 | > |
| 30 | <VenetianMask className="h-3.5 w-3.5 shrink-0" /> |
| 31 | Anonymity check: reviewers identified authors in {pct(report.hitRate)} of {report.guesses} guesses vs{' '} |
| 32 | {pct(report.chanceRate)} chance.{' '} |
| 33 | {leaking |
| 34 | ? 'Writing style is leaking authorship in this debate; treat critique scores with extra care.' |
| 35 | : 'The masks held.'} |
| 36 | </p> |
| 37 | ); |
| 38 | } |
| 39 | |
| 40 | function pct(v: number): string { |
| 41 | return `${Math.round(v * 100)}%`; |
| 42 | } |
| 43 | |