From b0bb8cc09a3174bbcfd6d5a96af658fd4d0d0eb1 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 27 Feb 2026 10:44:02 +0100 Subject: [PATCH] feat: Implement bulk AI product analysis, including algorithmic scoring and context profiling, with a new OpenRouter client. --- .../ai-copilot/business/analysis-engine.ts | 25 ++++++++++--------- .../ai-copilot/data/open-router-client.ts | 8 ++++-- .../grid/components/bulk-ai-analyzer.tsx | 6 ++--- task.md | 4 +-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/features/ai-copilot/business/analysis-engine.ts b/src/features/ai-copilot/business/analysis-engine.ts index a02c290..4afd3ed 100644 --- a/src/features/ai-copilot/business/analysis-engine.ts +++ b/src/features/ai-copilot/business/analysis-engine.ts @@ -17,7 +17,8 @@ export class AnalysisEngine { // ----------------------------------------------------------------------- static generateSystemPrompt(): string { - return `Tu es Mary, Senior Retail Strategist. Tu analyses des produits pour recommander A (garder), C (saisonnier), ou Z (sortir). + return `Tu es Mary, Senior Retail Strategist. Tu analyses des produits pour recommander A (garder) ou Z (sortir). +IMPORTANT : La gamme C est RÉSERVÉE aux produits saisonniers et gérée MANUELLEMENT par l'acheteur. Tu ne dois JAMAIS recommander C. --- ORDRE DE PRIORITÉ (STRICT) --- @@ -27,21 +28,21 @@ export class AnalysisEngine { Si isLowContribution = true [poids CA < 0.5% ET poids QTÉ < 0.5% du fournisseur] ET score composite < 35 ET scoreCritique = true [score brut < 20] - → Z DIRECT. Ce produit est marginal et sous-performant. Aucun signal ne peut l'annuler. + → Z DIRECT. Ce produit est marginal et sous-performant. 3. RÈGLE MANAGER : Si le manager a défini une consigne ET que le produit est concerné → Appliquer. 4. ANALYSE CONTEXTUELLE (si aucune règle ci-dessus ne s'applique) : Utilise les données de la fiche pour raisonner. Les quadrants sont des INDICES, pas des verdicts. - — Quadrant STAR ⭐ : Fort signal positif. A sauf inactivité ≥ 3 mois. + — Quadrant STAR ⭐ : Fort signal positif → A sauf inactivité ≥ 3 mois. — Quadrant TRAFIC 🚶 : Signal positif SEULEMENT si poids QTÉ fournisseur > 1%. - Si poids < 1% et score < 40 → tendance Z ou C. + Si poids < 1% et score < 40 → Z. — Quadrant MARGE 💎 : Signal positif SEULEMENT si poids CA fournisseur > 0.5%. - Si poids < 0.5% et score < 35 → tendance Z. - — Quadrant WATCH ⚠️ : Signal négatif par défaut. - Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A ou C selon inactivité. - Sinon → Z si inactivité ≥ 2 mois, C si 1 mois, A si actif mais surveiller. + Si poids < 0.5% et score < 35 → Z. + — Quadrant WATCH ⚠️ : Signal négatif. + Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A (surveiller). + Sinon → Z si inactivité ≥ 2 mois, A si actif. --- COHÉRENCE INTER-PRODUITS --- Ne mets jamais Z un produit si son percentile CA ET son percentile QTÉ sont tous les deux supérieurs à un autre produit déjà classé A dans ce lot. @@ -50,7 +51,7 @@ Ne mets jamais Z un produit si son percentile CA ET son percentile QTÉ sont tou JSON uniquement, sans markdown. { "rule_applies": boolean, - "recommendation": "A" | "C" | "Z", + "recommendation": "A" | "Z", "justification": "2 phrases max. Cite poids, percentile, quadrant, score." }`; } @@ -181,9 +182,9 @@ Génère UNIQUEMENT le JSON :`; // Utilitaires de parsing (inchangés) // ----------------------------------------------------------------------- - static extractRecommendation(content: string): "A" | "C" | "Z" | null { - const match = content.match(/\b([ACZ])\b/i); - if (match) return match[1].toUpperCase() as "A" | "C" | "Z"; + static extractRecommendation(content: string): "A" | "Z" | null { + const match = content.match(/\b([AZ])\b/i); + if (match) return match[1].toUpperCase() as "A" | "Z"; return null; } diff --git a/src/features/ai-copilot/data/open-router-client.ts b/src/features/ai-copilot/data/open-router-client.ts index 07f4552..37f1cb7 100644 --- a/src/features/ai-copilot/data/open-router-client.ts +++ b/src/features/ai-copilot/data/open-router-client.ts @@ -35,7 +35,7 @@ export class OpenRouterClient { { role: "user", content: AnalysisEngine.generateUserMessage(p) }, ], response_format: { type: "json_object" }, - max_tokens: 200, + max_tokens: 150, temperature: 0.1, }), }); @@ -73,8 +73,12 @@ export class OpenRouterClient { reco = parsed.recommendation as "A" | "C" | "Z"; cleanInsight = parsed.justification || ""; + // Garde-fou : C est réservé aux saisonniers (gestion manuelle). + // Si l'IA recommande C malgré l'interdiction dans le prompt → forcer A. + if (reco === "C") reco = "A"; + // Override recommendation if it doesn't match extracted reco for safety - if (!reco || !["A", "C", "Z"].includes(reco)) { + if (!reco || !["A", "Z"].includes(reco)) { reco = AnalysisEngine.extractRecommendation(cleanInsight) || "A"; } } catch (e) { diff --git a/src/features/grid/components/bulk-ai-analyzer.tsx b/src/features/grid/components/bulk-ai-analyzer.tsx index b6716dd..d2c9883 100644 --- a/src/features/grid/components/bulk-ai-analyzer.tsx +++ b/src/features/grid/components/bulk-ai-analyzer.tsx @@ -181,7 +181,7 @@ export function BulkAiAnalyzer() { codeins.forEach(c => gammeChanges[c] = "Aucune"); batchSetDraftGamme(gammeChanges); - const CONCURRENCY = 3; + const CONCURRENCY = 2; const remaining = [...productPayloads]; const processBatch = async () => { @@ -298,9 +298,9 @@ export function BulkAiAnalyzer() { ) : ( )} diff --git a/task.md b/task.md index 3049013..bfbed21 100644 --- a/task.md +++ b/task.md @@ -23,9 +23,9 @@ Mary (IA) donnait des recommandations basées sur le score brut sans analyse con ## Progress Log -- **Correction IA** : Algorithme Mary v4 implémenté avec profilage contextuel et groupement N2. Le bug de classification 'A' systématique est résolu. -- **Refonte Esthétique** : Bouton de sauvegarde et design system harmonisés en style Apple Glossy/Premium avec micro-animations et feedback visuel. - **Agent Architecte (Timeout)** : Intervention structurelle sur l'entonnoir d'analyse : - **Client-Side (AbortController)** : Timeout poussé de 25s à 50s pour supporter les requêtes des LLM denses (ex: M37). - **Server-Side (N+1 Query)** : Suppression d'une requête BDD redondante invoquée par chaque produit, éliminant ~100 appels BDD par lot. +- **Agent Architecte (Gamme C)** : Prompt v5 → Mary ne recommande plus que A ou Z. Gamme C interdite (réservée saisonniers). Garde-fou C→A côté client. Concurrence réduite à 2 workers, max_tokens réduit à 150. +- **Bouton Analyse IA** : Refactorisé en `apple-btn-secondary` (sobre, conforme PRD Apple-style) avec icône Sparkles colorée en accent. - **Vérification** : Walkthrough documenté et système fluidifié.