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:
@@ -237,10 +237,11 @@ func (p MembershipPlanConfig) QuotaPolicyJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
type BrandLibraryConfig struct {
|
||||
FreeBrandLimit int `mapstructure:"free_brand_limit"`
|
||||
PaidBrandLimit int `mapstructure:"paid_brand_limit"`
|
||||
MaxKeywords int `mapstructure:"max_keywords"`
|
||||
MaxQuestionsPerKeyword int `mapstructure:"max_questions_per_keyword"`
|
||||
FreeBrandLimit int `mapstructure:"free_brand_limit"`
|
||||
PaidBrandLimit int `mapstructure:"paid_brand_limit"`
|
||||
MaxKeywords int `mapstructure:"max_keywords"`
|
||||
MaxQuestionsPerKeyword int `mapstructure:"max_questions_per_keyword"`
|
||||
QuestionLimitsByPlan map[string]int `mapstructure:"question_limits_by_plan"`
|
||||
}
|
||||
|
||||
func (c BrandLibraryConfig) BrandLimitForPlan(planCode string) int {
|
||||
@@ -250,6 +251,20 @@ func (c BrandLibraryConfig) BrandLimitForPlan(planCode string) int {
|
||||
return c.PaidBrandLimit
|
||||
}
|
||||
|
||||
func (c BrandLibraryConfig) QuestionLimitForPlan(planCode string) int {
|
||||
limits := c.QuestionLimitsByPlan
|
||||
normalizedPlan := strings.ToLower(strings.TrimSpace(planCode))
|
||||
if limits != nil {
|
||||
if value := limits[normalizedPlan]; value > 0 {
|
||||
return value
|
||||
}
|
||||
if value := limits["default"]; value > 0 {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return 25
|
||||
}
|
||||
|
||||
type QdrantConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
@@ -1173,6 +1188,29 @@ func normalizeBrandLibraryConfig(cfg *BrandLibraryConfig) {
|
||||
if cfg.MaxQuestionsPerKeyword <= 0 {
|
||||
cfg.MaxQuestionsPerKeyword = 5
|
||||
}
|
||||
if cfg.QuestionLimitsByPlan == nil {
|
||||
cfg.QuestionLimitsByPlan = map[string]int{}
|
||||
}
|
||||
normalized := make(map[string]int, len(cfg.QuestionLimitsByPlan)+4)
|
||||
for key, value := range cfg.QuestionLimitsByPlan {
|
||||
trimmed := strings.ToLower(strings.TrimSpace(key))
|
||||
if trimmed != "" && value > 0 {
|
||||
normalized[trimmed] = value
|
||||
}
|
||||
}
|
||||
if normalized["default"] <= 0 {
|
||||
normalized["default"] = 25
|
||||
}
|
||||
if normalized["free"] <= 0 {
|
||||
normalized["free"] = 5
|
||||
}
|
||||
if normalized["plus"] <= 0 {
|
||||
normalized["plus"] = 25
|
||||
}
|
||||
if normalized["pro"] <= 0 {
|
||||
normalized["pro"] = 50
|
||||
}
|
||||
cfg.QuestionLimitsByPlan = normalized
|
||||
}
|
||||
|
||||
func normalizeMembershipConfig(cfg *MembershipConfig) {
|
||||
|
||||
@@ -62,7 +62,7 @@ func Diff(previous, current *Config) []FieldChange {
|
||||
if !reflect.DeepEqual(previous.Membership, current.Membership) {
|
||||
addChange("membership", true)
|
||||
}
|
||||
if previous.BrandLibrary != current.BrandLibrary {
|
||||
if !reflect.DeepEqual(previous.BrandLibrary, current.BrandLibrary) {
|
||||
addChange("brand_library", true)
|
||||
}
|
||||
if previous.Qdrant != current.Qdrant {
|
||||
|
||||
Reference in New Issue
Block a user