package processing import ( "context" "github.com/descrybe/descrybe-v2/apps/api/internal/company" "github.com/descrybe/descrybe-v2/apps/api/internal/eprel" ) // Pipeline step names (canonical order for "full"). const ( StepNormalize = "normalize" StepParseSpecs = "parse_specs" StepFillFields = "fill_fields" StepEPREL = "eprel" StepAIEnhance = "ai_enhance" ) // CanonicalSteps is the default full pipeline order. var CanonicalSteps = []string{ StepNormalize, StepParseSpecs, StepFillFields, StepEPREL, StepAIEnhance, } // Completer is the LLM chat boundary (OpenAI-compatible HTTP API). type Completer interface { Complete(ctx context.Context, system, user string) (Completion, error) } // EnableChecker optionally reports whether a Completer should run. type EnableChecker interface { Enabled() bool } // Completion is a single model response with usage for cost recording. type Completion struct { Text string PromptTokens int OutputTokens int TotalTokens int Model string Raw any } // Embedder turns text into vectors (OpenAI-compatible /v1/embeddings). // Used by vectorization / Pinecone paths; admin role: platformsettings.AIRoleVectorization. type Embedder interface { Embed(ctx context.Context, texts []string) ([][]float32, error) } // VectorCategorizer optionally ranks categories by embedding similarity (Pinecone). type VectorCategorizer interface { SuggestCategory(ctx context.Context, companyID, productText string, candidates []string) (string, error) Enabled() bool } // EPRELEnricher fetches EU energy-label data when an EPREL ID is present. type EPRELEnricher interface { Enabled() bool Fetch(ctx context.Context, eprelID string) (*eprel.Data, error) } // ProductInput is sanitized product payload for pipeline steps. type ProductInput struct { GTIN string Name string Description string Mapped map[string]any Raw map[string]any StandardFields []StandardFieldDef // BrandPrompt is brand-kit guidance injected into AI enhance when non-empty // (paid plans only; Free may edit the kit but AI apply is gated). BrandPrompt string // Language is the primary content language (companies.language). Language string // ContentLanguages is the ordered list of languages to enhance (primary first). ContentLanguages []string // EnhanceByLang maps language → company/built-in system+user templates. EnhanceByLang map[string]PromptTemplates // EnhanceSystemTemplate / EnhanceUserTemplate are primary-language prompts // (kept for tests / single-lang callers). EnhanceSystemTemplate string EnhanceUserTemplate string // CategoryEnhancePrompt overrides EnhanceUserTemplate when non-empty // (resolved for the active language before enhance). CategoryEnhancePrompt string // CategoryPromptsByLang maps lower(name) → lang → category override prompt. CategoryPromptsByLang map[string]company.LangPromptMap // Prior* are loaded from the last processed_products row for this raw product. PriorEnhanceHash string PriorProcessedName string PriorProcessedDescription string PriorCategory string PriorLocalized company.LocalizedContent } // PromptTemplates is a system+user pair for one language. type PromptTemplates struct { System string User string } // StepResult is the cumulative output for one product. type StepResult struct { Category string Name string Description string ProcessedName string ProcessedDescription string LocalizedContent company.LocalizedContent Attributes map[string]any ProcessedAttributes map[string]any FieldSources map[string]any EPREL map[string]any GPTResponse map[string]any TotalTokens int // AIProviderMode is written to processed_products.ai_provider_mode / // processing_jobs.ai_provider_mode for analytics // ("internal" | "popular:" | "custom" | "unknown"). AIProviderMode string Notes []string // SkipCreditDebit is set when AI enhance reused prior output because the // enhance input hash matched (ai_enhance_unchanged). processOne must not // ConsumeCredits in that case — no LLM and no meaningful rework. SkipCreditDebit bool } // StepProgress is a job-level snapshot of pipeline step status. type StepProgress struct { Step string `json:"step"` Status string `json:"status"` // pending|running|done|skipped|failed Note string `json:"note,omitempty"` } // Engine runs ordered processing steps behind interfaces. type Engine struct { Completer Completer Vector VectorCategorizer EPREL EPRELEnricher // ProviderMode is the analytics label for the active Completer: // "internal" | "popular:" | "custom". Empty falls back to CompleterProviderMode. ProviderMode string } // CompleterEnabled reports whether AI enhance should run. func (e *Engine) CompleterEnabled() bool { if e == nil || e.Completer == nil { return false } if c, ok := e.Completer.(EnableChecker); ok { return c.Enabled() } // HeuristicCompleter has no Enabled — treat as enabled only if explicitly set. // Worker sets Completer=nil when platform OpenAI (admin settings / env) is unset. _, isHeuristic := e.Completer.(HeuristicCompleter) return !isHeuristic }