feat(admin-brand): add question expansion service and related functionality

- Implemented QuestionExpansionService for generating and materializing questions based on combinations and AI distillation.
- Added question metadata classification logic to infer intent and layer of questions.
- Created API handlers for question expansion operations including combination preview, AI distillation, metadata classification, and materialization.
- Introduced database migrations to support new question-related fields and constraints in brand_questions and brand_keywords tables.
- Added caching mechanism for AI distillation results to improve performance.
- Defined JSON schemas for question distillation responses to ensure data integrity.
This commit is contained in:
2026-05-12 21:53:36 +08:00
parent 77d542c282
commit 37b0b32327
27 changed files with 4619 additions and 908 deletions
+69
View File
@@ -1214,6 +1214,7 @@ export interface Brand {
status: string
keyword_count: number
question_count: number
competitor_count?: number
created_at: string
updated_at?: string
}
@@ -1257,7 +1258,75 @@ export interface BrandLibrarySummary {
max_keywords: number
used_keywords: number
remaining_keywords: number
max_questions: number
used_questions: number
remaining_questions: number
max_questions_per_keyword: number
max_questions_per_brand: number
}
export type QuestionSource = 'manual' | 'combination' | 'ai_distill'
export type QuestionIntent = 'informational' | 'evaluative' | 'decisional'
export type QuestionLayer = 'L1' | 'L2' | 'L3' | 'L4' | 'L5'
export interface QuestionCombinationRequest {
region?: string[]
prefix?: string[]
core: string[]
industry: string[]
suffix?: string[]
max_items?: number
}
export interface QuestionDistillRequest {
seed_topic: string
}
export interface QuestionCandidate {
text: string
layer: QuestionLayer
intent: QuestionIntent
source: QuestionSource
missing_suffix?: boolean
too_short?: boolean
duplicate?: boolean
suggest_skip?: boolean
}
export interface QuestionCandidateResult {
candidates: QuestionCandidate[]
ai_points_charged?: number
cache_hit?: boolean
truncated?: boolean
}
export interface ClassifiedQuestion {
text: string
layer: QuestionLayer
intent: QuestionIntent
}
export interface MaterializeQuestion {
text: string
}
export interface MaterializeQuestionsRequest {
source: QuestionSource
questions: MaterializeQuestion[]
}
export interface SkipReason {
text: string
reason: string
}
export interface MaterializeQuestionsResult {
created_questions: number
skipped_questions: SkipReason[]
}
export interface ClassifyQuestionsRequest {
texts: string[]
}
export interface CompetitorRequest {