Fix product performance grouped JSON build

This commit is contained in:
M_Kececi
2026-07-06 16:54:36 +03:00
parent 3993c363cb
commit 5d4216d42e
3 changed files with 69 additions and 38 deletions
@@ -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) {