Fix product performance grouped JSON build
This commit is contained in:
@@ -3553,7 +3553,7 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||
if (avgDaily > 0 && turnoverBase > 0) out[`stock_days_${suffix}`] = turnoverBase / avgDaily
|
||||
else if (qty <= 0 && stockQty > 0) out[`stock_days_${suffix}`] = 9999
|
||||
else if (!Object.prototype.hasOwnProperty.call(out, `stock_days_${suffix}`)) out[`stock_days_${suffix}`] = 0
|
||||
out[`stock_turnover_${suffix}`] = turnoverBase > 0 ? qty / turnoverBase : 0
|
||||
out[`stock_turnover_${suffix}`] = annualizedStockTurnover(qty, turnoverBase, days)
|
||||
if (qty > 0) out[`avg_price_usd_${suffix}`] = sales / qty
|
||||
const { costPrice, basePrice } = periodCostPair(out, suffix)
|
||||
out[`base_price_usd_${suffix}`] = basePrice
|
||||
@@ -3606,7 +3606,7 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||
function productPerformancePeriodDays (row, suffix) {
|
||||
if (suffix === '90d') return 90
|
||||
if (suffix === '180d') return 180
|
||||
if (suffix === '365d') return 365
|
||||
if (suffix === '365d') return 360
|
||||
if (suffix !== 'total') return 0
|
||||
const start = parseProductPerformanceDate(row?.period_start) || new Date(Date.UTC(2022, 0, 1))
|
||||
const end = parseProductPerformanceDate(row?.period_end) || parseProductPerformanceDate(row?.kpi_date)
|
||||
@@ -3648,6 +3648,7 @@ function productPeriodMetricSource (row, suffix) {
|
||||
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0)
|
||||
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||
const stockQty = Number(row?.stock_qty || 0)
|
||||
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0) || stockQty
|
||||
const turnoverKey = `stock_turnover_${suffix}`
|
||||
const hasTurnover = Object.prototype.hasOwnProperty.call(row || {}, turnoverKey)
|
||||
return {
|
||||
@@ -3657,7 +3658,7 @@ function productPeriodMetricSource (row, suffix) {
|
||||
gross_margin_cost_90d: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
|
||||
gross_margin_90d: Number(row?.[`gross_margin_${suffix}`] ?? 0),
|
||||
sales_qty_90d: qty,
|
||||
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : (stockQty > 0 ? qty / stockQty : 0),
|
||||
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : annualizedStockTurnover(qty, avgStock, productPerformancePeriodDays(row, suffix)),
|
||||
market_count_90d: periodCount(row, 'market_count', suffix),
|
||||
customer_count_90d: periodCount(row, 'customer_count', suffix),
|
||||
stock_qty: stockQty
|
||||
@@ -3704,7 +3705,7 @@ function productScore100 ({ suffix = '90d', salesUSD = 0, margin = 0, stockTurno
|
||||
const revenue = ratioScore(salesUSD, productRevenueTarget(suffix))
|
||||
return clampScore(
|
||||
0.30 * marginScore(margin) +
|
||||
0.20 * ratioScore(stockTurnover, 1.5) +
|
||||
0.20 * ratioScore(stockTurnover, stockTurnoverTarget(suffix)) +
|
||||
0.20 * revenue +
|
||||
0.15 * ratioScore(marketCount, 8) +
|
||||
0.15 * ratioScore(customerCount, 25)
|
||||
@@ -3759,6 +3760,21 @@ function customerQtyTarget (suffix) {
|
||||
return 500
|
||||
}
|
||||
|
||||
const STOCK_TURNOVER_YEAR_DAYS = 360
|
||||
|
||||
function annualizedStockTurnover (salesQty, avgStock, periodDays) {
|
||||
const qty = Number(salesQty || 0)
|
||||
const stock = Number(avgStock || 0)
|
||||
const days = Number(periodDays || 0)
|
||||
if (!Number.isFinite(qty) || !Number.isFinite(stock) || qty <= 0 || stock <= 0) return 0
|
||||
const raw = qty / stock
|
||||
return days > 0 ? raw * STOCK_TURNOVER_YEAR_DAYS / days : raw
|
||||
}
|
||||
|
||||
function stockTurnoverTarget () {
|
||||
return 4
|
||||
}
|
||||
|
||||
function periodCount (row, prefix, suffix) {
|
||||
const keyed = Number(row?.[`${prefix}_${suffix}`] || 0)
|
||||
if (keyed > 0) return keyed
|
||||
@@ -4206,10 +4222,10 @@ function withProductMargins (row) {
|
||||
const out = {
|
||||
...next,
|
||||
avg_price_usd_365d: Number(next?.sales_qty_365d || 0) > 0 ? Number(next?.sales_usd_365d || 0) / Number(next?.sales_qty_365d || 0) : 0,
|
||||
stock_turnover_90d: existingOrStockTurnover(next?.stock_turnover_90d, next?.sales_qty_90d, next?.stock_qty),
|
||||
stock_turnover_180d: existingOrStockTurnover(next?.stock_turnover_180d, next?.sales_qty_180d, next?.stock_qty),
|
||||
stock_turnover_365d: existingOrStockTurnover(next?.stock_turnover_365d, next?.sales_qty_365d, next?.stock_qty),
|
||||
stock_turnover_total: existingOrStockTurnover(next?.stock_turnover_total, next?.sales_qty_total, next?.stock_qty)
|
||||
stock_turnover_90d: existingOrStockTurnover(next?.stock_turnover_90d, next?.sales_qty_90d, next?.avg_stock_90d || next?.stock_qty, 90),
|
||||
stock_turnover_180d: existingOrStockTurnover(next?.stock_turnover_180d, next?.sales_qty_180d, next?.avg_stock_180d || next?.stock_qty, 180),
|
||||
stock_turnover_365d: existingOrStockTurnover(next?.stock_turnover_365d, next?.sales_qty_365d, next?.avg_stock_365d || next?.stock_qty, 360),
|
||||
stock_turnover_total: existingOrStockTurnover(next?.stock_turnover_total, next?.sales_qty_total, next?.avg_stock_total || next?.stock_qty, productPerformancePeriodDays(next, 'total'))
|
||||
}
|
||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||
applyPeriodProfitFields(out, suffix)
|
||||
@@ -4222,7 +4238,7 @@ function withGeneralMargins (row) {
|
||||
applyPeriodProfitFields(out, 'total')
|
||||
return {
|
||||
...out,
|
||||
stock_turnover_total: existingOrStockTurnover(out?.stock_turnover_total, out?.sales_qty_total, out?.stock_qty)
|
||||
stock_turnover_total: existingOrStockTurnover(out?.stock_turnover_total, out?.sales_qty_total, out?.avg_stock_total || out?.stock_qty, productPerformancePeriodDays(out, 'total'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4289,15 +4305,13 @@ function applyPeriodProfitFields (row, suffix) {
|
||||
row[`gross_margin_cost_${suffix}`] = marginFromSalesCost(salesUSD, qty, costPrice)
|
||||
}
|
||||
|
||||
function stockTurnover (salesQty, stockQty) {
|
||||
const stock = Number(stockQty || 0)
|
||||
if (stock <= 0) return 0
|
||||
return Number(salesQty || 0) / stock
|
||||
function stockTurnover (salesQty, stockQty, periodDays) {
|
||||
return annualizedStockTurnover(salesQty, stockQty, periodDays)
|
||||
}
|
||||
|
||||
function existingOrStockTurnover (value, salesQty, stockQty) {
|
||||
function existingOrStockTurnover (value, salesQty, stockQty, periodDays) {
|
||||
const current = Number(value)
|
||||
return value !== undefined && value !== null && Number.isFinite(current) ? current : stockTurnover(salesQty, stockQty)
|
||||
return value !== undefined && value !== null && Number.isFinite(current) ? current : stockTurnover(salesQty, stockQty, periodDays)
|
||||
}
|
||||
|
||||
function filterKey (tableKey, name) {
|
||||
|
||||
Reference in New Issue
Block a user