/** * Cost Optimization Page (Story 3.5) * * Route: /cost-optimization * * Features: * - Total potential savings banner at top * - Recommendations table: current model → recommended model, complexity tier, * monthly savings, confidence level, call volume, agent * - Confidence badges: low (gray), medium (yellow), high (green) * - Filter by agent dropdown */ import React, { useState, useMemo } from 'react'; import { useApi } from '../hooks/useApi'; import { getOptimizationRecommendations, getAgents } from '../api/client'; import type { OptimizationRecommendationsData, OptimizationRecommendation } from '../api/client'; // ─── Helpers ──────────────────────────────────────────────────────── function formatCost(usd: number): string { if (usd < 0.01) return `$${usd.toFixed(4)}`; if (usd < 1) return `$${usd.toFixed(3)}`; return `$${usd.toFixed(2)}`; } function confidenceBadge(confidence: string): React.ReactElement { switch (confidence) { case 'high': return ( High ); case 'medium': return ( Medium ); default: return ( Low ); } } function tierBadge(tier: string): React.ReactElement { const colors: Record = { simple: 'bg-blue-50 text-blue-700 border-blue-200', moderate: 'bg-purple-50 text-purple-700 border-purple-200', complex: 'bg-orange-50 text-orange-700 border-orange-200', expert: 'bg-red-100 text-red-900 border-red-300', }; const cls = colors[tier] || 'bg-gray-50 text-gray-700 border-gray-200'; return ( {tier} ); } function formatNumber(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return String(n); } // ─── Main Component ───────────────────────────────────────────────── type Period = '7' | '14' | '30'; const PERIOD_OPTIONS: { value: Period; label: string }[] = [ { value: '7', label: '7 days' }, { value: '14', label: '14 days' }, { value: '30', label: '30 days' }, ]; export function CostOptimization(): React.ReactElement { const [period, setPeriod] = useState('7'); const [agentFilter, setAgentFilter] = useState(''); const agents = useApi(() => getAgents(), []); const { data, loading, error } = useApi( () => getOptimizationRecommendations({ period: Number(period), limit: 50, agentId: agentFilter || undefined, }), [period, agentFilter], ); const recommendations = data?.recommendations ?? []; // Calculate total savings const totalSavings = useMemo( () => recommendations.reduce((sum, r) => sum + (r.monthlySavings ?? 0), 0), [recommendations], ); // Sort: highest savings first const sortedRecs = useMemo( () => [...recommendations].sort((a, b) => (b.monthlySavings ?? 0) - (a.monthlySavings ?? 0)), [recommendations], ); return (
{/* Header */}

Cost Optimization

Model recommendations to reduce costs without sacrificing quality.

{PERIOD_OPTIONS.map((opt) => ( ))}
{/* Savings Banner */} {!loading && !error && recommendations.length > 0 && (

Total Potential Monthly Savings

{formatCost(totalSavings)}

from {recommendations.length} recommendation{recommendations.length !== 1 ? 's' : ''}

💰
)} {/* Filters */}
{/* Content */} {loading ? (
{[1, 2, 3, 4, 5].map((i) => (
))}
) : error ? (

Failed to load recommendations

{error}

) : recommendations.length === 0 ? (

No optimization recommendations available.

Your models are already well-optimized, or there isn't enough data yet.

) : (
{sortedRecs.map((rec, idx) => { const isLowConfidence = rec.confidence === 'low'; const rowClass = isLowConfidence ? 'hover:bg-gray-50 transition-colors opacity-60' : 'hover:bg-gray-50 transition-colors'; return ( ); })}
Agent Current Model Recommended Complexity Call Volume Monthly Savings Confidence
{rec.agentName || rec.agentId || '—'} {rec.currentModel} {rec.recommendedModel} {tierBadge(rec.complexityTier)} {formatNumber(rec.callVolume ?? 0)} {formatCost(rec.monthlySavings ?? 0)} {isLowConfidence && ( estimated )} {confidenceBadge(rec.confidence)} {isLowConfidence && ( insufficient data )}
{/* Summary footer */}
{recommendations.length} recommendation{recommendations.length !== 1 ? 's' : ''} Total: {formatCost(totalSavings)}/mo
)}
); }