feat(consultation): validate bounded consultation plans

This commit is contained in:
Jesse_Chen
2026-08-14 17:38:27 +08:00
parent a8141d955c
commit f4b3da6511
3 changed files with 62 additions and 1 deletions
+48
View File
@@ -0,0 +1,48 @@
import { z } from "zod";
export const consultationDomainValues = ["career", "marriage", "wealth", "timing"] as const;
export const consultationDepthValues = ["concise", "standard", "deep", "research"] as const;
export const consultationTimingHorizonValues = ["next_3_months", "next_12_months", "next_24_months", "long_term"] as const;
export const consultationEvidenceCategoryValues = ["natal_foundation", "domain", "timing", "validation"] as const;
export const consultationPlanSchema = z.object({
userIntent: z.string().trim().min(1).max(500),
requestedDomains: z.array(z.enum(consultationDomainValues)).min(1).max(3),
depth: z.enum(consultationDepthValues),
timingHorizon: z.enum(consultationTimingHorizonValues).nullable(),
requiredEvidenceCategories: z.array(z.enum(consultationEvidenceCategoryValues)).min(1).max(4),
}).strict();
export type ConsultationPlan = z.infer<typeof consultationPlanSchema>;
export type ConsultationPlanTheme = "career" | "marriage" | "wealth" | "timing" | "general";
const domainsByTheme: Record<ConsultationPlanTheme, ConsultationPlan["requestedDomains"]> = {
career: ["career"],
marriage: ["marriage"],
wealth: ["wealth"],
timing: ["timing"],
general: ["career", "marriage", "wealth"],
};
const evidenceByTheme: Record<ConsultationPlanTheme, ConsultationPlan["requiredEvidenceCategories"]> = {
career: ["natal_foundation", "domain"],
marriage: ["natal_foundation", "domain"],
wealth: ["natal_foundation", "domain"],
timing: ["natal_foundation", "timing", "validation"],
general: ["natal_foundation", "domain"],
};
export function createConsultationPlan(input: {
userIntent: string;
theme: ConsultationPlanTheme;
depth?: ConsultationPlan["depth"];
timingHorizon?: ConsultationPlan["timingHorizon"];
}): ConsultationPlan {
return consultationPlanSchema.parse({
userIntent: input.userIntent,
requestedDomains: domainsByTheme[input.theme],
depth: input.depth ?? "standard",
timingHorizon: input.timingHorizon ?? (input.theme === "timing" ? "next_12_months" : null),
requiredEvidenceCategories: evidenceByTheme[input.theme],
});
}