mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:45:24 +02:00
feat: add units/revenue toggle to Weekly Sales and improve traffic data parsing
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# Data Processing Patterns
|
||||
|
||||
## Table of Contents
|
||||
- [Column Mapping](#column-mapping)
|
||||
- [Currency Parsing](#currency-parsing)
|
||||
- [Month Normalization](#month-normalization)
|
||||
- [CSV Processing](#csv-processing)
|
||||
- [Excel Processing](#excel-processing)
|
||||
- [Filtering](#filtering)
|
||||
- [Aggregation](#aggregation)
|
||||
- [Key Constants](#key-constants)
|
||||
|
||||
## Column Mapping
|
||||
|
||||
Use `getColumnValue(row, aliases[])` to flexibly extract values from rows with varying header names:
|
||||
|
||||
```typescript
|
||||
const customer = getColumnValue(row, ['NEW CUSTOMER', 'Customer', 'Client', 'Account', 'Partner', 'COUNTRY']);
|
||||
const asin = getColumnValue(row, ['CUSTOMER REFERENCE', 'AMAZON ASIN', 'ASIN', 'PRODUCT ID']);
|
||||
const sku = getColumnValue(row, ['RAW ARTICLE NO.', 'SKU', 'Item No']);
|
||||
```
|
||||
|
||||
The function normalizes keys to lowercase and returns the first matching alias value.
|
||||
|
||||
## Currency Parsing
|
||||
|
||||
`parseCurrency()` handles EU and US formats:
|
||||
- EU: `1.234,56` → removes dots, replaces comma with period
|
||||
- US: `1,234.56` → removes commas
|
||||
- Strips currency symbols (`€`, `$`, `£`)
|
||||
- Returns `0` on parse failure
|
||||
|
||||
`parseUnits()` for integers — removes all dots and commas, parses as integer.
|
||||
|
||||
## Month Normalization
|
||||
|
||||
`normalizeMonth()` handles:
|
||||
- English short/long: `Jan`, `January`
|
||||
- Spanish: `Enero` → `Jan`, `Febrero` → `Feb`, `Marzo` → `Mar`, `Abril` → `Apr`, `Agosto` → `Aug`, `Diciembre` → `Dec`
|
||||
- Numeric: `01` → `Jan`, `1` → `Jan`
|
||||
- Combined: `Apr-23` → `Apr` (extracts month part)
|
||||
- Excel serial dates: `45544` → converts to month name
|
||||
|
||||
Mapping via `MONTH_MAP` constant (lowercase keys to 3-letter English months).
|
||||
|
||||
## CSV Processing
|
||||
|
||||
```typescript
|
||||
Papa.parse(fileOrContent, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results) => {
|
||||
const data = results.data
|
||||
.map((row, index) => mapRowToRecord(row, index))
|
||||
.filter(r => r.year > 2023 && isAllowedCustomer(r.customer));
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Excel Processing
|
||||
|
||||
```typescript
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
const rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
// Header row is index 0, data starts at index 1
|
||||
const headers = rawData[0];
|
||||
const data = rawData.slice(1).map((row, index) => {
|
||||
const obj = Object.fromEntries(headers.map((h, i) => [h, row[i]]));
|
||||
return mapRowToRecord(obj, index);
|
||||
});
|
||||
```
|
||||
|
||||
For multi-sheet workbooks (like Ads Weekly), iterate `workbook.SheetNames` and process each sheet with the year derived from the sheet name.
|
||||
|
||||
## Filtering
|
||||
|
||||
### Sales Filtering (`filterData`)
|
||||
Multi-dimensional filtering across: customer, year, month, week, line, asin, sku, title, stock, vendorStock, woc, bulkSearch, and columnFilters.
|
||||
|
||||
### Ads Filtering (`filterAdsData`)
|
||||
When no customer filter is selected, defaults to `PAN_EU_COUNTRIES` only. This means unfiltered ads data shows DE + IT + FR + ES combined.
|
||||
|
||||
### Column Filters
|
||||
Support Excel-style operators: `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `endsWith`, `gt`, `lt`, `gte`, `lte`. Plus `selectedValues` for checkbox filtering and `sort` for column sorting.
|
||||
|
||||
### Numeric Conditions (`checkNumericConditions`)
|
||||
Supports range strings (`"10-20"`), comparison operators (`">5"`, `"<=100"`), and special labels (`"Out of Stock"`, `"Low Stock"`, `"In Stock"`).
|
||||
|
||||
## Aggregation
|
||||
|
||||
`aggregateData()` produces:
|
||||
- `totalSellOut`, `totalUnits` — grand totals
|
||||
- `totalsByYear` — `Record<string, { sellOut, units }>`
|
||||
- `byLine` — revenue and units per product line
|
||||
- `byCustomer` — revenue per marketplace
|
||||
- `seasonality` — monthly trends across years
|
||||
- `topMovers`, `bottomMovers` — YoY growth/decline rankings
|
||||
- `comparisonPeriods` — which periods are being compared
|
||||
- `topLinesSplit`, `byCustomerSplit`, `byLineOverviewSplit` — yearly breakdown data
|
||||
|
||||
## Key Constants
|
||||
|
||||
```typescript
|
||||
const MONTH_ORDER = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const PAN_EU_COUNTRIES = ['Amazon DE', 'Amazon IT', 'Amazon FR', 'Amazon ES'];
|
||||
const ALLOWED_CUSTOMERS = ['Amazon DE', 'Amazon IT', 'Amazon FR', 'Amazon ES', 'Amazon UK', 'Amazon SC'];
|
||||
```
|
||||
|
||||
Country name normalization maps: "Germany"/"Deutschland"/"DE"/"Alemania" → "Amazon DE", and similar for other countries.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Type Definitions
|
||||
|
||||
All types live in `types.ts` at the project root.
|
||||
|
||||
## Core Record Types
|
||||
|
||||
```typescript
|
||||
interface SalesRecord {
|
||||
id: string;
|
||||
customer: string; // "Amazon DE", "Amazon UK", etc.
|
||||
year: number;
|
||||
month: string; // "Apr-23" format
|
||||
week?: number;
|
||||
asin: string;
|
||||
sku: string;
|
||||
title: string;
|
||||
articleName: string;
|
||||
units: number;
|
||||
sellOut: number; // € value
|
||||
line: string; // Product line
|
||||
}
|
||||
|
||||
interface AdsRecord {
|
||||
country: string; // "Amazon DE", etc.
|
||||
year: number;
|
||||
week: number;
|
||||
asin: string;
|
||||
cost: number;
|
||||
clicks: number;
|
||||
impressions: number;
|
||||
cpc: number;
|
||||
ctr: number;
|
||||
acos: number;
|
||||
conversions: number;
|
||||
attributedUnits30d: number;
|
||||
attributedSales30d: number;
|
||||
}
|
||||
|
||||
interface TrafficRecord {
|
||||
country: string;
|
||||
year: number;
|
||||
week: number;
|
||||
asin: string;
|
||||
glanceViews: number;
|
||||
}
|
||||
|
||||
interface ForecastRecord {
|
||||
asin: string;
|
||||
annualForecast: number;
|
||||
sku?: string;
|
||||
title?: string;
|
||||
line?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Filter Types
|
||||
|
||||
```typescript
|
||||
interface FilterState {
|
||||
customer: string[];
|
||||
year: string[];
|
||||
month: string[];
|
||||
line: string[];
|
||||
asin: string[];
|
||||
sku: string[];
|
||||
title: string[];
|
||||
week: string[];
|
||||
stock: string[];
|
||||
vendorStock: string[];
|
||||
woc: string[]; // Weeks of Coverage
|
||||
bulkSearch: string;
|
||||
columnFilters: Record<string, ColumnFilterCondition>;
|
||||
}
|
||||
|
||||
interface ColumnFilterCondition {
|
||||
textFilter?: {
|
||||
operator: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'startsWith' | 'notStartsWith' | 'endsWith' | 'notEndsWith' | 'gt' | 'lt' | 'gte' | 'lte';
|
||||
value: string;
|
||||
};
|
||||
selectedValues?: string[];
|
||||
sort?: 'asc' | 'desc';
|
||||
}
|
||||
```
|
||||
|
||||
## Aggregated Data
|
||||
|
||||
```typescript
|
||||
interface AggregatedData {
|
||||
totalSellOut: number;
|
||||
totalUnits: number;
|
||||
totalsByYear: Record<string, { sellOut: number; units: number }>;
|
||||
byLine: { name: string; value: number; units: number }[];
|
||||
byCustomer: { name: string; value: number }[];
|
||||
seasonality: SeasonalityPoint[];
|
||||
seasonalityUnits: SeasonalityPoint[];
|
||||
availableYears: string[];
|
||||
topMovers: GrowthMetric[];
|
||||
bottomMovers: GrowthMetric[];
|
||||
comparisonPeriods: { current: string; previous: string };
|
||||
topLinesSplit: YearlySplitData[];
|
||||
byCustomerSplit: YearlySplitData[];
|
||||
byLineOverviewSplit: YearlySplitData[];
|
||||
}
|
||||
```
|
||||
|
||||
## Growth Metrics
|
||||
|
||||
```typescript
|
||||
interface GrowthMetric {
|
||||
line: string;
|
||||
currentYearSellOut: number;
|
||||
previousYearSellOut: number;
|
||||
sellOutGrowthValue: number;
|
||||
sellOutGrowthPercentage: number;
|
||||
currentYearUnits: number;
|
||||
previousYearUnits: number;
|
||||
unitsGrowthValue: number;
|
||||
unitsGrowthPercentage: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Combined KPIs (Sales + Ads merged)
|
||||
|
||||
```typescript
|
||||
interface CombinedKPIs {
|
||||
id: string;
|
||||
marketplace: string;
|
||||
customer: string;
|
||||
month: string;
|
||||
week: number;
|
||||
year: number;
|
||||
asin: string;
|
||||
title: string;
|
||||
line: string;
|
||||
sku: string;
|
||||
salesTotal: number;
|
||||
unitsTotal: number;
|
||||
salesAds: number;
|
||||
unitsAds: number;
|
||||
cost: number;
|
||||
clicks: number;
|
||||
impressions: number;
|
||||
conversions: number;
|
||||
salesOrganic: number;
|
||||
unitsOrganic: number;
|
||||
paidSalesShare: number;
|
||||
organicSalesShare: number;
|
||||
acos: number;
|
||||
tacos: number;
|
||||
roas: number;
|
||||
ctr: number;
|
||||
cpc: number;
|
||||
cvrUnits: number;
|
||||
glanceViews: number;
|
||||
avgWeeklySales?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Pivot Table
|
||||
|
||||
```typescript
|
||||
interface PivotRow {
|
||||
id: string;
|
||||
customer: string;
|
||||
line: string;
|
||||
title: string;
|
||||
articleName: string;
|
||||
sku: string;
|
||||
asin: string;
|
||||
totalsByYear: Record<string, YearlyData>;
|
||||
months: MonthlyPivot[]; // Always 12 elements
|
||||
adsByYear?: Record<string, {
|
||||
adSpend: number;
|
||||
attributedSales: number;
|
||||
acos: number;
|
||||
tacos: number;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user