import db from '../config/database'; import { logger } from '../utils/logger'; export interface CompetitorProduct { platform: string; productId: string; price: number; sales: number; url: string; imageFingerprint: string; } /** * CompetitorService: 全球同行嗅探器核心逻辑 (BIZ_EXT_09) * 负责跨平台同款识别、价格监听与销量回测 */ export class CompetitorService { /** * 基于图像指纹识别跨平台同款 */ static async findCrossPlatformMatches(imageFingerprint: string): Promise { try { // 模拟跨平台搜索逻辑 // 实际生产环境应通过向量数据库 (如 Milvus/Pinecone) 检索相似图像指纹 const matches = await db('cf_product_competitor') .where({ imageFingerprint }) .select('*'); return matches; } catch (error: any) { logger.error(`[CompetitorSniffer] Search failed: ${error.message}`); return []; } } /** * 监听同行改价行为并触发告警 */ static async trackPriceChanges(productId: string, currentPrice: number) { try { const history = await db('cf_competitor_price_history') .where({ productId }) .orderBy('created_at', 'desc') .first(); if (history && history.price !== currentPrice) { logger.info(`[CompetitorSniffer] Price change detected for ${productId}: ${history.price} -> ${currentPrice}`); // 触发 Profit Guard 自动调价逻辑 (BIZ_EXT_06) // EventBus.emit('COMPETITOR_PRICE_CHANGED', { productId, newPrice: currentPrice }); } await db('cf_competitor_price_history').insert({ productId, price: currentPrice, created_at: new Date() }); } catch (error: any) { logger.error(`[CompetitorSniffer] Price tracking failed: ${error.message}`); } } /** * 同行销量趋势回测 (爆品生命周期分析) */ static async analyzeSalesTrend(productId: string): Promise { try { const salesData = await db('cf_competitor_sales_history') .where({ productId }) .orderBy('created_at', 'asc'); // 简单的增长率计算 if (salesData.length < 2) return { status: 'STABLE', growth: 0 }; const first = salesData[0].sales; const last = salesData[salesData.length - 1].sales; const growth = ((last - first) / first) * 100; return { status: growth > 20 ? 'TRENDING' : 'STABLE', growthRate: growth.toFixed(2) + '%', points: salesData.length }; } catch (error: any) { logger.error(`[CompetitorSniffer] Sales analysis failed: ${error.message}`); return null; } } }