fix: improve chinese fact extraction
This commit is contained in:
@@ -63,6 +63,23 @@ describe("workflow nodes", () => {
|
|||||||
expect(card.is_ready_for_optimization).toBe(false);
|
expect(card.is_ready_for_optimization).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("extracts Chinese company facts from Chinese articles", async () => {
|
||||||
|
const card = await extractCandidateFactCard({
|
||||||
|
title: "#探寻AIGC短视频培训选哪家,各品牌实力大比拼",
|
||||||
|
body: "伟思德鲁管理咨询(深圳)有限公司面向品牌商家和出海企业提供AIGC短视频培训服务,帮助企业解决内容工业化生产、品牌视觉统一和全球化传播问题。",
|
||||||
|
images: [{ type: "description", content: "AIGC短视频工作流示意图" }],
|
||||||
|
platform: "media_article",
|
||||||
|
user_instructions: "保留AIGC短视频培训与出海内容生产场景。",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(card.company_full_name).toBe("伟思德鲁管理咨询(深圳)有限公司");
|
||||||
|
expect(card.company_short_names).toContain("伟思德鲁");
|
||||||
|
expect(card.target_industry).toBe("AIGC短视频培训");
|
||||||
|
expect(card.target_audience).toBe("品牌商家、内容创作者、出海企业");
|
||||||
|
expect(card.image_topics).toEqual(["AIGC短视频工作流示意图"]);
|
||||||
|
expect(card.uncertain_items).not.toContain("Missing company full name");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not add claims outside the confirmed fact card", async () => {
|
it("does not add claims outside the confirmed fact card", async () => {
|
||||||
const optimized = await optimizeArticle({
|
const optimized = await optimizeArticle({
|
||||||
input: {
|
input: {
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export async function extractCandidateFactCard(
|
|||||||
|
|
||||||
return candidateFactCardSchema.parse({
|
return candidateFactCardSchema.parse({
|
||||||
company_full_name: companyFullName ?? "",
|
company_full_name: companyFullName ?? "",
|
||||||
company_short_names: companyFullName ? [companyFullName.split(/\s+/)[0] ?? ""] : [],
|
company_short_names: inferCompanyShortNames(companyFullName),
|
||||||
brand_names: inferCapitalizedNames(text),
|
brand_names: inferBrandNames(text, companyFullName),
|
||||||
product_names: inferProducts(text),
|
product_names: inferProducts(text),
|
||||||
target_industry: industry,
|
target_industry: industry,
|
||||||
target_audience: text.toLowerCase().includes("marketing")
|
target_audience: text.toLowerCase().includes("marketing")
|
||||||
? "Marketing teams"
|
? "Marketing teams"
|
||||||
: "Business readers",
|
: inferTargetAudience(text),
|
||||||
experience_years: years.length === 1 ? years[0] : null,
|
experience_years: years.length === 1 ? years[0] : null,
|
||||||
core_claims: years.length === 1 ? [`${years[0]} years of ${industry} experience`] : [],
|
core_claims: years.length === 1 ? [`${years[0]} years of ${industry} experience`] : [],
|
||||||
forbidden_claims: [],
|
forbidden_claims: [],
|
||||||
@@ -39,10 +39,15 @@ export async function extractCandidateFactCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findCompanyFullName(text: string) {
|
function findCompanyFullName(text: string) {
|
||||||
const match = text.match(
|
const englishMatch = text.match(
|
||||||
/([A-Z][A-Za-z0-9&.,\-\s]{2,}?(?:Co\.,?\s*Ltd\.?|Company|Inc\.?|LLC|Ltd\.))/,
|
/([A-Z][A-Za-z0-9&.,\-\s]{2,}?(?:Co\.,?\s*Ltd\.?|Company|Inc\.?|LLC|Ltd\.))/,
|
||||||
);
|
);
|
||||||
return match?.[1].trim() ?? null;
|
if (englishMatch?.[1]) return englishMatch[1].trim();
|
||||||
|
|
||||||
|
const chineseMatch = text.match(
|
||||||
|
/([\u4e00-\u9fa5A-Za-z0-9()()]{2,40}?(?:股份有限公司|有限公司|集团|公司))/,
|
||||||
|
);
|
||||||
|
return chineseMatch?.[1].trim() ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findExperienceYears(text: string) {
|
function findExperienceYears(text: string) {
|
||||||
@@ -55,19 +60,63 @@ function inferIndustry(text: string) {
|
|||||||
if (lower.includes("geo")) return "GEO optimization";
|
if (lower.includes("geo")) return "GEO optimization";
|
||||||
if (lower.includes("finance") || lower.includes("banking")) return "finance automation";
|
if (lower.includes("finance") || lower.includes("banking")) return "finance automation";
|
||||||
if (lower.includes("seo")) return "SEO";
|
if (lower.includes("seo")) return "SEO";
|
||||||
|
if (/AIGC|短视频|出海内容|内容工业化/.test(text)) return "AIGC短视频培训";
|
||||||
|
if (/工业零部件|爆品操盘|AI OBS|IPMS/.test(text)) return "工业零部件爆品操盘";
|
||||||
return "General business";
|
return "General business";
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferCapitalizedNames(text: string) {
|
function inferBrandNames(text: string, companyFullName: string | null) {
|
||||||
|
const chineseBrands = [
|
||||||
|
...new Set(
|
||||||
|
[
|
||||||
|
companyFullName ? inferChineseShortName(companyFullName) : "",
|
||||||
|
...[...text.matchAll(/\b(AIGC|AI OBS|IPMS|GEO|SEO)\b/g)].map(
|
||||||
|
(match) => match[1],
|
||||||
|
),
|
||||||
|
].filter(Boolean),
|
||||||
|
),
|
||||||
|
];
|
||||||
const names = [...text.matchAll(/\b[A-Z][A-Za-z0-9]{2,}\b/g)]
|
const names = [...text.matchAll(/\b[A-Z][A-Za-z0-9]{2,}\b/g)]
|
||||||
.map((match) => match[0])
|
.map((match) => match[0])
|
||||||
.filter((word) => !["The", "This", "And"].includes(word));
|
.filter((word) => !["The", "This", "And"].includes(word));
|
||||||
return [...new Set(names)].slice(0, 5);
|
return [...new Set([...chineseBrands, ...names])].slice(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferProducts(text: string) {
|
function inferProducts(text: string) {
|
||||||
const productMatches = [...text.matchAll(/\b([A-Z][A-Za-z0-9]+\s+GEO)\b/g)].map(
|
const productMatches = [
|
||||||
|
...[...text.matchAll(/\b([A-Z][A-Za-z0-9]+\s+GEO)\b/g)].map(
|
||||||
(match) => match[1],
|
(match) => match[1],
|
||||||
);
|
),
|
||||||
|
...[...text.matchAll(/\b(AIGC短视频培训|AI OBS|IPMS|爆品操盘数智系统)\b/g)].map(
|
||||||
|
(match) => match[1],
|
||||||
|
),
|
||||||
|
];
|
||||||
return [...new Set(productMatches)];
|
return [...new Set(productMatches)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inferCompanyShortNames(companyFullName: string | null) {
|
||||||
|
if (!companyFullName) return [];
|
||||||
|
if (/[\u4e00-\u9fa5]/.test(companyFullName)) {
|
||||||
|
const legalShortName = inferChineseShortName(companyFullName);
|
||||||
|
const brandShortName = legalShortName.replace(/管理咨询$/, "");
|
||||||
|
return [...new Set([brandShortName, legalShortName].filter(Boolean))];
|
||||||
|
}
|
||||||
|
return [companyFullName.split(/\s+/)[0] ?? ""].filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferChineseShortName(companyFullName: string) {
|
||||||
|
return companyFullName
|
||||||
|
.replace(/[((].*?[))]/g, "")
|
||||||
|
.replace(/股份有限公司|有限公司|集团|公司/g, "")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferTargetAudience(text: string) {
|
||||||
|
if (/品牌商家|内容创作者|出海企业/.test(text)) {
|
||||||
|
return "品牌商家、内容创作者、出海企业";
|
||||||
|
}
|
||||||
|
if (/工业企业|工业零部件制造商|采购/.test(text)) {
|
||||||
|
return "工业企业、采购团队、工业零部件制造商";
|
||||||
|
}
|
||||||
|
return "Business readers";
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user