mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:45:24 +02:00
fix: resolve LINE level experiments to individual ASINs using salesData so calculation works for product lines
This commit is contained in:
@@ -10,7 +10,10 @@ import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, For
|
|||||||
import { queryGemini } from './services/geminiService';
|
import { queryGemini } from './services/geminiService';
|
||||||
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons';
|
import { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons';
|
||||||
import { loadSalesData, saveSalesData, clearSalesData, loadAdsData, saveAdsData, clearAdsData } from './services/storage';
|
import { loadSalesData, saveSalesData, clearSalesData, loadAdsData, saveAdsData, clearAdsData } from './services/storage';
|
||||||
import { getActiveExperimentsForASINs } from './services/experiments';
|
import {
|
||||||
|
listExperiments,
|
||||||
|
getActiveExperiments,
|
||||||
|
} from './services/experiments';
|
||||||
|
|
||||||
// Lazy load heavy components for better initial performance
|
// Lazy load heavy components for better initial performance
|
||||||
const DataGrid = lazy(() => import('./components/DataGrid'));
|
const DataGrid = lazy(() => import('./components/DataGrid'));
|
||||||
@@ -258,8 +261,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const handleExperimentsFetch = useCallback(async () => {
|
const handleExperimentsFetch = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const allAsins: string[] = Array.from(new Set(rawData.map(r => String(r.asin).toUpperCase())));
|
const expMap = await getActiveExperiments(rawData);
|
||||||
const expMap = await getActiveExperimentsForASINs(allAsins);
|
|
||||||
setExperimentMap(expMap);
|
setExperimentMap(expMap);
|
||||||
console.log('[App] Loaded experiments for', expMap.size, 'ASINs');
|
console.log('[App] Loaded experiments for', expMap.size, 'ASINs');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
import { Experiment, ExperimentCreateInput, ExperimentType, ExperimentStatus, ExperimentMetric, CombinedKPIs } from '../types';
|
import { Experiment, ExperimentCreateInput, ExperimentType, ExperimentStatus, ExperimentMetric, CombinedKPIs } from '../types';
|
||||||
import { getExperiment, updateExperiment, deleteExperiment, calculateExperimentPerformance } from '../services/experiments';
|
import { getExperiment, updateExperiment, deleteExperiment, calculateExperimentPerformance, getExperimentAsins } from '../services/experiments';
|
||||||
import { ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge';
|
import { ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge';
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, ReferenceLine } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, ReferenceLine } from 'recharts';
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const ExperimentDetail: React.FC<ExperimentDetailProps> = ({ experimentId, onClo
|
|||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!experiment || !salesData.length) return [];
|
if (!experiment || !salesData.length) return [];
|
||||||
|
|
||||||
const asinSet = new Set((experiment.asins || []).map(a => (a || '').toUpperCase()));
|
const asinSet = getExperimentAsins(experiment.asins, salesData);
|
||||||
const weeklyMap = new Map<string, any>();
|
const weeklyMap = new Map<string, any>();
|
||||||
|
|
||||||
salesData.forEach(r => {
|
salesData.forEach(r => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { ExperimentListItem, ExperimentType, ExperimentStatus, CombinedKPIs } from '../types';
|
import { ExperimentListItem, ExperimentType, ExperimentStatus, CombinedKPIs } from '../types';
|
||||||
import { listExperiments, deleteExperiment, getExperimentStatusColor, getExperimentTypeColor, getExperimentIcon } from '../services/experiments';
|
import { listExperiments, deleteExperiment, getExperimentStatusColor, getExperimentTypeColor, getExperimentIcon, getExperimentAsins } from '../services/experiments';
|
||||||
import MultiSelectDropdown from './MultiSelectDropdown';
|
import MultiSelectDropdown from './MultiSelectDropdown';
|
||||||
import { ExperimentBadge, ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge';
|
import { ExperimentBadge, ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge';
|
||||||
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts';
|
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts';
|
||||||
@@ -19,7 +19,7 @@ const ExperimentSparkline: React.FC<{ experiment: ExperimentListItem; salesData:
|
|||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!experiment?.asins || !salesData?.length) return [];
|
if (!experiment?.asins || !salesData?.length) return [];
|
||||||
|
|
||||||
const asinSet = new Set((experiment.asins || []).map(a => (a || '').toUpperCase()));
|
const asinSet = getExperimentAsins(experiment.asins, salesData);
|
||||||
const weeklyMap = new Map<string, any>();
|
const weeklyMap = new Map<string, any>();
|
||||||
|
|
||||||
salesData.forEach(r => {
|
salesData.forEach(r => {
|
||||||
|
|||||||
+43
-18
@@ -10,6 +10,34 @@ import { CombinedKPIs } from '../types';
|
|||||||
|
|
||||||
const API_BASE = '/api/experiments';
|
const API_BASE = '/api/experiments';
|
||||||
|
|
||||||
|
// ============ Helper Functions ============
|
||||||
|
|
||||||
|
export const getExperimentAsins = (experimentAsins: string[], salesData: CombinedKPIs[]): Set<string> => {
|
||||||
|
const explicitAsins = new Set<string>();
|
||||||
|
const lines = new Set<string>();
|
||||||
|
|
||||||
|
(experimentAsins || []).forEach(a => {
|
||||||
|
const val = (a || '').trim().toUpperCase();
|
||||||
|
if (val.startsWith('LINE:')) {
|
||||||
|
lines.add(val.substring(5).trim());
|
||||||
|
} else if (val) {
|
||||||
|
explicitAsins.add(val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const asinSet = new Set<string>(explicitAsins);
|
||||||
|
if (lines.size > 0 && salesData) {
|
||||||
|
salesData.forEach(r => {
|
||||||
|
const line = (r.line || '').trim().toUpperCase();
|
||||||
|
if (line && lines.has(line)) {
|
||||||
|
if (r.asin) asinSet.add(r.asin.toUpperCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return asinSet;
|
||||||
|
};
|
||||||
|
|
||||||
// ============ CRUD Operations ============
|
// ============ CRUD Operations ============
|
||||||
|
|
||||||
export const createExperiment = async (input: ExperimentCreateInput): Promise<Experiment> => {
|
export const createExperiment = async (input: ExperimentCreateInput): Promise<Experiment> => {
|
||||||
@@ -135,13 +163,10 @@ export const listExperiments = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getActiveExperimentsForASINs = async (
|
export const getActiveExperiments = async (
|
||||||
asins: string[]
|
salesData: CombinedKPIs[]
|
||||||
): Promise<Map<string, ActiveExperiment[]>> => {
|
): Promise<Map<string, ActiveExperiment[]>> => {
|
||||||
const params = new URLSearchParams();
|
const response = await fetch(`${API_BASE}?status=active`);
|
||||||
asins.forEach(asin => params.append('asin', asin));
|
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}?${params.toString()}&status=active`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
@@ -160,17 +185,16 @@ export const getActiveExperimentsForASINs = async (
|
|||||||
|
|
||||||
const map = new Map<string, ActiveExperiment[]>();
|
const map = new Map<string, ActiveExperiment[]>();
|
||||||
|
|
||||||
for (const asin of asins) {
|
for (const exp of activeExperiments) {
|
||||||
const experiments = activeExperiments
|
const expAsins = getExperimentAsins(exp.asins || [], salesData);
|
||||||
.filter((exp: any) => exp.asins?.includes(asin))
|
|
||||||
.map((exp: any) => {
|
|
||||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||||
const daysRemaining = endDate
|
const daysRemaining = endDate
|
||||||
? Math.ceil((endDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))
|
? Math.ceil((endDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
const activeExp: ActiveExperiment = {
|
||||||
asin,
|
asin: '', // placeholder
|
||||||
experiment_id: exp.id,
|
experiment_id: exp.id,
|
||||||
experiment_name: exp.name,
|
experiment_name: exp.name,
|
||||||
type: exp.type,
|
type: exp.type,
|
||||||
@@ -178,12 +202,13 @@ export const getActiveExperimentsForASINs = async (
|
|||||||
start_date: exp.start_date,
|
start_date: exp.start_date,
|
||||||
end_date: exp.end_date,
|
end_date: exp.end_date,
|
||||||
days_remaining: daysRemaining,
|
days_remaining: daysRemaining,
|
||||||
} as ActiveExperiment;
|
};
|
||||||
});
|
|
||||||
|
|
||||||
if (experiments.length > 0) {
|
expAsins.forEach(asin => {
|
||||||
map.set(asin, experiments);
|
const asinList = map.get(asin) || [];
|
||||||
}
|
asinList.push({ ...activeExp, asin });
|
||||||
|
map.set(asin, asinList);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
@@ -216,7 +241,7 @@ export const calculateExperimentPerformance = async (
|
|||||||
const baselineEnd = startDate;
|
const baselineEnd = startDate;
|
||||||
|
|
||||||
// Filter sales data for experiment ASINs
|
// Filter sales data for experiment ASINs
|
||||||
const asinSet = new Set(experiment.asins.map(a => a.toUpperCase()));
|
const asinSet = getExperimentAsins(experiment.asins, salesData);
|
||||||
|
|
||||||
const baselineData = salesData.filter(r => {
|
const baselineData = salesData.filter(r => {
|
||||||
const recordDate = new Date(r.year, 0, 1 + (r.week - 1) * 7);
|
const recordDate = new Date(r.year, 0, 1 + (r.week - 1) * 7);
|
||||||
|
|||||||
Reference in New Issue
Block a user