import { computePlacementRecencyScore } from "@/lib/scan-recency"; export interface RawPost { externalId?: string; text: string; permalink?: string; authorName?: string; postedAt?: Date; likeCount?: number; replyCount?: number; repostCount?: number; } export interface RankedPost extends RawPost { score: number; } function recencyBoost(postedAt?: Date): number { if (!postedAt) return 0.5; const hours = (Date.now() - postedAt.getTime()) / (1000 * 60 * 60); if (hours <= 6) return 1.5; if (hours <= 24) return 1.2; if (hours <= 72) return 1.0; return 0.7; } export function computeScore(post: RawPost): number { const likes = post.likeCount ?? 0; const replies = post.replyCount ?? 0; const reposts = post.repostCount ?? 0; const engagement = likes * 1.0 + replies * 2.0 + reposts * 1.5; return engagement * recencyBoost(post.postedAt); } /** 置入海巡排序:互動為輔,時間窗口為主 */ export function computePlacementScore(post: RawPost): number { const likes = post.likeCount ?? 0; const replies = post.replyCount ?? 0; const reposts = post.repostCount ?? 0; const engagement = likes * 0.6 + replies * 1.2 + reposts * 0.8; const recency = computePlacementRecencyScore(post.postedAt); return recency * 2.5 + Math.log10(engagement + 1) * 12; } function normalizeText(text: string): string { return text.trim().toLowerCase().replace(/\s+/g, " "); } export function rankAndDedupe(posts: RawPost[], limit = 20): RankedPost[] { const seen = new Set(); const ranked: RankedPost[] = []; for (const post of posts) { const key = post.externalId ?? `${post.authorName ?? ""}:${normalizeText(post.text).slice(0, 120)}`; if (seen.has(key)) continue; seen.add(key); ranked.push({ ...post, score: computeScore(post) }); } return ranked.sort((a, b) => b.score - a.score).slice(0, limit); }