Fix product performance grouped JSON build
This commit is contained in:
@@ -3937,8 +3937,8 @@ func ListProductPerformanceGroupedFilterOptions(ctx context.Context, pg *sql.DB,
|
||||
if err := EnsureProductPerformanceTables(pg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Limit <= 0 || req.Limit > 5000 {
|
||||
req.Limit = 5000
|
||||
if req.Limit <= 0 || req.Limit > 50000 {
|
||||
req.Limit = 50000
|
||||
}
|
||||
levels := sanitizeProductPerformanceGroupLevels(req.GroupLevels)
|
||||
if len(levels) == 0 {
|
||||
@@ -4460,22 +4460,22 @@ FROM (
|
||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_usd_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS avg_price_usd_90d,
|
||||
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(sales_usd_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS avg_price_usd_180d,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN cost_price_usd * sales_qty_total WHEN stock_variant_rank = 1 THEN cost_price_usd * stock_qty ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0)
|
||||
ELSE 0
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN cost_price_usd * sales_qty_total ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END),0)
|
||||
ELSE COALESCE(AVG(NULLIF(cost_price_usd,0)),0)
|
||||
END AS cost_price_usd,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN base_price_usd * sales_qty_total WHEN stock_variant_rank = 1 THEN base_price_usd * stock_qty ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0)
|
||||
ELSE 0
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN base_price_usd * sales_qty_total ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END),0)
|
||||
ELSE COALESCE(AVG(NULLIF(base_price_usd,0)),0)
|
||||
END AS base_price_usd,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN base_price_try * sales_qty_total WHEN stock_variant_rank = 1 THEN base_price_try * stock_qty ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0)
|
||||
ELSE 0
|
||||
WHEN SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN sales_qty_total > 0 THEN base_price_try * sales_qty_total ELSE 0 END)
|
||||
/ NULLIF(SUM(CASE WHEN sales_qty_total > 0 THEN sales_qty_total ELSE 0 END),0)
|
||||
ELSE COALESCE(AVG(NULLIF(base_price_try,0)),0)
|
||||
END AS base_price_try,
|
||||
COALESCE(SUM(gross_profit_usd_90d),0) AS gross_profit_usd_90d,
|
||||
COALESCE(SUM(gross_profit_usd_180d),0) AS gross_profit_usd_180d,
|
||||
@@ -5275,7 +5275,7 @@ func (n *productPerformanceGroupedSnapshotNode) add(row map[string]any) {
|
||||
n.Avg[key] = state
|
||||
}
|
||||
number := floatFromAny(value)
|
||||
weight := floatFromMap(row, productPerformanceMetricWeightField(key))
|
||||
weight := productPerformanceMetricWeight(row, key)
|
||||
if weight > 0 {
|
||||
state.Weighted += number * weight
|
||||
state.Weight += weight
|
||||
@@ -5722,15 +5722,33 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
||||
if _, ok := out["net_stock_after_order"]; ok || orderQty > 0 {
|
||||
out["net_stock_after_order"] = floatFromMap(out, "stock_qty") - orderQty
|
||||
}
|
||||
out["customer_score_90d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "90d"))
|
||||
out["customer_score_180d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "180d"))
|
||||
out["customer_score_365d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "365d"))
|
||||
out["customer_score_total"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "total"))
|
||||
out["performance_score_90d"] = productPerformanceSalesPeriodScore(out, "90d")
|
||||
out["performance_score_180d"] = productPerformanceSalesPeriodScore(out, "180d")
|
||||
out["performance_score_365d"] = productPerformanceSalesPeriodScore(out, "365d")
|
||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
||||
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
||||
if _, ok := out["customer_score_90d"]; !ok {
|
||||
out["customer_score_90d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "90d"))
|
||||
}
|
||||
if _, ok := out["customer_score_180d"]; !ok {
|
||||
out["customer_score_180d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "180d"))
|
||||
}
|
||||
if _, ok := out["customer_score_365d"]; !ok {
|
||||
out["customer_score_365d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "365d"))
|
||||
}
|
||||
if _, ok := out["customer_score_total"]; !ok {
|
||||
out["customer_score_total"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "total"))
|
||||
}
|
||||
if _, ok := out["performance_score_90d"]; !ok {
|
||||
out["performance_score_90d"] = productPerformanceSalesPeriodScore(out, "90d")
|
||||
}
|
||||
if _, ok := out["performance_score_180d"]; !ok {
|
||||
out["performance_score_180d"] = productPerformanceSalesPeriodScore(out, "180d")
|
||||
}
|
||||
if _, ok := out["performance_score_365d"]; !ok {
|
||||
out["performance_score_365d"] = productPerformanceSalesPeriodScore(out, "365d")
|
||||
}
|
||||
if _, ok := out["performance_score_total"]; !ok {
|
||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
||||
}
|
||||
if _, ok := out["performance_score"]; !ok {
|
||||
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
||||
}
|
||||
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
||||
score := productPerformanceOrderGroupScore(out)
|
||||
out["performance_score_90d"] = score
|
||||
@@ -6087,6 +6105,9 @@ func shouldAverageProductPerformanceField(field string) bool {
|
||||
}
|
||||
|
||||
func productPerformanceMetricWeightField(field string) string {
|
||||
if productPerformanceUsesCostWeight(field) {
|
||||
return "__product_cost_weight"
|
||||
}
|
||||
if strings.Contains(field, "_180d") {
|
||||
return "sales_qty_180d"
|
||||
}
|
||||
@@ -6105,11 +6126,44 @@ func productPerformanceMetricWeightField(field string) string {
|
||||
return "sales_qty_90d"
|
||||
}
|
||||
|
||||
func productPerformanceUsesCostWeight(field string) bool {
|
||||
if !(strings.HasPrefix(field, "base_price") || strings.HasPrefix(field, "cost_price")) {
|
||||
return false
|
||||
}
|
||||
return !strings.Contains(field, "_90d") &&
|
||||
!strings.Contains(field, "_180d") &&
|
||||
!strings.Contains(field, "_365d") &&
|
||||
!strings.Contains(field, "_total")
|
||||
}
|
||||
|
||||
func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
||||
weightField := productPerformanceMetricWeightField(field)
|
||||
if weightField == "__product_cost_weight" {
|
||||
return productPerformanceCostWeight(row)
|
||||
}
|
||||
return floatFromMap(row, weightField)
|
||||
}
|
||||
|
||||
func productPerformanceCostWeight(row map[string]any) float64 {
|
||||
if weight := floatFromMap(row, "sales_qty_total"); weight > 0 {
|
||||
return weight
|
||||
}
|
||||
for _, field := range []string{"sales_qty_365d", "sales_qty_180d", "sales_qty_90d"} {
|
||||
if weight := floatFromMap(row, field); weight > 0 {
|
||||
return weight
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
||||
var weighted, qty float64
|
||||
for _, row := range rows {
|
||||
value := floatFromMap(row, valueField)
|
||||
weight := floatFromMap(row, qtyField)
|
||||
if qtyField == "__product_cost_weight" {
|
||||
weight = productPerformanceCostWeight(row)
|
||||
}
|
||||
if weight > 0 {
|
||||
weighted += value * weight
|
||||
qty += weight
|
||||
@@ -6118,9 +6172,50 @@ func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qt
|
||||
if qty > 0 {
|
||||
return weighted / qty
|
||||
}
|
||||
if qtyField == "__product_cost_weight" {
|
||||
return averageProductPerformanceDistinctVariantField(rows, valueField)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func averageProductPerformanceDistinctVariantField(rows []map[string]any, valueField string) float64 {
|
||||
seen := map[string]bool{}
|
||||
sum := 0.0
|
||||
count := 0.0
|
||||
hasKey := false
|
||||
for _, row := range rows {
|
||||
key := productPerformanceMapVariantKey(row)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
hasKey = true
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
value := floatFromMap(row, valueField)
|
||||
if value <= 0 {
|
||||
continue
|
||||
}
|
||||
sum += value
|
||||
count++
|
||||
}
|
||||
if !hasKey {
|
||||
for _, row := range rows {
|
||||
value := floatFromMap(row, valueField)
|
||||
if value <= 0 {
|
||||
continue
|
||||
}
|
||||
sum += value
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count <= 0 {
|
||||
return 0
|
||||
}
|
||||
return sum / count
|
||||
}
|
||||
|
||||
func dominantProductPerformanceValue(rows []map[string]any, field string) string {
|
||||
counts := map[string]int{}
|
||||
for _, row := range rows {
|
||||
|
||||
Reference in New Issue
Block a user