import type { ChannelPoint, DailyPoint, DayKey, Event, EventType, Kpis, Lead } from "@/lib/domain" import { clamp, toDayKey } from "@/lib/time" export function computeKpis(args: { events: Event[]; leads: Lead[] }): Kpis { const uv = countUv(args.events) const clicks = countType(args.events, "click") const submits = countType(args.events, "submit") const claims = countType(args.events, "claim") const redeems = countType(args.events, "redeem") const submitRate = uv ? submits / uv : 0 const redeemRate = submits ? redeems / submits : 0 return { uv, clicks, submits, claims, redeems, submitRate, redeemRate } } export function dailySeries(args: { events: Event[]; days: number }): DailyPoint[] { const map = new Map() const types: EventType[] = ["view", "submit", "redeem"] for (const e of args.events) { if (!types.includes(e.type)) continue const day = toDayKey(e.ts) const cur = map.get(day) ?? { day, uv: 0, submits: 0, redeems: 0 } if (e.type === "view") cur.uv += 1 if (e.type === "submit") cur.submits += 1 if (e.type === "redeem") cur.redeems += 1 map.set(day, cur) } const keys = Array.from(map.keys()).sort((a, b) => a.localeCompare(b)) const tail = keys.slice(Math.max(0, keys.length - args.days)) return tail.map((k) => map.get(k)!).map((p) => ({ ...p, uv: clamp(p.uv, 0, 999999) })) } export function channelSeries(args: { events: Event[]; max: number }): ChannelPoint[] { const map = new Map() for (const e of args.events) { const channel = (e.meta?.utm_source as string | undefined) ?? "direct" const cur = map.get(channel) ?? { channel, uv: 0, submits: 0, redeems: 0 } if (e.type === "view") cur.uv += 1 if (e.type === "submit") cur.submits += 1 if (e.type === "redeem") cur.redeems += 1 map.set(channel, cur) } return Array.from(map.values()) .sort((a, b) => b.uv - a.uv) .slice(0, args.max) } export function countUv(events: Event[]) { const views = events.filter((e) => e.type === "view") return views.length } export function countType(events: Event[], type: EventType) { return events.reduce((acc, e) => acc + (e.type === type ? 1 : 0), 0) }