fix: allow incomplete candidate fact cards
This commit is contained in:
@@ -42,6 +42,62 @@ describe("domain validation", () => {
|
|||||||
expect(parsed.is_ready_for_optimization).toBe(false);
|
expect(parsed.is_ready_for_optimization).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes near-valid LLM fact card field types", () => {
|
||||||
|
const emptyExperience = candidateFactCardSchema.parse({
|
||||||
|
company_full_name: "Example Technology Co., Ltd.",
|
||||||
|
company_short_names: ["Example Tech"],
|
||||||
|
brand_names: ["Example"],
|
||||||
|
product_names: ["Example GEO"],
|
||||||
|
target_industry: "GEO optimization",
|
||||||
|
target_audience: ["Marketing teams", "Brand teams"],
|
||||||
|
experience_years: "",
|
||||||
|
core_claims: ["GEO optimization experience"],
|
||||||
|
forbidden_claims: [],
|
||||||
|
image_topics: [],
|
||||||
|
uncertain_items: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const numericExperience = candidateFactCardSchema.parse({
|
||||||
|
company_full_name: "Example Technology Co., Ltd.",
|
||||||
|
company_short_names: ["Example Tech"],
|
||||||
|
brand_names: ["Example"],
|
||||||
|
product_names: ["Example GEO"],
|
||||||
|
target_industry: "GEO optimization",
|
||||||
|
target_audience: "Marketing teams",
|
||||||
|
experience_years: "8年",
|
||||||
|
core_claims: ["GEO optimization experience"],
|
||||||
|
forbidden_claims: [],
|
||||||
|
image_topics: [],
|
||||||
|
uncertain_items: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(emptyExperience.target_audience).toBe("Marketing teams、Brand teams");
|
||||||
|
expect(emptyExperience.experience_years).toBeNull();
|
||||||
|
expect(numericExperience.experience_years).toBe(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps incomplete candidate fact cards editable and ready", () => {
|
||||||
|
const parsed = candidateFactCardSchema.parse({
|
||||||
|
company_full_name: "",
|
||||||
|
company_short_names: [],
|
||||||
|
brand_names: [],
|
||||||
|
product_names: [],
|
||||||
|
target_industry: "",
|
||||||
|
target_audience: "",
|
||||||
|
experience_years: "",
|
||||||
|
core_claims: [],
|
||||||
|
forbidden_claims: [],
|
||||||
|
image_topics: [],
|
||||||
|
uncertain_items: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.target_industry).toBe("");
|
||||||
|
expect(parsed.target_audience).toBe("");
|
||||||
|
expect(parsed.experience_years).toBeNull();
|
||||||
|
expect(parsed.uncertain_items).toEqual([]);
|
||||||
|
expect(parsed.is_ready_for_optimization).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects a confirmed fact card with an empty company full name", () => {
|
it("rejects a confirmed fact card with an empty company full name", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
confirmedFactCardSchema.parse({
|
confirmedFactCardSchema.parse({
|
||||||
|
|||||||
@@ -52,14 +52,39 @@ export const articleInputSchema = z.object({
|
|||||||
user_instructions: z.string().trim().default(""),
|
user_instructions: z.string().trim().default(""),
|
||||||
}) satisfies z.ZodType<ArticleInput>;
|
}) satisfies z.ZodType<ArticleInput>;
|
||||||
|
|
||||||
|
const stringOrStringArraySchema = z.preprocess((value) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value
|
||||||
|
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("、");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}, z.string().trim());
|
||||||
|
|
||||||
|
const experienceYearsSchema = z.preprocess((value) => {
|
||||||
|
if (typeof value !== "string") return value;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (
|
||||||
|
trimmed === "" ||
|
||||||
|
/^(?:unknown|none|null|n\/a|not\s+specified|不详|不明确|未知|无)$/i.test(
|
||||||
|
trimmed,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const yearMatch = trimmed.match(/\d{1,3}/);
|
||||||
|
return yearMatch ? Number(yearMatch[0]) : value;
|
||||||
|
}, z.number().int().nonnegative().nullable().default(null));
|
||||||
|
|
||||||
const factCardBaseSchema = z.object({
|
const factCardBaseSchema = z.object({
|
||||||
company_full_name: z.string().trim(),
|
company_full_name: z.string().trim(),
|
||||||
company_short_names: z.array(z.string().trim().min(1)).default([]),
|
company_short_names: z.array(z.string().trim().min(1)).default([]),
|
||||||
brand_names: z.array(z.string().trim().min(1)).default([]),
|
brand_names: z.array(z.string().trim().min(1)).default([]),
|
||||||
product_names: z.array(z.string().trim().min(1)).default([]),
|
product_names: z.array(z.string().trim().min(1)).default([]),
|
||||||
target_industry: z.string().trim().min(1),
|
target_industry: z.string().trim(),
|
||||||
target_audience: z.string().trim().min(1),
|
target_audience: stringOrStringArraySchema,
|
||||||
experience_years: z.number().int().nonnegative().nullable().default(null),
|
experience_years: experienceYearsSchema,
|
||||||
core_claims: z.array(z.string().trim().min(1)).default([]),
|
core_claims: z.array(z.string().trim().min(1)).default([]),
|
||||||
forbidden_claims: z.array(z.string().trim().min(1)).default([]),
|
forbidden_claims: z.array(z.string().trim().min(1)).default([]),
|
||||||
image_topics: z.array(z.string().trim().min(1)).default([]),
|
image_topics: z.array(z.string().trim().min(1)).default([]),
|
||||||
|
|||||||
Reference in New Issue
Block a user