mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 19:25: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 { ChartIcon, TableIcon, UploadIcon, DownloadIcon, CloseIcon, TrendingIcon, MegaphoneIcon } from './components/Icons';
|
||||
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
|
||||
const DataGrid = lazy(() => import('./components/DataGrid'));
|
||||
@@ -258,8 +261,7 @@ const App: React.FC = () => {
|
||||
|
||||
const handleExperimentsFetch = useCallback(async () => {
|
||||
try {
|
||||
const allAsins: string[] = Array.from(new Set(rawData.map(r => String(r.asin).toUpperCase())));
|
||||
const expMap = await getActiveExperimentsForASINs(allAsins);
|
||||
const expMap = await getActiveExperiments(rawData);
|
||||
setExperimentMap(expMap);
|
||||
console.log('[App] Loaded experiments for', expMap.size, 'ASINs');
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
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 { 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(() => {
|
||||
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>();
|
||||
|
||||
salesData.forEach(r => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
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 { ExperimentBadge, ExperimentStatusBadge, ExperimentTypeBadge } from './ExperimentBadge';
|
||||
import { LineChart, Line, ResponsiveContainer, YAxis } from 'recharts';
|
||||
@@ -19,7 +19,7 @@ const ExperimentSparkline: React.FC<{ experiment: ExperimentListItem; salesData:
|
||||
const chartData = useMemo(() => {
|
||||
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>();
|
||||
|
||||
salesData.forEach(r => {
|
||||
|
||||
+54
-29
@@ -10,6 +10,34 @@ import { CombinedKPIs } from '../types';
|
||||
|
||||
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 ============
|
||||
|
||||
export const createExperiment = async (input: ExperimentCreateInput): Promise<Experiment> => {
|
||||
@@ -135,13 +163,10 @@ export const listExperiments = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const getActiveExperimentsForASINs = async (
|
||||
asins: string[]
|
||||
export const getActiveExperiments = async (
|
||||
salesData: CombinedKPIs[]
|
||||
): Promise<Map<string, ActiveExperiment[]>> => {
|
||||
const params = new URLSearchParams();
|
||||
asins.forEach(asin => params.append('asin', asin));
|
||||
|
||||
const response = await fetch(`${API_BASE}?${params.toString()}&status=active`);
|
||||
const response = await fetch(`${API_BASE}?status=active`);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
@@ -160,30 +185,30 @@ export const getActiveExperimentsForASINs = async (
|
||||
|
||||
const map = new Map<string, ActiveExperiment[]>();
|
||||
|
||||
for (const asin of asins) {
|
||||
const experiments = activeExperiments
|
||||
.filter((exp: any) => exp.asins?.includes(asin))
|
||||
.map((exp: any) => {
|
||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||
const daysRemaining = endDate
|
||||
? Math.ceil((endDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))
|
||||
: undefined;
|
||||
for (const exp of activeExperiments) {
|
||||
const expAsins = getExperimentAsins(exp.asins || [], salesData);
|
||||
|
||||
return {
|
||||
asin,
|
||||
experiment_id: exp.id,
|
||||
experiment_name: exp.name,
|
||||
type: exp.type,
|
||||
status: exp.status,
|
||||
start_date: exp.start_date,
|
||||
end_date: exp.end_date,
|
||||
days_remaining: daysRemaining,
|
||||
} as ActiveExperiment;
|
||||
});
|
||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||
const daysRemaining = endDate
|
||||
? Math.ceil((endDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))
|
||||
: undefined;
|
||||
|
||||
if (experiments.length > 0) {
|
||||
map.set(asin, experiments);
|
||||
}
|
||||
const activeExp: ActiveExperiment = {
|
||||
asin: '', // placeholder
|
||||
experiment_id: exp.id,
|
||||
experiment_name: exp.name,
|
||||
type: exp.type,
|
||||
status: exp.status,
|
||||
start_date: exp.start_date,
|
||||
end_date: exp.end_date,
|
||||
days_remaining: daysRemaining,
|
||||
};
|
||||
|
||||
expAsins.forEach(asin => {
|
||||
const asinList = map.get(asin) || [];
|
||||
asinList.push({ ...activeExp, asin });
|
||||
map.set(asin, asinList);
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
@@ -216,7 +241,7 @@ export const calculateExperimentPerformance = async (
|
||||
const baselineEnd = startDate;
|
||||
|
||||
// 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 recordDate = new Date(r.year, 0, 1 + (r.week - 1) * 7);
|
||||
|
||||
Reference in New Issue
Block a user