Files
stonks-oracle/frontend/src/pages/OpsCoverage.tsx
T

105 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCoverageGaps, useSymbolCoverage } from '../api/hooks';
import { LoadingSpinner, StatusBadge, Card } from '../components/ui';
export function OpsCoveragePage() {
const { data: gaps, isLoading: gapsLoading } = useCoverageGaps();
const { data: coverage, isLoading: covLoading } = useSymbolCoverage();
if (gapsLoading || covLoading) return <LoadingSpinner />;
const missing = (gaps?.missing_source_types ?? []) as Array<Record<string, unknown>>;
const stale = (gaps?.stale_sources ?? []) as Array<Record<string, unknown>>;
const matrix = (coverage ?? []) as Array<Record<string, unknown>>;
return (
<div className="space-y-6">
<h1 className="text-xl font-semibold text-gray-100">Source Coverage</h1>
{/* Coverage Matrix */}
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Company × Source Type Matrix</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-surface-700 text-left text-gray-500">
<th className="px-3 py-2">Ticker</th>
<th className="px-3 py-2">Name</th>
<th className="px-3 py-2 text-center">Market</th>
<th className="px-3 py-2 text-center">News</th>
<th className="px-3 py-2 text-center">Filings</th>
<th className="px-3 py-2 text-center">Web</th>
<th className="px-3 py-2 text-center">Broker</th>
<th className="px-3 py-2 text-center">Total</th>
</tr>
</thead>
<tbody>
{matrix.map((row, i) => (
<tr key={i} className="border-b border-surface-700/50">
<td className="px-3 py-2 font-mono font-semibold text-brand-300">{String(row.ticker)}</td>
<td className="px-3 py-2 text-gray-300">{String(row.legal_name)}</td>
<CoverageCell count={row.market_sources as number} />
<CoverageCell count={row.news_sources as number} />
<CoverageCell count={row.filings_sources as number} />
<CoverageCell count={row.web_scrape_sources as number} />
<CoverageCell count={row.broker_sources as number} />
<td className="px-3 py-2 text-center text-gray-300">{String(row.active_sources)}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
{/* Missing Source Types */}
{missing.length > 0 && (
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Missing Source Types ({missing.length})</h2>
<div className="space-y-2">
{missing.map((m, i) => {
const activeTypes = (m.active_types as string[]) ?? [];
const expected = (m.expected_types as string[]) ?? [];
const missingTypes = expected.filter((t) => !activeTypes.includes(t));
return (
<div key={i} className="flex items-center gap-3 rounded border border-yellow-700/30 bg-yellow-900/10 p-2">
<span className="font-mono font-semibold text-brand-300">{String(m.ticker)}</span>
<span className="text-xs text-gray-500">missing:</span>
{missingTypes.map((t) => (
<StatusBadge key={t} status={t} />
))}
</div>
);
})}
</div>
</Card>
)}
{/* Stale Sources */}
{stale.length > 0 && (
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Stale Sources ({stale.length})</h2>
<div className="space-y-2">
{stale.map((s, i) => (
<div key={i} className="flex items-center justify-between rounded border border-red-700/30 bg-red-900/10 p-2">
<div className="flex items-center gap-3">
<span className="font-mono font-semibold text-brand-300">{String(s.ticker)}</span>
<StatusBadge status={String(s.source_type)} />
<span className="text-xs text-gray-400">{String(s.source_name)}</span>
</div>
<div className="text-xs text-gray-500">
Last success: {s.last_success ? new Date(String(s.last_success)).toLocaleString() : 'never'}
{s.recent_failures ? ` | ${s.recent_failures} failures (24h)` : ''}
</div>
</div>
))}
</div>
</Card>
)}
</div>
);
}
function CoverageCell({ count }: { count: number }) {
const color = count > 0 ? 'text-green-400' : 'text-red-400';
return <td className={`px-3 py-2 text-center font-mono ${color}`}>{count}</td>;
}