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
+36 -19
View File
@@ -3804,10 +3804,10 @@ SELECT
avg_stock_180d,
avg_stock_365d,
avg_stock_total,
CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) ELSE 0 END AS stock_turnover_90d,
CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) ELSE 0 END AS stock_turnover_180d,
CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) * 4.0 ELSE 0 END AS stock_turnover_90d,
CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) * 2.0 ELSE 0 END AS stock_turnover_180d,
CASE WHEN avg_stock_365d > 0 THEN sales_qty_365d / NULLIF(avg_stock_365d,0) ELSE 0 END AS stock_turnover_365d,
CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) ELSE 0 END AS stock_turnover_total,
CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) * 360.0 / GREATEST(1, (current_date - DATE '2022-01-01') + 1) ELSE 0 END AS stock_turnover_total,
has_cost,
sales_index_90d,
CASE WHEN NOT has_cost THEN 0 WHEN sales_usd_90d <= 0 THEN 1 ELSE
@@ -3845,7 +3845,7 @@ SELECT
CASE WHEN NOT has_cost THEN 0 WHEN sales_usd_90d <= 0 THEN 1 ELSE
ROUND(
LEAST(30, GREATEST(gross_margin_cost_90d,0) / 0.40 * 30)
+ LEAST(20, (CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) ELSE 0 END) / 1.5 * 20)
+ LEAST(20, (CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) * 4.0 ELSE 0 END) / 4.0 * 20)
+ LEAST(20, sales_usd_90d / 10000 * 20)
+ LEAST(15, market_count_90d / 8.0 * 15)
+ LEAST(15, customer_count_90d / 25.0 * 15),
@@ -4517,10 +4517,10 @@ SELECT (
'stock_days_180d', stock_days_180d,
'stock_days_365d', stock_days_365d,
'stock_days_total', stock_days_total,
'stock_turnover_90d', CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) ELSE 0 END,
'stock_turnover_180d', CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) ELSE 0 END,
'stock_turnover_90d', CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) * 4.0 ELSE 0 END,
'stock_turnover_180d', CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) * 2.0 ELSE 0 END,
'stock_turnover_365d', CASE WHEN avg_stock_365d > 0 THEN sales_qty_365d / NULLIF(avg_stock_365d,0) ELSE 0 END,
'stock_turnover_total', CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) ELSE 0 END
'stock_turnover_total', CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) * 360.0 / GREATEST(1, ((SELECT kpi_date FROM LatestKPIDate) - DATE '2022-01-01') + 1) ELSE 0 END
)
) AS row_json
FROM (
@@ -4643,10 +4643,10 @@ func queryProductPerformanceSQLLeafRows(ctx context.Context, pg *sql.DB, mode st
query := productPerformanceSQLSourceCTE(mode) + fmt.Sprintf(`
SELECT to_jsonb(t) || jsonb_build_object(
'row_key', 'leaf|' || product_code || '|' || color_code || '|' || yaka_kodu || '|' || market_key,
'stock_turnover_90d', CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) ELSE 0 END,
'stock_turnover_180d', CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) ELSE 0 END,
'stock_turnover_90d', CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) * 4.0 ELSE 0 END,
'stock_turnover_180d', CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) * 2.0 ELSE 0 END,
'stock_turnover_365d', CASE WHEN avg_stock_365d > 0 THEN sales_qty_365d / NULLIF(avg_stock_365d,0) ELSE 0 END,
'stock_turnover_total', CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) ELSE 0 END
'stock_turnover_total', CASE WHEN avg_stock_total > 0 THEN sales_qty_total / NULLIF(avg_stock_total,0) * 360.0 / GREATEST(1, ((SELECT kpi_date FROM LatestKPIDate) - DATE '2022-01-01') + 1) ELSE 0 END
) AS row_json
FROM Source t
%s
@@ -6000,11 +6000,7 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
} else if _, ok := out["stock_days_"+suffix]; !ok {
out["stock_days_"+suffix] = 0
}
if turnoverBase > 0 {
out["stock_turnover_"+suffix] = qty / turnoverBase
} else {
out["stock_turnover_"+suffix] = 0
}
out["stock_turnover_"+suffix] = productPerformanceAnnualizedStockTurnover(qty, turnoverBase, days)
if qty > 0 {
out["avg_price_usd_"+suffix] = sales / qty
}
@@ -6111,7 +6107,7 @@ func productPerformancePeriodDays(row map[string]any, suffix string) float64 {
case "180d":
return 180
case "365d":
return 365
return 360
case "total":
start := parseProductPerformanceDate(stringFromMap(row, "period_start"))
if start.IsZero() {
@@ -6130,6 +6126,19 @@ func productPerformancePeriodDays(row map[string]any, suffix string) float64 {
}
}
const productPerformanceStockTurnoverYearDays = 360.0
func productPerformanceAnnualizedStockTurnover(salesQty, avgStock, periodDays float64) float64 {
if salesQty <= 0 || avgStock <= 0 {
return 0
}
raw := salesQty / avgStock
if periodDays <= 0 {
return raw
}
return raw * productPerformanceStockTurnoverYearDays / periodDays
}
func parseProductPerformanceDate(value string) time.Time {
value = strings.TrimSpace(value)
if value == "" {
@@ -6206,8 +6215,12 @@ func productPerformanceSalesPeriodScore(row map[string]any, suffix string) float
salesQty := floatFromMap(row, "sales_qty_"+suffix)
stockTurnover := floatFromMap(row, "stock_turnover_"+suffix)
if stockTurnover == 0 {
if stockQty := floatFromMap(row, "stock_qty"); stockQty > 0 {
stockTurnover = salesQty / stockQty
avgStock := floatFromMap(row, "avg_stock_"+suffix)
if avgStock <= 0 {
avgStock = floatFromMap(row, "stock_qty")
}
if avgStock > 0 {
stockTurnover = productPerformanceAnnualizedStockTurnover(salesQty, avgStock, productPerformancePeriodDays(row, suffix))
}
}
return productPerformanceProductScore(
@@ -6267,13 +6280,17 @@ func productPerformanceProductScore(suffix string, salesUSD, salesIndex, margin,
}
revenueScore := productPerformanceRevenueScore(suffix, salesUSD, salesIndex, productPerformanceProductRevenueTarget(suffix))
score := 0.30*productPerformanceMarginComponentScore(margin) +
0.20*productPerformanceRatioScore(stockTurnover, 1.50) +
0.20*productPerformanceRatioScore(stockTurnover, productPerformanceStockTurnoverTarget(suffix)) +
0.20*revenueScore +
0.15*productPerformanceRatioScore(marketCount, 8) +
0.15*productPerformanceRatioScore(customerCount, 25)
return productPerformanceRoundScore(score)
}
func productPerformanceStockTurnoverTarget(suffix string) float64 {
return 4
}
func productPerformanceCustomerScore(suffix string, salesUSD, margin, productGroupCount, salesQty float64) float64 {
if salesUSD <= 0 {
return 1
+4 -4
View File
@@ -638,10 +638,10 @@ Scored AS (
CASE WHEN b.avg_daily_sales_180d = 0 THEN 0 ELSE b.avg_stock_180d / NULLIF(b.avg_daily_sales_180d, 0) END AS stock_days_180d,
CASE WHEN b.avg_daily_sales_365d = 0 THEN 0 ELSE b.avg_stock_365d / NULLIF(b.avg_daily_sales_365d, 0) END AS stock_days_365d,
CASE WHEN b.avg_daily_sales_total = 0 THEN 0 ELSE b.avg_stock_total / NULLIF(b.avg_daily_sales_total, 0) END AS stock_days_total,
CASE WHEN b.avg_stock_90d = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(b.avg_stock_90d, 0) END AS stock_turnover_90d,
CASE WHEN b.avg_stock_180d = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(b.avg_stock_180d, 0) END AS stock_turnover_180d,
CASE WHEN b.avg_stock_90d = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(b.avg_stock_90d, 0) * 4.0 END AS stock_turnover_90d,
CASE WHEN b.avg_stock_180d = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(b.avg_stock_180d, 0) * 2.0 END AS stock_turnover_180d,
CASE WHEN b.avg_stock_365d = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(b.avg_stock_365d, 0) END AS stock_turnover_365d,
CASE WHEN b.avg_stock_total = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(b.avg_stock_total, 0) END AS stock_turnover_total,
CASE WHEN b.avg_stock_total = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(b.avg_stock_total, 0) * 360.0 / GREATEST(1, ($1::date - DATE '2022-01-01') + 1) END AS stock_turnover_total,
CASE WHEN COALESCE(m.market_avg_sales_qty_90d, 0) = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(m.market_avg_sales_qty_90d, 0) END AS sales_index_90d,
CASE WHEN COALESCE(m.market_avg_sales_qty_180d, 0) = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(m.market_avg_sales_qty_180d, 0) END AS sales_index_180d,
CASE WHEN COALESCE(m.market_avg_sales_qty_365d, 0) = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(m.market_avg_sales_qty_365d, 0) END AS sales_index_365d,
@@ -687,7 +687,7 @@ SELECT
+ LEAST(25, GREATEST(COALESCE(gross_margin_90d, 0), 0) * 50)
+ LEAST(20, COALESCE(customer_count_90d, 0) * 2)
+ LEAST(10, COALESCE(market_count_90d, 0) * 3)
+ CASE WHEN COALESCE(stock_days_90d, 0) BETWEEN 10 AND 90 THEN 10 WHEN COALESCE(stock_days_90d, 0) > 180 THEN -10 ELSE 0 END
+ LEAST(20, COALESCE(stock_turnover_90d, 0) / 4.0 * 20)
, 4)
END AS performance_score,
CASE