'use client'; import { useState, useEffect } from 'react'; import { api } from '@/lib/trpc'; import { useActiveSymbol } from '@/stores/active-symbol-store'; import { CollapsibleSection } from '@/components/CollapsibleSection'; // --------------------------------------------------------------------------- // Types (subset of server types for display) // --------------------------------------------------------------------------- type Quality = 'strong-bullish' | 'moderate-bullish' | 'weak-bullish' | 'mixed' | 'weak-bearish' | 'moderate-bearish' | 'strong-bearish' | 'sparse'; const QUALITY_COLOR: Record = { 'strong-bullish': 'bg-green-600/20 text-green-300 border-green-600/40', 'moderate-bullish': 'bg-green-600/10 text-green-400 border-green-600/20', 'weak-bullish': 'bg-green-600/5 text-green-500 border-green-600/10', 'mixed': 'bg-zinc-700/30 text-zinc-300 border-zinc-600/30', 'weak-bearish': 'bg-red-600/5 text-red-500 border-red-600/10', 'moderate-bearish': 'bg-red-600/10 text-red-400 border-red-600/20', 'strong-bearish': 'bg-red-600/20 text-red-300 border-red-600/40', 'sparse': 'bg-zinc-800/50 text-zinc-500 border-zinc-700/30', }; const QUALITY_LABEL: Record = { 'strong-bullish': 'Strong Bullish', 'moderate-bullish': 'Moderate Bullish', 'weak-bullish': 'Weak Bullish', 'mixed': 'Mixed', 'weak-bearish': 'Weak Bearish', 'moderate-bearish': 'Moderate Bearish', 'strong-bearish': 'Strong Bearish', 'sparse': 'Sparse Data', }; const BODY_BADGE: Record = { bull: 'bg-green-600/15 text-green-400', bear: 'bg-red-600/15 text-red-400', exit: 'bg-amber-600/15 text-amber-400', }; // --------------------------------------------------------------------------- // Panel // --------------------------------------------------------------------------- export function ConfluencePanel() { const activeSymbol = useActiveSymbol((s) => s.activeSymbol); const [symbol, setSymbol] = useState(activeSymbol); const [selectedRackId, setSelectedRackId] = useState(null); // Racks const [racks, setRacks] = useState<{ system: Array<{ id: string; name: string }>; user: Array<{ id: string; name: string }> }>({ system: [], user: [] }); useEffect(() => { api.confluence.racks().then(setRacks).catch(() => {}); }, []); // Evaluation const [evaluation, setEvaluation] = useState | null>(null); const rackId = selectedRackId ?? racks.system[0]?.id ?? null; useEffect(() => { if (!symbol) return; api.confluence.evaluation(symbol, { rackId: rackId ?? undefined, limit: 6 }) .then((r) => setEvaluation(r as Record)) .catch(() => setEvaluation(null)); }, [symbol, rackId]); // Scorecard const [scorecard, setScorecard] = useState<{ slots: Array>; note: string } | null>(null); useEffect(() => { if (!symbol) return; api.confluence.scorecard(symbol) .then((r) => setScorecard(r as { slots: Array>; note: string })) .catch(() => setScorecard(null)); }, [symbol]); // Slots const [slotMeta, setSlotMeta] = useState>(new Map()); useEffect(() => { api.confluence.slots() .then((r) => setSlotMeta(new Map(r.slots.map((s) => [s.id, { name: s.name, body: s.body, explain: s.explain }])))) .catch(() => {}); }, []); const latest = (evaluation?.latest as Record) ?? null; const quality = (latest?.quality as Quality) ?? null; const assessments = (latest?.assessments as Array<{ id: string; state: string; note?: string }>) ?? []; const bullEvidence = Number(latest?.bullEvidence ?? 0); const bearEvidence = Number(latest?.bearEvidence ?? 0); const change = evaluation?.change as { changed: boolean; changeType: string; previous: string | null } | null; return (
{/* Header */}
setSymbol(e.target.value.toUpperCase().trim())} className="w-24 rounded bg-bg-secondary border border-fg-muted/20 px-2 py-1.5 text-sm text-fg" maxLength={12} />
{/* Picture quality + evidence summary */} {quality ? (
{QUALITY_LABEL[quality]}
Bull {bullEvidence.toFixed(1)} {' '}·{' '} Bear {bearEvidence.toFixed(1)} {' '}·{' '} Net = 0 ? 'text-green-400' : 'text-red-400'}> {(bullEvidence - bearEvidence).toFixed(1)}
{change?.changed && (
{change.changeType === 'improved' ? 'Improved' : 'Deteriorated'} from {change.previous ?? '?'}
)}
) : evaluation !== null ? (
No evaluations yet for {symbol}. Data will appear once the confluence evaluator runs.
) : null} {/* Slot evidence grid */} {assessments.length > 0 && (
{assessments.map((a) => { const meta = slotMeta.get(a.id); const fired = a.state === 'fired'; return (
{meta?.name ?? a.id} {meta?.body ?? '?'}
{a.note &&
{a.note}
}
); })}
)} {/* Scorecard */} {scorecard && scorecard.slots.length > 0 && (

{scorecard.note}

{scorecard.slots .sort((a, b) => ((b.hitRate as number) ?? 0) - ((a.hitRate as number) ?? 0)) .map((s) => ( ))}
Slot Fires Hit Rate Reliability
{slotMeta.get(s.slotId as string)?.name ?? (s.slotId as string)} {s.fires as number} {s.hitRate != null ? `${((s.hitRate as number) * 100).toFixed(0)}%` : '-'} {(s.reliabilityWeight as number).toFixed(2)}
)}
); }