import type { ScanOpportunityRow } from "@/lib/dashboard-types"; export const REGIONS = [ { key: "east_asia", labelEn: "East Asia", labelZh: "东亚", sort: 1 }, { key: "southeast_asia", labelEn: "Southeast Asia", labelZh: "东南亚", sort: 2 }, { key: "central_asia", labelEn: "Central / South Asia", labelZh: "中亚 / 南亚", sort: 3 }, { key: "west_asia", labelEn: "West Asia / Middle East", labelZh: "西亚 / 中东", sort: 4 }, { key: "europe_africa", labelEn: "Europe / Africa", labelZh: "欧洲 / 非洲", sort: 5 }, { key: "south_america", labelEn: "Latin America", labelZh: "拉美", sort: 6 }, { key: "north_america", labelEn: "North America", labelZh: "北美", sort: 7 }, ] as const; export type RegionKey = (typeof REGIONS)[number]["key"]; const REGION_KEYS = new Set(REGIONS.map((r) => r.key)); const CITY_REGION: Record = { beijing: "east_asia", busan: "east_asia", chengdu: "east_asia", chongqing: "east_asia", guangzhou: "east_asia", "hong kong": "east_asia", qingdao: "east_asia", seoul: "east_asia", shanghai: "east_asia", shenzhen: "east_asia", taipei: "east_asia", tokyo: "east_asia", wuhan: "east_asia", jakarta: "southeast_asia", "kuala lumpur": "southeast_asia", manila: "southeast_asia", singapore: "southeast_asia", karachi: "central_asia", lucknow: "central_asia", ankara: "west_asia", istanbul: "west_asia", jeddah: "west_asia", "tel aviv": "west_asia", amsterdam: "europe_africa", "cape town": "europe_africa", helsinki: "europe_africa", london: "europe_africa", madrid: "europe_africa", milan: "europe_africa", moscow: "europe_africa", munich: "europe_africa", paris: "europe_africa", warsaw: "europe_africa", "buenos aires": "south_america", "sao paulo": "south_america", "mexico city": "north_america", atlanta: "north_america", austin: "north_america", chicago: "north_america", dallas: "north_america", denver: "north_america", houston: "north_america", "los angeles": "north_america", miami: "north_america", "new york": "north_america", "panama city": "south_america", "san francisco": "north_america", seattle: "north_america", toronto: "north_america", wellington: "east_asia", }; function normalizeCity(value?: string | null) { return String(value || "").trim().toLowerCase() .replace(/[_-]+/g, " ").replace(/\s+/g, " "); } export function getCityRegion(row: ScanOpportunityRow): RegionKey | null { const key = normalizeCity(row.city || row.city_display_name || row.display_name); return CITY_REGION[key] || null; } /** Default region on first load — East Asia. */ export function getDefaultRegion(): RegionKey { return "east_asia"; } export interface ContinentGroup { key: RegionKey | "active_signals"; labelEn: string; labelZh: string; sort: number; rows: ScanOpportunityRow[]; activeCount: number; watchCount: number; hotCity: string | null; localTimeRange: string | null; } export function isActiveSignal(row: ScanOpportunityRow): boolean { const decision = String(row.ai_decision || row.v4_metar_decision || "").toLowerCase(); if (decision.includes("approve")) return true; if (row.tradable && row.active) return true; return false; } export function isWatchSignal(row: ScanOpportunityRow): boolean { const decision = String(row.ai_decision || row.v4_metar_decision || row.signal_status || "").toLowerCase(); if (decision.includes("watch")) return true; if (decision.includes("monitor")) return true; if (!row.tradable && row.active) return true; return false; } export function isDeadSignal(row: ScanOpportunityRow): boolean { if (row.closed) return true; const decision = String(row.ai_decision || row.v4_metar_decision || "").toLowerCase(); if (decision.includes("veto")) return true; return false; } export function getSignalState(row: ScanOpportunityRow): "active" | "watch" | "closed" | "data" { if (isDeadSignal(row)) return "closed"; if (isActiveSignal(row)) return "active"; if (isWatchSignal(row)) return "watch"; return "data"; } export function getSignalLabel(state: ReturnType, isEn: boolean): string { switch (state) { case "active": return isEn ? "◆ Active" : "◆ 活跃"; case "watch": return isEn ? "● Watch" : "● 观察"; case "closed": return isEn ? "○ Closed" : "○ 关闭"; case "data": return isEn ? "! Data" : "! 数据"; } } export type GapColor = "green" | "orange" | "slate" | "gray" | "red"; export function getGapColor(row: ScanOpportunityRow): GapColor { const gap = Number(row.signed_gap ?? row.gap_to_target); if (!Number.isFinite(gap)) return "gray"; if (gap >= 2) return "green"; if (gap >= 0) return "orange"; if (gap < -5) return "gray"; return "slate"; } export const GAP_COLOR_MAP: Record = { green: "text-emerald-600", orange: "text-amber-600", slate: "text-slate-500", gray: "text-slate-400", red: "text-red-500", }; export function formatPrice(_midpoint?: number | null, _ask?: number | null, _bid?: number | null): string { return "--"; } export function formatSpreadLiquidity(_spread?: number | null, _liquidity?: number | null): string { return "--"; } export function buildContinentGroups(rows: ScanOpportunityRow[], isEn: boolean): ContinentGroup[] { const regionMap = new Map(); for (const row of rows) { const region = getCityRegion(row) || "unknown"; if (!regionMap.has(region)) regionMap.set(region, []); regionMap.get(region)!.push(row); } const groups: ContinentGroup[] = []; // Active Signals virtual group const activeRows = rows.filter((r) => isActiveSignal(r)); if (activeRows.length > 0) { const hotRow = activeRows.reduce((best, r) => Number(r.edge_percent || 0) > Number(best.edge_percent || 0) ? r : best ); groups.push({ key: "active_signals", labelEn: "Active Signals", labelZh: "活跃信号", sort: 0, rows: activeRows, activeCount: activeRows.filter((r) => isActiveSignal(r)).length, watchCount: activeRows.filter((r) => isWatchSignal(r)).length, hotCity: hotRow?.city_display_name || hotRow?.city || null, localTimeRange: null, }); } for (const region of REGIONS) { const regionRows = regionMap.get(region.key) || []; if (regionRows.length === 0) continue; const activeCount = regionRows.filter((r) => isActiveSignal(r)).length; const watchCount = regionRows.filter((r) => isWatchSignal(r)).length; const sorted = [...regionRows].sort((a, b) => Number(b.final_score || 0) - Number(a.final_score || 0) ); const hotCity = sorted[0]?.city_display_name || sorted[0]?.city || null; const times = regionRows .map((r) => String(r.local_time || "").trim()) .filter(Boolean) .sort(); const ltRange = times.length >= 2 ? `${times[0]}-${times[times.length - 1]}` : times[0] || null; groups.push({ key: region.key, labelEn: region.labelEn, labelZh: region.labelZh, sort: region.sort, rows: regionRows, activeCount, watchCount, hotCity, localTimeRange: ltRange, }); } groups.sort((a, b) => a.sort - b.sort); return groups; } export function getDefaultExpanded(groups: ContinentGroup[]): Set { const expanded = new Set(); for (const g of groups) { if (g.key === "active_signals") { expanded.add(g.key); } else if (g.activeCount > 0 || g.watchCount > 0) { expanded.add(g.key); } } return expanded; }