|
|
@@ -8,6 +8,7 @@ interface BriefAnalysisResult {
|
|
|
fanRange: { min: number; max: number };
|
|
|
budgetRange: { min: number; max: number };
|
|
|
region?: string;
|
|
|
+ regionalBudgetRules?: RegionalBudgetRule[];
|
|
|
gender?: string;
|
|
|
contentTags?: string[];
|
|
|
excludeTags?: string[];
|
|
|
@@ -27,6 +28,12 @@ export interface EvaluationNeeds {
|
|
|
reasons?: string[];
|
|
|
}
|
|
|
|
|
|
+export interface RegionalBudgetRule {
|
|
|
+ regions: string[];
|
|
|
+ min: number;
|
|
|
+ max: number;
|
|
|
+}
|
|
|
+
|
|
|
const BRIEF_ANALYSIS_PROMPT = `你是一个专业的媒介策划助手。请分析以下客户Brief文件内容,提取出结构化的投放需求。
|
|
|
|
|
|
要求提取以下信息:
|
|
|
@@ -42,7 +49,8 @@ const BRIEF_ANALYSIS_PROMPT = `你是一个专业的媒介策划助手。请分
|
|
|
|
|
|
请以JSON格式输出,包含requirements数组和searchCriteria对象。
|
|
|
requirements格式:[{label: "字段名", value: "提取值", confidence: 0-100}]
|
|
|
-searchCriteria格式:{platforms: [], keywords: [], fanRange: {min, max}, budgetRange: {min, max}, region?, gender?, contentTags?: [], excludeTags?: [], targetCount?}
|
|
|
+searchCriteria格式:{platforms: [], keywords: [], fanRange: {min, max}, budgetRange: {min, max}, region?, regionalBudgetRules?: [{regions: [], min, max}], gender?, contentTags?: [], excludeTags?: [], targetCount?}
|
|
|
+如果 Brief 里出现“某些城市/地区对应不同单个预算/平台价格”,请把这些规则放入 regionalBudgetRules,并保留总 budgetRange 为全部规则的最小/最大范围。
|
|
|
如果 Brief 写明达人数量,请额外输出 targetCount,表示最终需求达人总数(不是候选池数量)。
|
|
|
|
|
|
Brief内容:
|
|
|
@@ -114,6 +122,10 @@ export async function analyzeBrief(briefText: string): Promise<BriefAnalysisResu
|
|
|
searchCriteria: parsed.searchCriteria || { platforms: [], keywords: [], fanRange: { min: 10000, max: 500000 }, budgetRange: { min: 2000, max: 20000 } },
|
|
|
};
|
|
|
result.searchCriteria.targetCount = normalizeTargetCount(result.searchCriteria.targetCount) || inferTargetCount(briefText, result.requirements);
|
|
|
+ result.searchCriteria.regionalBudgetRules = normalizeRegionalBudgetRules(
|
|
|
+ result.searchCriteria.regionalBudgetRules,
|
|
|
+ briefText,
|
|
|
+ );
|
|
|
result.searchCriteria.evaluationNeeds = normalizeEvaluationNeeds(
|
|
|
result.searchCriteria.evaluationNeeds,
|
|
|
briefText,
|
|
|
@@ -156,12 +168,57 @@ function fallbackAnalysis(briefText: string): BriefAnalysisResult {
|
|
|
keywords,
|
|
|
fanRange: { min: 10000, max: 500000 },
|
|
|
budgetRange: { min: 2000, max: 20000 },
|
|
|
+ regionalBudgetRules: inferRegionalBudgetRules(briefText),
|
|
|
targetCount: inferTargetCount(briefText, []),
|
|
|
evaluationNeeds: inferEvaluationNeeds(briefText, []),
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+function normalizeRegionalBudgetRules(parsedRules: unknown, briefText: string): RegionalBudgetRule[] | undefined {
|
|
|
+ const parsed = Array.isArray(parsedRules)
|
|
|
+ ? parsedRules
|
|
|
+ .map((item) => {
|
|
|
+ if (typeof item !== 'object' || item === null) return null;
|
|
|
+ const raw = item as Record<string, unknown>;
|
|
|
+ const regions = toStringArray(raw.regions || raw.region || raw.cities || raw.city);
|
|
|
+ const min = Number(raw.min || raw.minPrice || raw.budgetMin || 0);
|
|
|
+ const max = Number(raw.max || raw.maxPrice || raw.budgetMax || 0);
|
|
|
+ return regions.length > 0 && max > 0 ? { regions, min: Math.max(0, min), max } : null;
|
|
|
+ })
|
|
|
+ .filter((item): item is RegionalBudgetRule => item !== null)
|
|
|
+ : [];
|
|
|
+ const inferred = inferRegionalBudgetRules(briefText);
|
|
|
+ const combined = [...parsed, ...inferred];
|
|
|
+ const deduped = new Map<string, RegionalBudgetRule>();
|
|
|
+ for (const rule of combined) {
|
|
|
+ const regions = [...new Set(rule.regions.map((region) => region.trim()).filter(Boolean))];
|
|
|
+ if (regions.length === 0 || rule.max <= 0) continue;
|
|
|
+ const key = `${regions.sort().join('|')}:${rule.min}-${rule.max}`;
|
|
|
+ deduped.set(key, { regions, min: rule.min, max: rule.max });
|
|
|
+ }
|
|
|
+ return deduped.size > 0 ? [...deduped.values()] : undefined;
|
|
|
+}
|
|
|
+
|
|
|
+function inferRegionalBudgetRules(briefText: string): RegionalBudgetRule[] {
|
|
|
+ const cityNames = [
|
|
|
+ '北京', '上海', '广州', '深圳', '成都', '杭州', '武汉', '西安', '南京', '重庆',
|
|
|
+ '天津', '苏州', '长沙', '郑州', '青岛', '厦门', '宁波', '合肥', '福州', '无锡',
|
|
|
+ ];
|
|
|
+ const rules: RegionalBudgetRule[] = [];
|
|
|
+ const pattern = /([^\n。;;::]{1,80})[::][^\n。;;]{0,30}?(\d{3,6})\s*(?:-|~|—|至|到)\s*(\d{3,6})/g;
|
|
|
+ for (const match of briefText.matchAll(pattern)) {
|
|
|
+ const regions = cityNames.filter((city) => match[1].includes(city));
|
|
|
+ if (regions.length === 0) continue;
|
|
|
+ const min = Number(match[2]);
|
|
|
+ const max = Number(match[3]);
|
|
|
+ if (Number.isFinite(min) && Number.isFinite(max) && max > 0) {
|
|
|
+ rules.push({ regions, min: Math.min(min, max), max: Math.max(min, max) });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rules;
|
|
|
+}
|
|
|
+
|
|
|
function normalizeEvaluationNeeds(
|
|
|
parsedNeeds: unknown,
|
|
|
briefText: string,
|
|
|
@@ -213,7 +270,9 @@ function inferEvaluationNeeds(
|
|
|
}
|
|
|
|
|
|
function toStringArray(value: unknown): string[] {
|
|
|
- return Array.isArray(value) ? value.map(String).filter(Boolean) : [];
|
|
|
+ if (Array.isArray(value)) return value.map(String).filter(Boolean);
|
|
|
+ if (typeof value === 'string') return value.split(/[、,,\s/]+/).map((item) => item.trim()).filter(Boolean);
|
|
|
+ return [];
|
|
|
}
|
|
|
|
|
|
function normalizeTargetCount(value: unknown): number | undefined {
|