mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 05:28:11 +02:00
feat: Implement bulk AI product analysis, including algorithmic scoring and context profiling, with a new OpenRouter client.
This commit is contained in:
1 parent
02fdd18a52
commit
b0bb8cc09a
4 files changed
+24
-19
No files matched your search
@@ -17,7 +17,8 @@ export class AnalysisEngine {
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
static generateSystemPrompt(): string {
|
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) ---
|
--- 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]
|
Si isLowContribution = true [poids CA < 0.5% ET poids QTÉ < 0.5% du fournisseur]
|
||||||
ET score composite < 35
|
ET score composite < 35
|
||||||
ET scoreCritique = true [score brut < 20]
|
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.
|
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) :
|
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.
|
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%.
|
— 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%.
|
— Quadrant MARGE 💎 : Signal positif SEULEMENT si poids CA fournisseur > 0.5%.
|
||||||
Si poids < 0.5% et score < 35 → tendance Z.
|
Si poids < 0.5% et score < 35 → Z.
|
||||||
— Quadrant WATCH ⚠️ : Signal négatif par défaut.
|
— Quadrant WATCH ⚠️ : Signal négatif.
|
||||||
Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A ou C selon inactivité.
|
Si poids CA rayon > 5% ou poids QTÉ rayon > 5% → A (surveiller).
|
||||||
Sinon → Z si inactivité ≥ 2 mois, C si 1 mois, A si actif mais surveiller.
|
Sinon → Z si inactivité ≥ 2 mois, A si actif.
|
||||||
|
|
||||||
--- COHÉRENCE INTER-PRODUITS ---
|
--- 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.
|
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.
|
JSON uniquement, sans markdown.
|
||||||
{
|
{
|
||||||
"rule_applies": boolean,
|
"rule_applies": boolean,
|
||||||
"recommendation": "A" | "C" | "Z",
|
"recommendation": "A" | "Z",
|
||||||
"justification": "2 phrases max. Cite poids, percentile, quadrant, score."
|
"justification": "2 phrases max. Cite poids, percentile, quadrant, score."
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
@@ -181,9 +182,9 @@ Génère UNIQUEMENT le JSON :`;
|
|||||||
// Utilitaires de parsing (inchangés)
|
// Utilitaires de parsing (inchangés)
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
static extractRecommendation(content: string): "A" | "C" | "Z" | null {
|
static extractRecommendation(content: string): "A" | "Z" | null {
|
||||||
const match = content.match(/\b([ACZ])\b/i);
|
const match = content.match(/\b([AZ])\b/i);
|
||||||
if (match) return match[1].toUpperCase() as "A" | "C" | "Z";
|
if (match) return match[1].toUpperCase() as "A" | "Z";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class OpenRouterClient {
|
|||||||
{ role: "user", content: AnalysisEngine.generateUserMessage(p) },
|
{ role: "user", content: AnalysisEngine.generateUserMessage(p) },
|
||||||
],
|
],
|
||||||
response_format: { type: "json_object" },
|
response_format: { type: "json_object" },
|
||||||
max_tokens: 200,
|
max_tokens: 150,
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -73,8 +73,12 @@ export class OpenRouterClient {
|
|||||||
reco = parsed.recommendation as "A" | "C" | "Z";
|
reco = parsed.recommendation as "A" | "C" | "Z";
|
||||||
cleanInsight = parsed.justification || "";
|
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
|
// 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";
|
reco = AnalysisEngine.extractRecommendation(cleanInsight) || "A";
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export function BulkAiAnalyzer() {
|
|||||||
codeins.forEach(c => gammeChanges[c] = "Aucune");
|
codeins.forEach(c => gammeChanges[c] = "Aucune");
|
||||||
batchSetDraftGamme(gammeChanges);
|
batchSetDraftGamme(gammeChanges);
|
||||||
|
|
||||||
const CONCURRENCY = 3;
|
const CONCURRENCY = 2;
|
||||||
const remaining = [...productPayloads];
|
const remaining = [...productPayloads];
|
||||||
|
|
||||||
const processBatch = async () => {
|
const processBatch = async () => {
|
||||||
@@ -298,9 +298,9 @@ export function BulkAiAnalyzer() {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={handleAnalyze}
|
onClick={handleAnalyze}
|
||||||
className="apple-btn-ai"
|
className="apple-btn-secondary"
|
||||||
>
|
>
|
||||||
<Sparkles className="w-4 h-4" />
|
<Sparkles className="w-4 h-4" style={{ color: "var(--accent)" }} />
|
||||||
Analyse IA
|
Analyse IA
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ Mary (IA) donnait des recommandations basées sur le score brut sans analyse con
|
|||||||
|
|
||||||
## Progress Log
|
## 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 :
|
- **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).
|
- **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.
|
- **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é.
|
- **Vérification** : Walkthrough documenté et système fluidifié.
|
||||||
Reference in new issue
Block a user