mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-18 12:46:32 +02:00
feat: Implement AI Copilot for product analysis and add comprehensive product grid data fetching.
This commit is contained in:
1 parent
cefe6060d0
commit
aa02d019bd
8 files changed
+119
-14
No files matched your search
@@ -5,7 +5,7 @@
|
||||
* Principe: Tout est relatif à la moyenne du lot fournisseur.
|
||||
*/
|
||||
|
||||
import type { ProductAnalysisInput } from "../models/ai-analysis.types";
|
||||
import type { ProductAnalysisInput, SiteMonthlyData } from "../models/ai-analysis.types";
|
||||
import type { ProductContextProfile } from "./context-profiler";
|
||||
|
||||
export class AnalysisEngine {
|
||||
@@ -18,6 +18,9 @@ export class AnalysisEngine {
|
||||
Ton modèle économique repose sur la rotation rapide, le volume, et la rentabilité de l'espace en rayon.
|
||||
Tu analyses des produits pour recommander A (garder) ou Z (sortir).
|
||||
|
||||
📦 RÈGLE STOCK NÉGATIF (s'applique toujours) :
|
||||
Un stock négatif signifie que la commande fournisseur a été validée APRÈS la mise en vente du produit en magasin. La mise à jour informatique du stock est faite a posteriori. Ce n'est PAS une anomalie — c'est un indicateur que le magasin a vendu avant d'avoir reçu le stock officiellement. Ne pas pénaliser un produit pour un stock négatif.
|
||||
|
||||
--- ORDRE DE PRIORITÉ (STRICT) ---
|
||||
|
||||
📝 RÈGLE 1 — RÈGLE MANAGER (PRIORITÉ ABSOLUE):
|
||||
@@ -173,6 +176,34 @@ SI AUCUNE RÈGLE MANAGER n'est définie, tu recommandes UNIQUEMENT A ou Z (jamai
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Bloc comportement par magasin (si données mensuelles disponibles)
|
||||
if (p.siteMonthlyData && p.siteMonthlyData.length > 0) {
|
||||
lines.push(`--- COMPORTEMENT PAR MAGASIN (12 mois) ---`);
|
||||
lines.push(`⚠️ Stock négatif = commande validée avant réception informatique (normal en retail).`);
|
||||
lines.push("");
|
||||
|
||||
const bySite = new Map<string, SiteMonthlyData[]>();
|
||||
for (const row of p.siteMonthlyData) {
|
||||
if (!bySite.has(row.site)) bySite.set(row.site, []);
|
||||
bySite.get(row.site)!.push(row);
|
||||
}
|
||||
|
||||
for (const [site, rows] of bySite.entries()) {
|
||||
lines.push(`${site} :`);
|
||||
lines.push(`Mois | Ventes Qté | CA HT | Marge | Stock fin mois | Réceptions`);
|
||||
rows.sort((a, b) => a.mois.localeCompare(b.mois)).forEach(r => {
|
||||
const mois = r.mois.padEnd(10);
|
||||
const qty = String(r.ventes_qte).padStart(10);
|
||||
const ca = `${r.ventes_ca.toFixed(2)}€`.padStart(11);
|
||||
const mg = `${r.marge.toFixed(2)}€`.padStart(10);
|
||||
const stk = String(r.stock_fin_mois).padStart(14);
|
||||
const rec = String(r.receptions_qte).padStart(10);
|
||||
lines.push(`${mois} |${qty} |${ca} |${mg} |${stk} |${rec}`);
|
||||
});
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(`Génère UNIQUEMENT le JSON :`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ export function AiInsightBlock({ row }: AiInsightBlockProps) {
|
||||
const handleAnalyze = async () => {
|
||||
if (status === "loading") return;
|
||||
|
||||
// Produit sans aucune vente sur 12 mois → Z direct, pas d'appel IA
|
||||
if (row.totalQuantite === 0) {
|
||||
setDraftGamme(row.codein, "Z");
|
||||
useAiCopilotStore.getState().setInsight(row.codein, "Aucune vente sur 12 mois — produit classé Z automatiquement.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset Visuel Immédiat
|
||||
setLoading(row.codein);
|
||||
setDraftGamme(row.codein, "Aucune");
|
||||
@@ -90,6 +97,7 @@ export function AiInsightBlock({ row }: AiInsightBlockProps) {
|
||||
|
||||
analyzeProduct({
|
||||
codein: row.codein,
|
||||
noid: row.noid,
|
||||
libelle1: row.libelle1,
|
||||
totalCa: row.totalCa,
|
||||
tauxMarge: row.tauxMarge,
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import type { ProductContextProfile } from "../business/context-profiler";
|
||||
|
||||
export interface SiteMonthlyData {
|
||||
site: string; // "Frouard" ou "Houdemont"
|
||||
mois: string; // "YYYY-MM"
|
||||
ventes_qte: number;
|
||||
ventes_ca: number;
|
||||
marge: number;
|
||||
stock_fin_mois: number; // peut être négatif (validation tardive de commande)
|
||||
receptions_qte: number;
|
||||
}
|
||||
|
||||
export interface ProductAnalysisInput {
|
||||
codein: string;
|
||||
libelle1: string;
|
||||
@@ -51,6 +61,10 @@ export interface ProductAnalysisInput {
|
||||
};
|
||||
/** Optional context rules for the supplier */
|
||||
supplierContext?: string;
|
||||
/** SQL Server internal ID — pour fetch mensuel per-site */
|
||||
noid?: number;
|
||||
/** Données mensuelles per-site (ventes, stock, réceptions) */
|
||||
siteMonthlyData?: SiteMonthlyData[];
|
||||
/** Données stock & approvisionnement (API FF Nancy) */
|
||||
stockActuel?: number;
|
||||
stockTotal?: number;
|
||||
|
||||
@@ -18,6 +18,7 @@ interface AiCopilotState {
|
||||
setError: (codein: string, error: string) => void;
|
||||
analyzeProduct: (payload: {
|
||||
codein: string;
|
||||
noid?: number;
|
||||
libelle1: string;
|
||||
totalCa: number;
|
||||
tauxMarge: number;
|
||||
@@ -36,6 +37,9 @@ interface AiCopilotState {
|
||||
shareMarge?: number;
|
||||
shareQty?: number;
|
||||
totalFournisseurCa?: number;
|
||||
codeFournisseur?: string;
|
||||
supplierContext?: string;
|
||||
scoring?: Record<string, unknown>;
|
||||
}) => Promise<void>;
|
||||
resetInsights: () => void;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user