← back to Bertha

src/app/dashboard/models/page.js

97 lines

'use client';
import { useState, useEffect } from 'react';

export default function ModelsPage() {
  const [models, setModels] = useState([]);
  const [predictions, setPredictions] = useState([]);
  const [calibration, setCalibration] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function load() {
      try {
        const res = await fetch('/api/models');
        const data = await res.json();
        setModels(data.models || []);
        setPredictions(data.predictions || []);
        setCalibration(data.calibration || []);
      } catch (err) { console.error(err); }
      setLoading(false);
    }
    load();
  }, []);

  async function runPredictions() {
    try {
      const res = await fetch('/api/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'run_predictions' }) });
      const data = await res.json();
      alert(`Generated ${data.predictions_made || 0} predictions`);
      window.location.reload();
    } catch (err) { alert('Error: ' + err.message); }
  }

  if (loading) return <div className="flex items-center justify-center h-64"><div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full" /></div>;

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">Models & Predictions</h1>
          <p className="text-gray-500 text-sm">Probabilistic forecasting and calibration</p>
        </div>
        <button onClick={runPredictions} className="btn btn-primary">Run Predictions</button>
      </div>

      <div className="grid grid-cols-4 gap-4">
        <div className="card"><p className="text-xs text-gray-500 uppercase mb-1">Model Versions</p><p className="stat-value text-blue-400">{models.length}</p></div>
        <div className="card"><p className="text-xs text-gray-500 uppercase mb-1">Total Predictions</p><p className="stat-value text-purple-400">{predictions.length}</p></div>
        <div className="card"><p className="text-xs text-gray-500 uppercase mb-1">Avg Brier Score</p><p className="stat-value text-green-400">{calibration.length > 0 ? (calibration.reduce((a, c) => a + (c.brier_score || 0), 0) / calibration.length).toFixed(4) : 'N/A'}</p></div>
        <div className="card"><p className="text-xs text-gray-500 uppercase mb-1">Active Models</p><p className="stat-value text-yellow-400">{models.filter(m => m.active).length}</p></div>
      </div>

      {/* Model Versions */}
      <div className="card">
        <h2 className="font-semibold mb-3">Model Versions</h2>
        <table>
          <thead><tr><th>ID</th><th>Type</th><th>Version</th><th>Status</th><th>Metrics</th><th>Created</th></tr></thead>
          <tbody>
            {models.map(m => (
              <tr key={m.id}>
                <td className="font-mono">#{m.id}</td>
                <td className="font-medium">{m.model_type}</td>
                <td>{m.version_tag}</td>
                <td><span className={`badge ${m.active ? 'badge-green' : 'badge-yellow'}`}>{m.active ? 'Active' : 'Inactive'}</span></td>
                <td className="text-xs text-gray-400 font-mono">{JSON.stringify(m.metrics || {}).substring(0, 40)}</td>
                <td className="text-gray-400 text-sm">{new Date(m.created_at).toLocaleDateString()}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Predictions */}
      <div className="card">
        <h2 className="font-semibold mb-3">Recent Predictions</h2>
        <table>
          <thead><tr><th>Market</th><th>P(Yes)</th><th>Confidence</th><th>Edge</th><th>Mid</th><th>Signal</th><th>Time</th></tr></thead>
          <tbody>
            {predictions.length === 0 ? (
              <tr><td colSpan={7} className="text-center text-gray-500 py-8">No predictions yet. Ingest markets and forecasts first.</td></tr>
            ) : predictions.map((p, i) => (
              <tr key={i}>
                <td className="font-mono text-xs">{p.condition_id?.substring(0, 16)}...</td>
                <td className="font-mono text-green-400">{(p.p_yes * 100).toFixed(1)}%</td>
                <td className="font-mono">{p.confidence ? (p.confidence * 100).toFixed(0) + '%' : '-'}</td>
                <td className={`font-mono ${(p.edge || 0) > 0 ? 'text-green-400' : 'text-red-400'}`}>{p.edge ? (p.edge * 100).toFixed(2) + '%' : '-'}</td>
                <td className="font-mono">{p.market_mid ? (p.market_mid * 100).toFixed(1) + '%' : '-'}</td>
                <td><span className={`badge ${p.recommendation?.startsWith('BUY') ? 'badge-green' : p.recommendation === 'SKIP' ? 'badge-red' : 'badge-yellow'}`}>{p.recommendation || '-'}</span></td>
                <td className="text-gray-400 text-sm">{new Date(p.asof_ts).toLocaleString()}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}