feat(round-12): 实体卡外贸生产工单/雷达多渠道归因漏斗/AI地道外联草拟/现代图片自适应协商 (87套件1007测试全绿)

这个提交包含在:
wikiboss
2026-09-21 02:06:55 +08:00
父节点 76f0dc17c0
当前提交 3cfbed9e6a
共有 8 个文件被更改,包括 897 次插入 和 0 次删除

查看文件

@@ -0,0 +1,111 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export interface OutreachDraftRequest {
leadName: string;
company?: string;
targetLanguage: string; // 'en' | 'ar' | 'es' | 'zh' | 'fr' | 'ja'
interestTopics?: string[];
calendarUrl?: string;
senderName?: string;
senderTitle?: string;
tone?: 'professional' | 'friendly' | 'respectful';
channel?: 'email' | 'whatsapp' | 'linkedin';
}
export interface OutreachDraftResponse {
channel: 'email' | 'whatsapp' | 'linkedin';
subject?: string;
body: string;
targetLanguage: string;
generatedAt: number;
}
/**
* 组装出海销售跟进提示词
*/
export function formatOutreachPrompt(req: OutreachDraftRequest): string {
const langCode = (req.targetLanguage || 'en').toLowerCase();
const langMap: Record<string, string> = {
ar: 'Arabic',
en: 'English',
es: 'Spanish',
fr: 'French',
ja: 'Japanese',
zh: 'Chinese',
};
const lang = langMap[langCode] || langCode;
const topics = req.interestTopics?.join(', ') || 'Smart Digital Cards & Mobile Portal';
const tone = req.tone || 'professional';
return `Task: Draft a high-converting B2B outreach message.
Target Language: ${lang} (${langCode})
Prospect Name: ${req.leadName}
Prospect Company: ${req.company || 'Their Esteemed Firm'}
Topics of Interest: ${topics}
Tone: ${tone}
Channel: ${req.channel || 'email'}
Call to Action: Include meeting booking link if available: ${req.calendarUrl || 'Available on request'}
Guidelines:
1. Swiss internationalist style: concise, credible, no hype, no emojis.
2. Maximize relevance to their browsing interests.`;
}
/**
* AI 销售潜客智能跟进邮件/消息地道草拟引擎
*/
@Provide()
export class AiOutreachDraftService extends BaseService {
/**
* 生成地道跟进草稿
*/
async generateDraft(req: OutreachDraftRequest): Promise<OutreachDraftResponse> {
const channel = req.channel || 'email';
const lang = (req.targetLanguage || 'en').toLowerCase();
const sender = req.senderName || 'OmniCard Team';
const topics = req.interestTopics && req.interestTopics.length > 0
? req.interestTopics.join(', ')
: 'OmniCard AI';
if (channel === 'whatsapp') {
let body = '';
if (lang === 'es') {
body = `Hola ${req.leadName}, un gusto saludarte. Vi tu interés en ${topics} en nuestra web. ¿Te gustaría agendar una breve llamada de 10 min? Saludos, ${sender}.`;
} else if (lang === 'ar') {
body = `مرحباً ${req.leadName}، تشرفنا باهتمامكم بـ ${topics}. يسعدنا ترتيب محادثة سريعة لمناقشة التفاصيل. تحياتي، ${sender}.`;
} else {
body = `Hi ${req.leadName}, great connecting. Noticed your interest in ${topics}. Would love to share a 10-min overview this week. Best, ${sender}.`;
}
return {
channel: 'whatsapp',
body,
targetLanguage: lang,
generatedAt: Date.now(),
};
}
// Default Email
let subject = '';
let body = '';
if (lang === 'ar') {
subject = `متابعة حلول ${topics} لشركة ${req.company || ''}`;
body = `عزيزي/عزيزتي ${req.leadName}،\n\nتحية طيبة وبعد،\n\nيسعدنا تواصلكم واهتمامكم بـ ${topics}. نود تقديم عرض مخصص يلبي تطلعات شركتكم الموقرة ${req.company || ''}.\n\nيمكنكم حجز موعد مناسب عبر الرابط:\n${req.calendarUrl || 'https://omnicard.ai/book'}\n\nوتفضلوا بقبول فائق الاحترام،\n${sender}\n${req.senderTitle || ''}`;
} else if (lang === 'es') {
subject = `Seguimiento de ${topics} para ${req.company || req.leadName}`;
body = `Estimado/a ${req.leadName},\n\nEspero que se encuentre bien. Me pongo en contacto tras notar su interés en ${topics}.\n\nNos encantaría explorar cómo nuestra solución puede potenciar las operaciones de ${req.company || 'su empresa'}.\n\nPuede seleccionar un horario conveniente aquí:\n${req.calendarUrl || 'https://omnicard.ai/book'}\n\nAtentamente,\n${sender}\n${req.senderTitle || ''}`;
} else {
subject = `Following up: ${topics} for ${req.company || req.leadName}`;
body = `Hi ${req.leadName},\n\nI hope this note finds you well.\n\nI noticed you explored our ${topics} at ${req.company || 'your organization'}. We have helped leading global trade teams streamline their international networking and lead conversion.\n\nFeel free to choose a time that suits your schedule:\n${req.calendarUrl || 'https://omnicard.ai/book'}\n\nBest regards,\n${sender}\n${req.senderTitle || ''}`;
}
return {
channel: 'email',
subject,
body,
targetLanguage: lang,
generatedAt: Date.now(),
};
}
}

查看文件

@@ -0,0 +1,91 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export type SupportedImageFormat = 'avif' | 'webp' | 'png' | 'jpg';
export interface MediaNegotiationContext {
acceptHeader?: string;
networkType?: 'wifi' | '5g' | '4g' | '3g' | '2g' | 'unknown' | string;
targetWidth?: number;
dpr?: number;
}
export interface OptimizedMediaResult {
format: SupportedImageFormat;
width?: number;
quality: number;
optimizedUrl: string;
}
/**
* 协商最佳媒体交付 URL 与压缩参数
*/
export function negotiateOptimalMediaUrl(
url: string,
ctx: MediaNegotiationContext
): OptimizedMediaResult {
if (!url || typeof url !== 'string' || !url.trim()) {
return {
format: 'jpg',
quality: 80,
optimizedUrl: '',
};
}
const accept = (ctx.acceptHeader || '').toLowerCase();
const net = (ctx.networkType || 'wifi').toLowerCase();
const isSlowNetwork = net === '2g' || net === '3g';
// 1. 协商格式: avif > webp > 原图扩展名(png/jpg)
let format: SupportedImageFormat = 'jpg';
if (accept.includes('image/avif')) {
format = 'avif';
} else if (accept.includes('image/webp')) {
format = 'webp';
} else if (url.toLowerCase().endsWith('.png') || accept.includes('image/png')) {
format = 'png';
}
// 2. 协商质量与宽度
let quality = 85;
if (isSlowNetwork) {
quality = 65;
}
let width = ctx.targetWidth;
if (width != null && width > 0) {
const dpr = ctx.dpr && ctx.dpr > 1 ? ctx.dpr : 1;
if (isSlowNetwork) {
// 弱网降规 75%
width = Math.round(width * 0.75);
} else {
width = Math.round(width * dpr);
}
}
// 3. 构造优化 URL 参数
const separator = url.includes('?') ? '&' : '?';
const queryParts: string[] = [`format=${format}`, `q=${quality}`];
if (width != null && width > 0) {
queryParts.push(`w=${width}`);
}
const optimizedUrl = `${url}${separator}${queryParts.join('&')}`;
return {
format,
width,
quality,
optimizedUrl,
};
}
/**
* 微官网现代图片自适应协商服务
*/
@Provide()
export class PortalMediaNegotiatorService extends BaseService {
negotiate(url: string, ctx: MediaNegotiationContext): OptimizedMediaResult {
return negotiateOptimalMediaUrl(url, ctx);
}
}

查看文件

@@ -0,0 +1,169 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export type TrafficChannelType =
| 'nfc_tap'
| 'booth_qr'
| 'whatsapp_chat'
| 'linkedin_share'
| 'email_signature'
| 'direct_or_organic';
export type FunnelStageType =
| 'impression'
| 'interactive'
| 'ai_consult'
| 'lead_submitted'
| 'meeting_booked';
export interface ChannelAttributionEvent {
visitorId: string;
channel: TrafficChannelType | string;
stage: FunnelStageType;
timestamp?: number;
}
export interface AttributionFunnelReport {
totalVisitors: number;
stages: Record<FunnelStageType, number>;
stageRates: Record<FunnelStageType, number>;
overallConversionRate: number;
}
export interface ChannelPerformanceItem {
channel: string;
visitors: number;
leads: number;
leadRate: number;
meetings: number;
}
/**
* 流量来源与渠道归一化解析
*/
export function normalizeTrafficChannel(
source?: string,
medium?: string
): TrafficChannelType {
const s = (source || '').toLowerCase().trim();
const m = (medium || '').toLowerCase().trim();
if (s.includes('nfc') || m.includes('nfc') || m.includes('tap')) {
return 'nfc_tap';
}
if (s.includes('booth') || m.includes('booth') || m.includes('qrcode')) {
return 'booth_qr';
}
if (s.includes('whatsapp') || m.includes('whatsapp')) {
return 'whatsapp_chat';
}
if (s.includes('linkedin') || m.includes('linkedin')) {
return 'linkedin_share';
}
if (s.includes('email') || m.includes('sig')) {
return 'email_signature';
}
return 'direct_or_organic';
}
/**
* 计算全链路出海留资转化漏斗指标
*/
export function calculateAttributionFunnel(
events: ChannelAttributionEvent[]
): AttributionFunnelReport {
const visitorSet = new Set<string>();
const stageSets: Record<FunnelStageType, Set<string>> = {
impression: new Set(),
interactive: new Set(),
ai_consult: new Set(),
lead_submitted: new Set(),
meeting_booked: new Set(),
};
for (const e of events) {
visitorSet.add(e.visitorId);
if (stageSets[e.stage]) {
stageSets[e.stage].add(e.visitorId);
}
}
const total = visitorSet.size;
const stages: Record<FunnelStageType, number> = {
impression: stageSets.impression.size,
interactive: stageSets.interactive.size,
ai_consult: stageSets.ai_consult.size,
lead_submitted: stageSets.lead_submitted.size,
meeting_booked: stageSets.meeting_booked.size,
};
const stageRates: Record<FunnelStageType, number> = {
impression: total > 0 ? Number((stages.impression / total).toFixed(2)) : 0,
interactive: total > 0 ? Number((stages.interactive / total).toFixed(2)) : 0,
ai_consult: total > 0 ? Number((stages.ai_consult / total).toFixed(2)) : 0,
lead_submitted: total > 0 ? Number((stages.lead_submitted / total).toFixed(2)) : 0,
meeting_booked: total > 0 ? Number((stages.meeting_booked / total).toFixed(2)) : 0,
};
const overallConversionRate =
total > 0 ? Number((stages.lead_submitted / total).toFixed(2)) : 0;
return {
totalVisitors: total,
stages,
stageRates,
overallConversionRate,
};
}
/**
* 访客雷达多渠道归因与出海转化分析服务
*/
@Provide()
export class RadarAttributionService extends BaseService {
/**
* 渠道转化表现分析
*/
analyzeChannelPerformance(
events: ChannelAttributionEvent[]
): Record<string, ChannelPerformanceItem> {
const channelMap: Record<string, { visitors: Set<string>; leads: Set<string>; meetings: Set<string> }> = {};
for (const e of events) {
const ch = e.channel;
if (!channelMap[ch]) {
channelMap[ch] = {
visitors: new Set(),
leads: new Set(),
meetings: new Set(),
};
}
channelMap[ch].visitors.add(e.visitorId);
if (e.stage === 'lead_submitted') {
channelMap[ch].leads.add(e.visitorId);
}
if (e.stage === 'meeting_booked') {
channelMap[ch].meetings.add(e.visitorId);
}
}
const report: Record<string, ChannelPerformanceItem> = {};
for (const [channel, data] of Object.entries(channelMap)) {
const vCount = data.visitors.size;
const lCount = data.leads.size;
const mCount = data.meetings.size;
const rate = vCount > 0 ? Number((lCount / vCount).toFixed(2)) : 0;
report[channel] = {
channel,
visitors: vCount,
leads: lCount,
leadRate: rate,
meetings: mCount,
};
}
return report;
}
}

查看文件

@@ -0,0 +1,180 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
export type ManufacturingBatchStatus =
| 'draft'
| 'ordered'
| 'manufacturing'
| 'qa_inspected'
| 'in_stock'
| 'rejected'
| 'cancelled';
export interface CreateManufacturingBatchDto {
batchNo: string;
materialType: string;
targetQuantity: number;
factoryName: string;
chipModel: string;
cardFinishing?: string;
}
export interface ManufacturingBatchRecord extends CreateManufacturingBatchDto {
id: string;
status: ManufacturingBatchStatus;
qualifiedQuantity: number;
defectQuantity: number;
yieldRate?: number;
createdAt: number;
updatedAt: number;
}
export interface BatchYieldResult {
yieldRate: number;
defectRate: number;
isQualified: boolean;
defectCount: number;
}
/**
* 生产批次工单生命周期合法转换图
*/
const ALLOWED_TRANSITIONS: Record<ManufacturingBatchStatus, ManufacturingBatchStatus[]> = {
draft: ['ordered', 'cancelled'],
ordered: ['manufacturing', 'cancelled'],
manufacturing: ['qa_inspected', 'cancelled'],
qa_inspected: ['in_stock', 'rejected'],
in_stock: [],
rejected: ['manufacturing'], // 返工重制
cancelled: [],
};
/**
* 判定工单状态机流转是否合法
*/
export function canTransitionBatchStatus(
from: ManufacturingBatchStatus,
to: ManufacturingBatchStatus
): boolean {
const allowed = ALLOWED_TRANSITIONS[from];
return allowed != null && allowed.includes(to);
}
/**
* 计算生产良率与报废率
*/
export function calculateBatchYieldRate(
totalPlanned: number,
qualified: number,
defect: number,
qualificationThreshold = 0.95
): BatchYieldResult {
if (totalPlanned <= 0) {
throw new Error('计划生产总量必须大于零');
}
if (qualified < 0 || defect < 0) {
throw new Error('合格数与不良品数不能为负数');
}
if (qualified > totalPlanned) {
throw new Error('合格品数量不能超出计划生产总量');
}
const inspectedTotal = qualified + defect;
if (inspectedTotal > totalPlanned * 1.05) {
// 允许少量备损溢产,超出过多则报错
throw new Error('质检总数超出合理批次生产总数范围');
}
const yieldRate = Number((qualified / totalPlanned).toFixed(3));
const defectRate = Number((defect / totalPlanned).toFixed(3));
const isQualified = yieldRate >= qualificationThreshold;
return {
yieldRate,
defectRate,
isQualified,
defectCount: defect,
};
}
/**
* 跨境实体卡供应链生产批次工单管理服务
*/
@Provide()
export class NfcManufacturingService extends BaseService {
private readonly batches = new Map<string, ManufacturingBatchRecord>();
/**
* 创建生产批次工单
*/
createBatch(dto: CreateManufacturingBatchDto): ManufacturingBatchRecord {
if (!dto.batchNo || !dto.targetQuantity || dto.targetQuantity <= 0) {
throw new Error('生产批次号与计划生产数量为必填项');
}
const now = Date.now();
const record: ManufacturingBatchRecord = {
id: `mfg_${now}_${Math.random().toString(36).slice(2, 6)}`,
...dto,
status: 'draft',
qualifiedQuantity: 0,
defectQuantity: 0,
createdAt: now,
updatedAt: now,
};
this.batches.set(dto.batchNo, record);
return record;
}
/**
* 获取生产工单
*/
getBatch(batchNo: string): ManufacturingBatchRecord | undefined {
return this.batches.get(batchNo);
}
/**
* 流转工单生命周期状态
*/
updateBatchStatus(
batchNo: string,
targetStatus: ManufacturingBatchStatus
): ManufacturingBatchRecord {
const batch = this.batches.get(batchNo);
if (!batch) {
throw new Error(`未找到批次工单: ${batchNo}`);
}
if (!canTransitionBatchStatus(batch.status, targetStatus)) {
throw new Error(`非法状态流转: 不能从 ${batch.status} 流转至 ${targetStatus}`);
}
batch.status = targetStatus;
batch.updatedAt = Date.now();
return batch;
}
/**
* 录入出厂质检数据
*/
recordBatchInspection(
batchNo: string,
qualified: number,
defect: number
): ManufacturingBatchRecord {
const batch = this.batches.get(batchNo);
if (!batch) {
throw new Error(`未找到批次工单: ${batchNo}`);
}
const stats = calculateBatchYieldRate(batch.targetQuantity, qualified, defect);
batch.qualifiedQuantity = qualified;
batch.defectQuantity = defect;
batch.yieldRate = stats.yieldRate;
batch.status = 'qa_inspected';
batch.updatedAt = Date.now();
return batch;
}
}