83 行
3.3 KiB
TypeScript
83 行
3.3 KiB
TypeScript
import {
|
|
RadarAttributionService,
|
|
normalizeTrafficChannel,
|
|
calculateAttributionFunnel,
|
|
ChannelAttributionEvent,
|
|
} from '../../../src/modules/crm/service/attribution';
|
|
|
|
describe('Point 46: 访客雷达多渠道来源归因与转化漏斗分析', () => {
|
|
let service: RadarAttributionService;
|
|
|
|
beforeEach(() => {
|
|
service = new RadarAttributionService();
|
|
});
|
|
|
|
describe('1. 流量渠道归一化解析 (normalizeTrafficChannel)', () => {
|
|
it('应精准识别 NFC 碰一碰与展位二维码', () => {
|
|
expect(normalizeTrafficChannel('nfc', 'tap')).toBe('nfc_tap');
|
|
expect(normalizeTrafficChannel('booth', 'qrcode')).toBe('booth_qr');
|
|
});
|
|
|
|
it('应精准识别 WhatsApp 与 LinkedIn 国际社交渠道', () => {
|
|
expect(normalizeTrafficChannel('whatsapp', 'im')).toBe('whatsapp_chat');
|
|
expect(normalizeTrafficChannel('linkedin', 'social')).toBe('linkedin_share');
|
|
expect(normalizeTrafficChannel('email', 'sig')).toBe('email_signature');
|
|
});
|
|
|
|
it('未知或直接访问归入 direct_or_organic', () => {
|
|
expect(normalizeTrafficChannel('', '')).toBe('direct_or_organic');
|
|
expect(normalizeTrafficChannel(undefined, undefined)).toBe('direct_or_organic');
|
|
});
|
|
});
|
|
|
|
describe('2. calculateAttributionFunnel (5阶段留资转化漏斗计算)', () => {
|
|
const mockEvents: ChannelAttributionEvent[] = [
|
|
// 访客 1: 全链路完成
|
|
{ visitorId: 'v1', channel: 'nfc_tap', stage: 'impression' },
|
|
{ visitorId: 'v1', channel: 'nfc_tap', stage: 'interactive' },
|
|
{ visitorId: 'v1', channel: 'nfc_tap', stage: 'ai_consult' },
|
|
{ visitorId: 'v1', channel: 'nfc_tap', stage: 'lead_submitted' },
|
|
{ visitorId: 'v1', channel: 'nfc_tap', stage: 'meeting_booked' },
|
|
|
|
// 访客 2: 停留于 AI 咨询
|
|
{ visitorId: 'v2', channel: 'nfc_tap', stage: 'impression' },
|
|
{ visitorId: 'v2', channel: 'nfc_tap', stage: 'interactive' },
|
|
{ visitorId: 'v2', channel: 'nfc_tap', stage: 'ai_consult' },
|
|
|
|
// 访客 3: WhatsApp 渠道完成留资
|
|
{ visitorId: 'v3', channel: 'whatsapp_chat', stage: 'impression' },
|
|
{ visitorId: 'v3', channel: 'whatsapp_chat', stage: 'lead_submitted' },
|
|
|
|
// 访客 4: 仅浏览展位
|
|
{ visitorId: 'v4', channel: 'booth_qr', stage: 'impression' },
|
|
];
|
|
|
|
it('应精确统计各漏斗阶段的独立访客数与流失率', () => {
|
|
const funnel = calculateAttributionFunnel(mockEvents);
|
|
|
|
expect(funnel.totalVisitors).toBe(4);
|
|
expect(funnel.stages.impression).toBe(4);
|
|
expect(funnel.stages.interactive).toBe(2);
|
|
expect(funnel.stages.ai_consult).toBe(2);
|
|
expect(funnel.stages.lead_submitted).toBe(2);
|
|
expect(funnel.stages.meeting_booked).toBe(1);
|
|
|
|
// 留资总体转化率: 2 / 4 = 50%
|
|
expect(funnel.overallConversionRate).toBe(0.5);
|
|
});
|
|
|
|
it('应支持按渠道拆分多维度漏斗表现', () => {
|
|
const breakdown = service.analyzeChannelPerformance(mockEvents);
|
|
|
|
expect(breakdown['nfc_tap']).toBeDefined();
|
|
expect(breakdown['nfc_tap'].visitors).toBe(2);
|
|
expect(breakdown['nfc_tap'].leads).toBe(1);
|
|
expect(breakdown['nfc_tap'].leadRate).toBe(0.5);
|
|
|
|
expect(breakdown['whatsapp_chat'].visitors).toBe(1);
|
|
expect(breakdown['whatsapp_chat'].leads).toBe(1);
|
|
expect(breakdown['whatsapp_chat'].leadRate).toBe(1.0);
|
|
});
|
|
});
|
|
});
|