mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:45:23 +02:00
feat: add Detail Level BSR metric to experiments analysis
This commit is contained in:
@@ -5,6 +5,7 @@ import Dashboard from './components/Dashboard';
|
|||||||
import FilterBar from './components/FilterBar';
|
import FilterBar from './components/FilterBar';
|
||||||
import AIChat from './components/AIChat';
|
import AIChat from './components/AIChat';
|
||||||
import CrazeLogo from './components/CrazeLogo';
|
import CrazeLogo from './components/CrazeLogo';
|
||||||
|
import TopMovers from './components/TopMovers';
|
||||||
import { processCSV, processExcel, filterData, aggregateData, processAdsCSV, processAdsExcel, mergeSalesAndAdsData, processTrafficExcel, processStockExcel, filterAdsData, calculateForecastViewData, processVendorStockExcel, processUKInventoryExcel, calculateVelocityMap, getUniqueValues, processForecastExcel, processBuyBoxExcel, processBSRExcel, filterBsrData } from './services/dataProcessor';
|
import { processCSV, processExcel, filterData, aggregateData, processAdsCSV, processAdsExcel, mergeSalesAndAdsData, processTrafficExcel, processStockExcel, filterAdsData, calculateForecastViewData, processVendorStockExcel, processUKInventoryExcel, calculateVelocityMap, getUniqueValues, processForecastExcel, processBuyBoxExcel, processBSRExcel, filterBsrData } from './services/dataProcessor';
|
||||||
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, ForecastRecord, ProductForecastData, ActiveExperiment, BSRRecord } from './types';
|
import { SalesRecord, FilterState, AggregatedData, AdsRecord, TrafficRecord, ForecastRecord, ProductForecastData, ActiveExperiment, BSRRecord } from './types';
|
||||||
import { queryGemini } from './services/geminiService';
|
import { queryGemini } from './services/geminiService';
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
// 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'));
|
||||||
const WeeklyGrid = lazy(() => import('./components/WeeklyGrid'));
|
const WeeklyGrid = lazy(() => import('./components/WeeklyGrid'));
|
||||||
const TopMovers = lazy(() => import('./components/TopMovers'));
|
|
||||||
const AdsPerformance = lazy(() => import('./components/AdsPerformance'));
|
const AdsPerformance = lazy(() => import('./components/AdsPerformance'));
|
||||||
const ForecastView = lazy(() => import('./components/ForecastView'));
|
const ForecastView = lazy(() => import('./components/ForecastView'));
|
||||||
const VendorDataView = lazy(() => import('./components/VendorDataView'));
|
const VendorDataView = lazy(() => import('./components/VendorDataView'));
|
||||||
@@ -564,15 +564,20 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
// Determine which dataset to use for velocity calculation based on top50Mode
|
// Determine which dataset to use for velocity calculation based on top50Mode
|
||||||
|
|
||||||
|
// Derived Data for Vendor (BSR)
|
||||||
|
const filteredBsrData = useMemo(() => {
|
||||||
|
return filterBsrData(bsrData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode);
|
||||||
|
}, [bsrData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode]);
|
||||||
|
|
||||||
// Combine Sales & Ads Data dynamically based on current filters
|
// Combine Sales & Ads Data dynamically based on current filters
|
||||||
const combinedAdsData = useMemo(() => {
|
const combinedAdsData = useMemo(() => {
|
||||||
return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap);
|
return mergeSalesAndAdsData(filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap, filteredBsrData);
|
||||||
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap]);
|
}, [filteredData, filteredAdsData, globalAsinMetadata, trafficData, velocityMap, filteredBsrData]);
|
||||||
|
|
||||||
// Unfiltered combined data for Experiment calculations (so they aren't affected by UI filters)
|
// Unfiltered combined data for Experiment calculations (so they aren't affected by UI filters)
|
||||||
const unfilteredCombinedData = useMemo(() => {
|
const unfilteredCombinedData = useMemo(() => {
|
||||||
return mergeSalesAndAdsData(rawData, adsData, globalAsinMetadata, trafficData, velocityMap);
|
return mergeSalesAndAdsData(rawData, adsData, globalAsinMetadata, trafficData, velocityMap, bsrData);
|
||||||
}, [rawData, adsData, globalAsinMetadata, trafficData, velocityMap]);
|
}, [rawData, adsData, globalAsinMetadata, trafficData, velocityMap, bsrData]);
|
||||||
|
|
||||||
// Derived Data for Grid (YTD Filtered - Isolated)
|
// Derived Data for Grid (YTD Filtered - Isolated)
|
||||||
// 1. Identify "Current Year" max week
|
// 1. Identify "Current Year" max week
|
||||||
@@ -627,11 +632,6 @@ const App: React.FC = () => {
|
|||||||
return aggregateData(ytdFilteredData);
|
return aggregateData(ytdFilteredData);
|
||||||
}, [ytdFilteredData]);
|
}, [ytdFilteredData]);
|
||||||
|
|
||||||
// Derived Data for Vendor (BSR)
|
|
||||||
const filteredBsrData = useMemo(() => {
|
|
||||||
return filterBsrData(bsrData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode);
|
|
||||||
}, [bsrData, filters, globalAsinMetadata, stockMap, vendorStockMap, top50Mode]);
|
|
||||||
|
|
||||||
// Derived Data for Views
|
// Derived Data for Views
|
||||||
const years = useMemo(() => getUniqueValues(rawData, 'year').sort().reverse(), [rawData]);
|
const years = useMemo(() => getUniqueValues(rawData, 'year').sort().reverse(), [rawData]);
|
||||||
|
|
||||||
@@ -823,7 +823,7 @@ const App: React.FC = () => {
|
|||||||
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
|
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<div className={view === 'dashboard' ? '' : 'hidden'}>
|
<div className={view === 'dashboard' ? '' : 'hidden'}>
|
||||||
<Dashboard data={ytdAggregatedData} filterState={filters} adsData={ytdFilteredAdsData} stockMap={stockMap} />
|
<Dashboard data={ytdAggregatedData} filterState={filters} adsData={ytdFilteredAdsData} stockMap={stockMap} rawData={rawData} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
@@ -868,11 +868,9 @@ const App: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<div className={view === 'movers' ? '' : 'hidden'}>
|
||||||
<div className={view === 'movers' ? '' : 'hidden'}>
|
<TopMovers data={filteredData} />
|
||||||
<TopMovers data={filteredData} stockMap={stockMap} />
|
</div>
|
||||||
</div>
|
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
<div className={view === 'ads' ? '' : 'hidden'}>
|
<div className={view === 'ads' ? '' : 'hidden'}>
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { AggregatedData, GrowthMetric, AdsRecord } from '../types';
|
import { AggregatedData, GrowthMetric, AdsRecord, SalesRecord } from '../types';
|
||||||
import {
|
import {
|
||||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
LineChart, Line, Legend
|
LineChart, Line, Legend
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
import TopMovers from './TopMovers';
|
||||||
|
|
||||||
interface DashboardProps {
|
interface DashboardProps {
|
||||||
data: AggregatedData;
|
data: AggregatedData;
|
||||||
contextData?: AggregatedData | null;
|
contextData?: AggregatedData | null;
|
||||||
adsData?: AdsRecord[];
|
adsData?: AdsRecord[];
|
||||||
|
rawData?: SalesRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9'];
|
const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9'];
|
||||||
@@ -457,7 +459,7 @@ const GrowthTable: React.FC<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [] }) => {
|
const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [], rawData = [] }) => {
|
||||||
const [seasonalityMetric, setSeasonalityMetric] = useState<'sellOut' | 'units'>('sellOut');
|
const [seasonalityMetric, setSeasonalityMetric] = useState<'sellOut' | 'units'>('sellOut');
|
||||||
const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut');
|
const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut');
|
||||||
|
|
||||||
@@ -808,6 +810,13 @@ const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [] }
|
|||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Top Movers Table - Full Width */}
|
||||||
|
{rawData && rawData.length > 0 && (
|
||||||
|
<div className="mt-6">
|
||||||
|
<TopMovers data={rawData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const METRIC_OPTIONS: { value: ExperimentMetric; label: string }[] = [
|
|||||||
{ value: 'roas', label: 'ROAS' },
|
{ value: 'roas', label: 'ROAS' },
|
||||||
{ value: 'revenue', label: 'Revenue' },
|
{ value: 'revenue', label: 'Revenue' },
|
||||||
{ value: 'acos', label: 'ACOS' },
|
{ value: 'acos', label: 'ACOS' },
|
||||||
|
{ value: 'detail_bsr', label: 'Detail Level BSR' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// ============ Main Component ============
|
// ============ Main Component ============
|
||||||
@@ -567,7 +568,7 @@ const ExperimentDetailView: React.FC<{
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-white/5">
|
<tbody className="divide-y divide-white/5">
|
||||||
{/* Render specific metrics: Units, Revenue, CVR, BSR/ACOS */}
|
{/* Render specific metrics: Units, Revenue, CVR, BSR/ACOS */}
|
||||||
{['units', 'revenue', 'cvr', 'acos', 'sessions', 'roas', 'ctr'].map(metric => {
|
{['units', 'revenue', 'cvr', 'acos', 'sessions', 'roas', 'ctr', 'detail_bsr'].map(metric => {
|
||||||
const res = didResults?.metrics?.[metric];
|
const res = didResults?.metrics?.[metric];
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return (
|
return (
|
||||||
@@ -584,7 +585,7 @@ const ExperimentDetailView: React.FC<{
|
|||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const isPositiveGood = !['acos', 'bsr'].includes(metric);
|
const isPositiveGood = !['acos', 'detail_bsr'].includes(metric);
|
||||||
const isGood = isPositiveGood ? res.lift_percent >= 0 : res.lift_percent <= 0;
|
const isGood = isPositiveGood ? res.lift_percent >= 0 : res.lift_percent <= 0;
|
||||||
return (
|
return (
|
||||||
<tr key={metric} className="hover:bg-white/[0.02] transition-colors">
|
<tr key={metric} className="hover:bg-white/[0.02] transition-colors">
|
||||||
|
|||||||
@@ -783,7 +783,8 @@ export const mergeSalesAndAdsData = (
|
|||||||
adsData: AdsRecord[],
|
adsData: AdsRecord[],
|
||||||
asinMetadataMap?: Map<string, { sku: string; title: string; line: string }>,
|
asinMetadataMap?: Map<string, { sku: string; title: string; line: string }>,
|
||||||
trafficData?: TrafficRecord[],
|
trafficData?: TrafficRecord[],
|
||||||
velocityMap?: Map<string, number>
|
velocityMap?: Map<string, number>,
|
||||||
|
bsrData?: BSRRecord[]
|
||||||
): CombinedKPIs[] => {
|
): CombinedKPIs[] => {
|
||||||
const stringCache: Record<string, string> = {};
|
const stringCache: Record<string, string> = {};
|
||||||
const getNorm = (s: string) => {
|
const getNorm = (s: string) => {
|
||||||
@@ -808,6 +809,20 @@ export const mergeSalesAndAdsData = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build BSR lookup map
|
||||||
|
const bsrMap = new Map<string, number>();
|
||||||
|
if (bsrData) {
|
||||||
|
for (let i = 0; i < bsrData.length; i++) {
|
||||||
|
const b = bsrData[i];
|
||||||
|
const year = b.date ? parseInt(b.date.split('-')[0]) : new Date().getFullYear();
|
||||||
|
const country = mapCountryToMarketplace(b.market);
|
||||||
|
const key = `${getNorm(b.asin)}|${getNorm(country)}|${year}|${b.week}`;
|
||||||
|
if (b.detailLevelBSR !== null && b.detailLevelBSR !== undefined) {
|
||||||
|
bsrMap.set(key, b.detailLevelBSR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Initialize metadata lookup map with provided global map if available, otherwise build from current sales
|
// 1. Initialize metadata lookup map with provided global map if available, otherwise build from current sales
|
||||||
const asinMetadata = asinMetadataMap || new Map<string, { sku: string; title: string; line: string }>();
|
const asinMetadata = asinMetadataMap || new Map<string, { sku: string; title: string; line: string }>();
|
||||||
|
|
||||||
@@ -942,6 +957,7 @@ export const mergeSalesAndAdsData = (
|
|||||||
cpc,
|
cpc,
|
||||||
cvrUnits,
|
cvrUnits,
|
||||||
glanceViews: trafficMap.get(key) || 0,
|
glanceViews: trafficMap.get(key) || 0,
|
||||||
|
detailLevelBSR: bsrMap.get(key),
|
||||||
avgWeeklySales
|
avgWeeklySales
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -983,6 +999,7 @@ export const mergeSalesAndAdsData = (
|
|||||||
cpc: ad.clicks > 0 ? ad.cost / ad.clicks : 0,
|
cpc: ad.clicks > 0 ? ad.cost / ad.clicks : 0,
|
||||||
cvrUnits: ad.clicks > 0 ? (ad.attributedUnits30d / ad.clicks) * 100 : 0,
|
cvrUnits: ad.clicks > 0 ? (ad.attributedUnits30d / ad.clicks) * 100 : 0,
|
||||||
glanceViews: trafficMap.get(key) || 0,
|
glanceViews: trafficMap.get(key) || 0,
|
||||||
|
detailLevelBSR: bsrMap.get(key),
|
||||||
avgWeeklySales
|
avgWeeklySales
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ interface WeeklyMetrics {
|
|||||||
cost: number;
|
cost: number;
|
||||||
clicks: number;
|
clicks: number;
|
||||||
impressions: number;
|
impressions: number;
|
||||||
|
detail_bsr: number;
|
||||||
|
bsrCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComputedWeeklyMetrics extends WeeklyMetrics {
|
interface ComputedWeeklyMetrics extends WeeklyMetrics {
|
||||||
@@ -159,6 +161,8 @@ function aggregateWeeklyMetrics(
|
|||||||
cost: 0,
|
cost: 0,
|
||||||
clicks: 0,
|
clicks: 0,
|
||||||
impressions: 0,
|
impressions: 0,
|
||||||
|
detail_bsr: 0,
|
||||||
|
bsrCount: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +174,10 @@ function aggregateWeeklyMetrics(
|
|||||||
w.cost += Number(r.cost) || 0;
|
w.cost += Number(r.cost) || 0;
|
||||||
w.clicks += r.clicks || 0;
|
w.clicks += r.clicks || 0;
|
||||||
w.impressions += r.impressions || 0;
|
w.impressions += r.impressions || 0;
|
||||||
|
if (r.detailLevelBSR != null) {
|
||||||
|
w.detail_bsr += r.detailLevelBSR;
|
||||||
|
w.bsrCount += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug ad map
|
// Debug ad map
|
||||||
@@ -189,6 +197,7 @@ function aggregateWeeklyMetrics(
|
|||||||
cvr: w.sessions > 0 ? (w.units / w.sessions) * 100 : 0,
|
cvr: w.sessions > 0 ? (w.units / w.sessions) * 100 : 0,
|
||||||
ctr: w.impressions > 0 ? (w.clicks / w.impressions) * 100 : 0,
|
ctr: w.impressions > 0 ? (w.clicks / w.impressions) * 100 : 0,
|
||||||
roas: w.cost > 0 ? w.adRevenue / w.cost : 0,
|
roas: w.cost > 0 ? w.adRevenue / w.cost : 0,
|
||||||
|
detail_bsr: w.bsrCount > 0 ? w.detail_bsr / w.bsrCount : 0,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +210,7 @@ function getMetricValue(w: ComputedWeeklyMetrics, metric: string): number {
|
|||||||
case 'roas': return w.roas;
|
case 'roas': return w.roas;
|
||||||
case 'revenue': return w.revenue;
|
case 'revenue': return w.revenue;
|
||||||
case 'acos': return w.cost > 0 && w.adRevenue > 0 ? (w.cost / w.adRevenue) * 100 : 0;
|
case 'acos': return w.cost > 0 && w.adRevenue > 0 ? (w.cost / w.adRevenue) * 100 : 0;
|
||||||
|
case 'detail_bsr': return w.detail_bsr;
|
||||||
default: return w.units;
|
default: return w.units;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,6 +275,13 @@ function avgMetric(data: ComputedWeeklyMetrics[], metric: string, durationWeeks:
|
|||||||
const totalCost = data.reduce((s, w) => s + w.cost, 0);
|
const totalCost = data.reduce((s, w) => s + w.cost, 0);
|
||||||
return totalAdRevenue > 0 && totalCost > 0 ? (totalCost / totalAdRevenue) * 100 : 0;
|
return totalAdRevenue > 0 && totalCost > 0 ? (totalCost / totalAdRevenue) * 100 : 0;
|
||||||
}
|
}
|
||||||
|
if (metric === 'detail_bsr') {
|
||||||
|
// For detail_bsr, we want the average of the non-zero weeks
|
||||||
|
const validWeeks = data.filter(w => w.detail_bsr > 0);
|
||||||
|
if (validWeeks.length === 0) return 0;
|
||||||
|
const sum = validWeeks.reduce((s, w) => s + w.detail_bsr, 0);
|
||||||
|
return sum / validWeeks.length;
|
||||||
|
}
|
||||||
|
|
||||||
// For absolute quantities (units, revenue, sessions), we sum them and divide by the number of weeks
|
// For absolute quantities (units, revenue, sessions), we sum them and divide by the number of weeks
|
||||||
// Use actual data.length instead of theoretical durationWeeks for accuracy
|
// Use actual data.length instead of theoretical durationWeeks for accuracy
|
||||||
@@ -272,8 +289,8 @@ function avgMetric(data: ComputedWeeklyMetrics[], metric: string, durationWeeks:
|
|||||||
return sum / data.length;
|
return sum / data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const METRICS = ['units', 'sessions', 'cvr', 'ctr', 'roas', 'revenue', 'acos'];
|
const METRICS = ['units', 'sessions', 'cvr', 'ctr', 'roas', 'revenue', 'acos', 'detail_bsr'];
|
||||||
const LOWER_IS_BETTER = new Set(['acos']);
|
const LOWER_IS_BETTER = new Set(['acos', 'detail_bsr']);
|
||||||
|
|
||||||
function computeMetricDiD(
|
function computeMetricDiD(
|
||||||
treatmentBefore: ComputedWeeklyMetrics[],
|
treatmentBefore: ComputedWeeklyMetrics[],
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ export const METRIC_LABELS: Record<string, string> = {
|
|||||||
roas: 'ROAS',
|
roas: 'ROAS',
|
||||||
revenue: 'Revenue',
|
revenue: 'Revenue',
|
||||||
acos: 'ACOS',
|
acos: 'ACOS',
|
||||||
|
detail_bsr: 'Detail Level BSR',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatMetricValue = (value: number, metric: string): string => {
|
export const formatMetricValue = (value: number, metric: string): string => {
|
||||||
@@ -290,6 +291,8 @@ export const formatMetricValue = (value: number, metric: string): string => {
|
|||||||
return `${value.toFixed(2)}x`;
|
return `${value.toFixed(2)}x`;
|
||||||
case 'revenue':
|
case 'revenue':
|
||||||
return `€${value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
return `€${value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
||||||
|
case 'detail_bsr':
|
||||||
|
return `#${value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
||||||
default:
|
default:
|
||||||
return value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
|
return value.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export interface CombinedKPIs {
|
|||||||
cpc: number;
|
cpc: number;
|
||||||
cvrUnits: number;
|
cvrUnits: number;
|
||||||
glanceViews: number; // Traffic / page views
|
glanceViews: number; // Traffic / page views
|
||||||
|
detailLevelBSR?: number; // Added for Detail Level BSR in Experiments
|
||||||
avgWeeklySales?: number; // Added for Weeks of Coverage
|
avgWeeklySales?: number; // Added for Weeks of Coverage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +296,7 @@ export interface BSRRecord {
|
|||||||
// Experiment Tracking Types
|
// Experiment Tracking Types
|
||||||
export type ExperimentType = 'pricing' | 'advertising' | 'content' | 'promotion' | 'seo';
|
export type ExperimentType = 'pricing' | 'advertising' | 'content' | 'promotion' | 'seo';
|
||||||
export type ExperimentStatus = 'planned' | 'active' | 'completed' | 'paused';
|
export type ExperimentStatus = 'planned' | 'active' | 'completed' | 'paused';
|
||||||
export type ExperimentMetric = 'units' | 'sessions' | 'cvr' | 'ctr' | 'roas' | 'revenue' | 'acos';
|
export type ExperimentMetric = 'units' | 'sessions' | 'cvr' | 'ctr' | 'roas' | 'revenue' | 'acos' | 'detail_bsr';
|
||||||
export type ExperimentVerdict = 'winner' | 'loser' | 'inconclusive';
|
export type ExperimentVerdict = 'winner' | 'loser' | 'inconclusive';
|
||||||
|
|
||||||
export interface ExperimentChangeAnnotation {
|
export interface ExperimentChangeAnnotation {
|
||||||
|
|||||||
Reference in New Issue
Block a user