import Link from "next/link"; import { PanelShell } from "@/components/panel-shell"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { getLongTermVolume, getWeeklyGrowth, getProviderShareTrend, getUnderSupportedBrands, getWmiOpportunities, getPeakHeatmap, getCacheHitTrend, getEfficiencyTrend, } from "@/lib/sase/vin-trends"; export const dynamic = "force-dynamic"; const DAYS_OF_WEEK = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"]; const PROVIDER_COLORS: Record = { cache: "bg-slate-400", "parts-catalogs": "bg-blue-500", pcat: "bg-blue-500", pl24: "bg-emerald-500", emex: "bg-amber-500", "vin-api": "bg-purple-500", corgi: "bg-pink-500", none: "bg-muted", aborted: "bg-destructive", unknown: "bg-zinc-400", }; export default async function TrendsPage() { const [volume, growth, shareTrend, underSupported, wmiOpps, heatmap, cache, efficiency] = await Promise.all([ getLongTermVolume(90), getWeeklyGrowth(12), getProviderShareTrend(30), getUnderSupportedBrands(30, 20, 0.75), getWmiOpportunities(30, 15, 0.7), getPeakHeatmap(30), getCacheHitTrend(30), getEfficiencyTrend(30), ]); const volMax = Math.max(1, ...volume.map((v) => v.total)); const cacheMax = Math.max(1, ...cache.map((c) => c.total)); return (
← VIN Decode dashboard

Trends & Kapasite

Uzun dönem volume, provider mix, peak saatler, cache trend, verimlilik skoru.

{/* 1. Long-term volume */} 1. Son 90 gün — günlük decode hacmi
{volume.map((v) => (
{v.succeeded > 0 && (
)}
))}
{volume[0]?.date} maks {volMax}/gün {volume[volume.length - 1]?.date}
{/* 2. Weekly growth */} 2. Haftalık büyüme (son 12 hafta) Hafta başı Sorgu WoW büyüme {growth.map((g) => ( {g.weekStart} {g.volume.toLocaleString("tr-TR")} 0 ? "text-emerald-600" : "text-destructive" }`} > {g.growthPct == null ? "—" : `${g.growthPct > 0 ? "+" : ""}${(g.growthPct * 100).toFixed(1)}%`} ))}
{/* 3. Provider share trend */} 3. Provider yük dağılımı (son 30g) {shareTrend.length === 0 ? (

Veri yok.

) : ( <>
{shareTrend.map((d) => { const providers = Object.entries(d.byProvider).sort( ([a], [b]) => a.localeCompare(b), ); return (
` ${p}: ${c}`) .join("\n")}`} className="flex flex-1 min-w-[3px] flex-col-reverse overflow-hidden rounded-sm bg-muted" style={{ height: "100%" }} > {providers.map(([p, c]) => (
))}
); })}
{Array.from( new Set(shareTrend.flatMap((d) => Object.keys(d.byProvider))), ) .sort() .map((p) => (
{p}
))}
)} {/* 4. Under-supported brands */} 4. Düşük destekli markalar (son 30g, hacim ≥20, başarı ≤75%) {underSupported.length === 0 ? (

Düşük destekli marka yok — tüm markalarda başarı yüksek.

) : ( Marka Toplam Fail Kullanıcı Başarı {underSupported.map((b) => ( {b.brandSlug ?? "?"} {b.brandName} {b.total.toLocaleString("tr-TR")} {b.failed.toLocaleString("tr-TR")} {b.uniqueUsers} {(b.successRate * 100).toFixed(1)}% ))}
)}

Yeni provider / decoder yatırım sinyali. Üst sıradakilere ek kapasite (PCAT/PL24 hesabı, ML decoder fine-tune, vb.).

{/* 4b. WMI opportunity radar — data-driven Faz 4 */} 4b. WMI fırsat radarı (son 30g, hacim ≥15, başarı ≤70%) — sıradaki ekleme adayları, talebe göre sıralı {wmiOpps.length === 0 ? (

Düşük başarılı WMI yok — talep edilen üretici kodları yeterince karşılanıyor.

) : ( WMI Durum / marka Talep (kullanıcı) Toplam Fail Başarı {wmiOpps.map((w) => ( {w.wmi} {w.brandMatched === 0 ? ( eşleşmesiz WMI ) : ( <> {w.topBrandSlug ?? "?"} {w.topBrandName && ( {w.topBrandName} )} · decode zayıf )} {w.topResultKind && ( ({w.topResultKind}) )} {w.uniqueUsers} {w.total.toLocaleString("tr-TR")} {w.failed.toLocaleString("tr-TR")} {(w.successRate * 100).toFixed(1)}% ))}
)}

"eşleşmesiz WMI" = hiç markaya bağlanamayan üretici kodu → eklenince tamamen yeni kapsama. Talep (farklı kullanıcı) sütununa göre sıralı: en çok kişinin isteyip alamadığı WMI en üstte. Faz 4'ün el yordamı yerine veri-güdümlü hali.

{/* 5. Peak heatmap */} 5. Peak saat heatmap (son 30g, saat × gün)
{Array.from({ length: 24 }, (_, h) => ( ))} {DAYS_OF_WEEK.map((dayLabel, day) => ( {Array.from({ length: 24 }, (_, hour) => { const cell = heatmap.cells.find( (c) => c.dayOfWeek === day && c.hour === hour, ); const count = cell?.count ?? 0; const intensity = heatmap.max > 0 ? count / heatmap.max : 0; return ( ))}
· {h.toString().padStart(2, "0")}
{dayLabel} 0 ? `rgba(59, 130, 246, ${0.1 + intensity * 0.8})` : "transparent", }} title={`${dayLabel} ${hour}:00 → ${count}`} /> ); })}

Maks {heatmap.max}/saat. B2B çalışma saatlerinde yoğunluk → infra ölçekleme planlaması.

{/* 6. Cache hit trend */} 6. Cache hit trendi (son 30g)
{cache.map((c) => (
0 ? `${c.cacheHitRate * 100}%` : "1px", opacity: c.total > 0 ? 1 : 0.2, }} className="flex-1 min-w-[3px] rounded-sm bg-emerald-500" /> ))}
{cache[0]?.date} 30g ortalaması {pct(avgCacheRate(cache))} {cache[cache.length - 1]?.date}
{/* 7. Efficiency composite */} 7. Verimlilik composite skoru (success 50% · cache 20% · latency 20% · 1-fallback 10%) {efficiency.length === 0 ? (

Veri yok.

) : ( Gün Skor Success Cache P95 Fallback {efficiency .slice() .reverse() .slice(0, 14) .map((e) => ( {e.date} = 0.8 ? "text-emerald-600" : e.score >= 0.6 ? "" : "text-destructive" }`} > {(e.score * 100).toFixed(0)} {pct(e.successRate)} {pct(e.cacheHitRate)} {e.p95 != null ? `${e.p95}ms` : "—"} {pct(e.fallbackRate)} ))}
)}
); } function pct(v: number): string { return `${(v * 100).toFixed(1)}%`; } function avgCacheRate(rows: Array<{ total: number; cacheHits: number }>): number { const total = rows.reduce((a, r) => a + r.total, 0); const hits = rows.reduce((a, r) => a + r.cacheHits, 0); return total > 0 ? hits / total : 0; }