mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
feat: Integrate PapaParse for CSV handling
Adds papaparse as a dependency and updates the data processing service to use it for more robust CSV file parsing. This replaces manual CSV parsing logic with a dedicated library, improving reliability and handling of various CSV formats. Also renames the `MoversIcon` to `TrendingIcon` to better reflect its usage in indicating trending performance metrics.
This commit is contained in:
+55
-46
@@ -2,39 +2,49 @@
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import { AggregatedData } from "../types";
|
||||
|
||||
// Declare process to avoid TypeScript errors without causing aggressive bundler shims
|
||||
declare const process: any;
|
||||
|
||||
const SYSTEM_INSTRUCTION = `
|
||||
You are an expert Data Analyst Assistant for "Craze Analytix".
|
||||
You have access to a structured dataset of sales performance including Revenue (Sell Out), Units, Product Lines, and Seasonality.
|
||||
You are a senior data analyst assistant for a retail dashboard called "Craze Analytix".
|
||||
You have access to a detailed report of the currently filtered sales data.
|
||||
The data includes Sell Out (Revenue in €), Units Sold, Product Lines, Customers/Markets, and Seasonality trends.
|
||||
|
||||
Your Capabilities:
|
||||
1. **Analyze Trends**: Use the provided Seasonality and Yearly Breakdown data.
|
||||
2. **Perform Calculations**: You have access to detailed Product Line totals. You MUST calculate growth percentages, market shares, and sums dynamically if the user asks.
|
||||
3. **Compare**: Compare performance between years (e.g., 2024 vs 2025).
|
||||
|
||||
Rules:
|
||||
- If the user asks for a calculation (e.g., "What is the % share of Line X?"), perform the math using the provided numbers.
|
||||
- Always format currency as € (e.g., €1,200) and units with 'u' or 'units' (e.g., 500 units).
|
||||
- Be concise but insightful. Point out significant growth or decline.
|
||||
- If data is missing for a specific query, state clearly that it is not in the current filtered view.
|
||||
Your goal is to answer user questions specific to the provided data.
|
||||
- If asked about "Trends" or "Seasonality", look at the Monthly Seasonality section.
|
||||
- If asked about "Growth" or "Decline", look at the Top/Bottom Movers sections.
|
||||
- If asked about specific Product Lines, look at the Product Line Breakdown.
|
||||
- Always format numbers clearly (e.g., "€1.2M", "€5,200", "15k units").
|
||||
- When comparing years, calculate the percentage difference if not explicitly provided.
|
||||
- Keep answers professional, concise, and business-focused.
|
||||
`;
|
||||
|
||||
// Helper to get API key safely
|
||||
const getApiKey = (): string | undefined => {
|
||||
try {
|
||||
return process.env.API_KEY;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (val: number) => `€${val.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}`;
|
||||
const formatUnits = (val: number) => `${val.toLocaleString()} units`;
|
||||
|
||||
export const queryGemini = async (
|
||||
apiKey: string,
|
||||
question: string,
|
||||
context: AggregatedData,
|
||||
filteredRecordCount: number
|
||||
): Promise<string> => {
|
||||
|
||||
const apiKey = getApiKey();
|
||||
|
||||
if (!apiKey) {
|
||||
return "Please provide your Gemini API Key in the settings to enable the AI assistant.";
|
||||
return "API Key is missing. Please configure your environment variables (API_KEY) or check your .env file.";
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure the key is clean of whitespace
|
||||
const ai = new GoogleGenAI({ apiKey: apiKey.trim() });
|
||||
const ai = new GoogleGenAI({ apiKey });
|
||||
|
||||
// --- CONTEXT GENERATION ---
|
||||
// We construct a structured report mirroring the dashboard charts
|
||||
@@ -46,62 +56,64 @@ export const queryGemini = async (
|
||||
.join('\n');
|
||||
|
||||
// 2. Seasonality (Line Chart Data)
|
||||
// We simplify this to a CSV-like list for the AI to parse trends
|
||||
const seasonalitySummary = context.seasonality.map(p => {
|
||||
// Extract values for each year in the point
|
||||
const yearValues = context.availableYears.map(y => `${y}: ${formatCurrency(p[y] as number || 0)}`).join(', ');
|
||||
return ` - ${p.name}: [${yearValues}]`;
|
||||
}).join('\n');
|
||||
|
||||
// 3. Growth/Decline
|
||||
// 3. Top Movers (Growth Table) - Limit to Top 10
|
||||
const growthSummary = context.topMovers.slice(0, 10).map(m =>
|
||||
` - ${m.line}: +€${m.sellOutGrowthValue.toLocaleString()} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
|
||||
).join('\n');
|
||||
|
||||
// 4. Declining Movers (Decline Table) - Limit to Top 10
|
||||
const declineSummary = context.bottomMovers.slice(0, 10).map(m =>
|
||||
` - ${m.line}: -€${Math.abs(m.sellOutGrowthValue).toLocaleString()} (${m.sellOutGrowthPercentage.toFixed(1)}%)`
|
||||
).join('\n');
|
||||
|
||||
// 4. DETAILED BREAKDOWN (For Calculations)
|
||||
// We provide a JSON-like structure of ALL top product lines so the AI can compute shares/totals.
|
||||
// We limit this to top 100 to avoid token limits, which covers most relevant data.
|
||||
const detailedLines = context.byLine.slice(0, 100).map(l => ({
|
||||
name: l.name,
|
||||
revenue: l.value,
|
||||
units: l.units
|
||||
}));
|
||||
// 5. Product Lines Overview (Bar Charts) - Limit to Top 50 to save tokens but give depth
|
||||
const topLinesSummary = context.byLine.slice(0, 50).map((l, i) =>
|
||||
` ${i+1}. ${l.name}: ${formatCurrency(l.value)} | ${formatUnits(l.units)}`
|
||||
).join('\n');
|
||||
|
||||
// 6. Customer Distribution (Customer Chart)
|
||||
const customerSummary = context.byCustomer.map(c =>
|
||||
` - ${c.name}: ${formatCurrency(c.value)}`
|
||||
).join('\n');
|
||||
|
||||
const fullReport = `
|
||||
REPORT CONTEXT (Based on Current Filters):
|
||||
------------------------------------------
|
||||
GLOBAL METRICS:
|
||||
REPORT CONTEXT:
|
||||
----------------
|
||||
GLOBAL TOTALS:
|
||||
Total Sell Out: ${formatCurrency(context.totalSellOut)}
|
||||
Total Units: ${formatUnits(context.totalUnits)}
|
||||
Records Analyzed: ${filteredRecordCount}
|
||||
Years Available: ${context.availableYears.join(', ')}
|
||||
|
||||
YEARLY TOTALS:
|
||||
YEARLY BREAKDOWN:
|
||||
${yearlySummary}
|
||||
|
||||
MONTHLY TRENDS (Seasonality):
|
||||
MONTHLY SEASONALITY (Revenue Trends):
|
||||
${seasonalitySummary}
|
||||
|
||||
TOP PERFORMERS (Growth YoY):
|
||||
FASTEST GROWING LINES (Year-over-Year):
|
||||
${growthSummary}
|
||||
|
||||
WORST PERFORMERS (Decline YoY):
|
||||
DECLINING LINES (Year-over-Year):
|
||||
${declineSummary}
|
||||
|
||||
DETAILED PRODUCT LINE DATA (Use this for specific calculations):
|
||||
${JSON.stringify(detailedLines, null, 2)}
|
||||
TOP PRODUCT LINES (Revenue & Units):
|
||||
${topLinesSummary}
|
||||
|
||||
PERFORMANCE BY CUSTOMER:
|
||||
${customerSummary}
|
||||
`;
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model: 'gemini-3-pro-preview', // Updated to the latest capable model for complex reasoning
|
||||
contents: [
|
||||
{
|
||||
role: 'user',
|
||||
parts: [{ text: `Context Data:\n${fullReport}\n\nUser Question: ${question}` }]
|
||||
}
|
||||
],
|
||||
model: 'gemini-2.5-flash',
|
||||
contents: `Context Data:\n${fullReport}\n\nUser Question: ${question}`,
|
||||
config: {
|
||||
systemInstruction: SYSTEM_INSTRUCTION,
|
||||
}
|
||||
@@ -111,11 +123,8 @@ ${JSON.stringify(detailedLines, null, 2)}
|
||||
} catch (error: any) {
|
||||
console.error("Gemini API Error:", error);
|
||||
|
||||
if (error.message && error.message.includes("403")) {
|
||||
return "Error 403: Invalid API Key. Please check your key in the settings.";
|
||||
}
|
||||
if (error.message && error.message.includes("429")) {
|
||||
return "Error 429: Quota exceeded. You are sending too many requests.";
|
||||
if (error.message && error.message.includes("Not implemented on this platform")) {
|
||||
return "System Error: The AI SDK detected a platform mismatch.";
|
||||
}
|
||||
|
||||
return `Error: ${error.message || "An unexpected error occurred while analyzing the data."}`;
|
||||
|
||||
Reference in New Issue
Block a user