Fix product performance grouped JSON build
This commit is contained in:
@@ -445,12 +445,12 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_price_dim (
|
||||
`
|
||||
UPDATE mk_product_performance_price_dim
|
||||
SET
|
||||
cost_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||
base_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||
cost_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||
base_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||
updated_at = now()
|
||||
WHERE cost_price_usd > 0
|
||||
AND base_price_usd > 0
|
||||
AND cost_price_usd < base_price_usd`,
|
||||
AND cost_price_usd > base_price_usd`,
|
||||
`
|
||||
DELETE FROM mk_product_performance_price_dim
|
||||
WHERE upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')`,
|
||||
@@ -527,20 +527,20 @@ WHERE upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00D
|
||||
`
|
||||
UPDATE mk_product_performance_kpi_daily
|
||||
SET
|
||||
cost_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||
base_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||
gross_profit_usd_90d = COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * GREATEST(cost_price_usd, base_price_usd)),
|
||||
gross_profit_usd_180d = COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * GREATEST(cost_price_usd, base_price_usd)),
|
||||
gross_margin_90d = CASE WHEN COALESCE(sales_usd_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * GREATEST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_90d,0) END,
|
||||
gross_margin_180d = CASE WHEN COALESCE(sales_usd_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * GREATEST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_180d,0) END,
|
||||
unit_profit_cost_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_cost_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_base_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_base_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||
cost_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||
base_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||
gross_profit_usd_90d = COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * LEAST(cost_price_usd, base_price_usd)),
|
||||
gross_profit_usd_180d = COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * LEAST(cost_price_usd, base_price_usd)),
|
||||
gross_margin_90d = CASE WHEN COALESCE(sales_usd_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * LEAST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_90d,0) END,
|
||||
gross_margin_180d = CASE WHEN COALESCE(sales_usd_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * LEAST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_180d,0) END,
|
||||
unit_profit_cost_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_cost_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_base_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||
unit_profit_base_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||
updated_at = now()
|
||||
WHERE cost_price_usd > 0
|
||||
AND base_price_usd > 0
|
||||
AND cost_price_usd < base_price_usd`,
|
||||
AND cost_price_usd > base_price_usd`,
|
||||
`
|
||||
UPDATE mk_product_performance_kpi_daily
|
||||
SET askili_yan = '', updated_at = now()
|
||||
@@ -4486,7 +4486,29 @@ FROM (
|
||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS sales_index_90d,
|
||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(price_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS price_index_90d,
|
||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(margin_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS margin_index_90d,
|
||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(performance_score * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS performance_score,
|
||||
CASE
|
||||
WHEN SUM(CASE
|
||||
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||
ELSE 1
|
||||
END) > 0
|
||||
THEN SUM(performance_score * CASE
|
||||
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||
ELSE 1
|
||||
END) / NULLIF(SUM(CASE
|
||||
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||
ELSE 1
|
||||
END),0)
|
||||
ELSE 0
|
||||
END AS performance_score,
|
||||
MODE() WITHIN GROUP (ORDER BY performance_bucket) AS performance_bucket,
|
||||
COALESCE(MODE() WITHIN GROUP (ORDER BY NULLIF(recommendation,'')), '') AS recommendation,
|
||||
COALESCE(SUM(CASE WHEN stock_variant_rank = 1 THEN idle_cost_usd ELSE 0 END),0) AS idle_cost_usd
|
||||
@@ -5167,7 +5189,7 @@ type productPerformanceGroupedSnapshotNode struct {
|
||||
Count int
|
||||
Children map[string]*productPerformanceGroupedSnapshotNode
|
||||
ChildOrder []string
|
||||
StockSeen map[string]bool
|
||||
StockMetricSeen map[string]map[string]bool
|
||||
IdleSeen map[string]bool
|
||||
Avg map[string]*productPerformanceGroupedSnapshotAvgState
|
||||
BucketCounts map[string]int
|
||||
@@ -5235,19 +5257,8 @@ func (n *productPerformanceGroupedSnapshotNode) add(row map[string]any) {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case key == "stock_qty":
|
||||
variantKey := productPerformanceMapVariantKey(row)
|
||||
if variantKey == "" {
|
||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||
continue
|
||||
}
|
||||
if n.StockSeen == nil {
|
||||
n.StockSeen = map[string]bool{}
|
||||
}
|
||||
if !n.StockSeen[variantKey] {
|
||||
n.StockSeen[variantKey] = true
|
||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||
}
|
||||
case isProductPerformanceDistinctVariantStockMetric(key):
|
||||
n.addDistinctVariantMetric(row, key, value)
|
||||
case key == "idle_cost_usd":
|
||||
variantKey := productPerformanceMapVariantKey(row)
|
||||
if variantKey == "" {
|
||||
@@ -5289,6 +5300,27 @@ func (n *productPerformanceGroupedSnapshotNode) add(row map[string]any) {
|
||||
n.addDistinctSpread(row)
|
||||
}
|
||||
|
||||
func (n *productPerformanceGroupedSnapshotNode) addDistinctVariantMetric(row map[string]any, key string, value any) {
|
||||
variantKey := productPerformanceMapVariantKey(row)
|
||||
if variantKey == "" {
|
||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||
return
|
||||
}
|
||||
if n.StockMetricSeen == nil {
|
||||
n.StockMetricSeen = map[string]map[string]bool{}
|
||||
}
|
||||
seen := n.StockMetricSeen[key]
|
||||
if seen == nil {
|
||||
seen = map[string]bool{}
|
||||
n.StockMetricSeen[key] = seen
|
||||
}
|
||||
if seen[variantKey] {
|
||||
return
|
||||
}
|
||||
seen[variantKey] = true
|
||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||
}
|
||||
|
||||
func (n *productPerformanceGroupedSnapshotNode) addDistinctSpread(row map[string]any) {
|
||||
market := displayProductPerformanceMarketName(stringFromMap(row, "market_key"))
|
||||
if market != "" && market != "STOK" {
|
||||
@@ -5355,11 +5387,11 @@ func (n *productPerformanceGroupedSnapshotNode) snapshotRow() map[string]any {
|
||||
row[key] = state.Sum / state.Count
|
||||
}
|
||||
}
|
||||
applyProductPerformanceDistinctSpread(row, n.Market90Seen, n.MarketTotalSeen, n.Customer90Seen, n.CustomerTotalSeen)
|
||||
deriveProductPerformanceGroupMetrics(row, n.Field)
|
||||
if bucket := n.dominantBucket(); bucket != "" {
|
||||
row["performance_bucket"] = bucket
|
||||
}
|
||||
applyProductPerformanceDistinctSpread(row, n.Market90Seen, n.MarketTotalSeen, n.Customer90Seen, n.CustomerTotalSeen)
|
||||
clearProductPerformanceGroupDimensions(row, n.Field, n.Value)
|
||||
row["__group"] = true
|
||||
row["row_key"] = "group|" + n.Key
|
||||
@@ -5489,8 +5521,8 @@ func aggregateProductPerformanceRows(rows []map[string]any, groupField string) m
|
||||
if isProductPerformanceMarginField(key) {
|
||||
continue
|
||||
}
|
||||
if key == "stock_qty" {
|
||||
out[key] = distinctProductPerformanceVariantStockQty(rows)
|
||||
if isProductPerformanceDistinctVariantStockMetric(key) {
|
||||
out[key] = distinctProductPerformanceVariantNumber(rows, key)
|
||||
continue
|
||||
}
|
||||
if key == "idle_cost_usd" {
|
||||
@@ -5517,9 +5549,9 @@ func aggregateProductPerformanceRows(rows []map[string]any, groupField string) m
|
||||
}
|
||||
}
|
||||
}
|
||||
applyProductPerformanceDistinctSpread(out, market90Seen, marketTotalSeen, customer90Seen, customerTotalSeen)
|
||||
deriveProductPerformanceGroupMetrics(out, groupField)
|
||||
out["performance_bucket"] = dominantProductPerformanceValue(rows, "performance_bucket")
|
||||
applyProductPerformanceDistinctSpread(out, market90Seen, marketTotalSeen, customer90Seen, customerTotalSeen)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -5613,7 +5645,11 @@ func isProductPerformanceMarginField(field string) bool {
|
||||
return strings.HasPrefix(field, "gross_margin")
|
||||
}
|
||||
|
||||
func distinctProductPerformanceVariantStockQty(rows []map[string]any) float64 {
|
||||
func isProductPerformanceDistinctVariantStockMetric(field string) bool {
|
||||
return field == "stock_qty" || strings.HasPrefix(field, "avg_stock_")
|
||||
}
|
||||
|
||||
func distinctProductPerformanceVariantNumber(rows []map[string]any, field string) float64 {
|
||||
seen := map[string]bool{}
|
||||
total := 0.0
|
||||
hasKey := false
|
||||
@@ -5627,13 +5663,13 @@ func distinctProductPerformanceVariantStockQty(rows []map[string]any) float64 {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
total += floatFromMap(row, "stock_qty")
|
||||
total += floatFromMap(row, field)
|
||||
}
|
||||
if hasKey {
|
||||
return total
|
||||
}
|
||||
for _, row := range rows {
|
||||
total += floatFromMap(row, "stock_qty")
|
||||
total += floatFromMap(row, field)
|
||||
}
|
||||
return total
|
||||
}
|
||||
@@ -5674,36 +5710,57 @@ func productPerformanceMapVariantKey(row map[string]any) string {
|
||||
}
|
||||
|
||||
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
||||
normalizeProductPerformanceCostFields(out)
|
||||
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
||||
sales := floatFromMap(out, "sales_usd_"+suffix)
|
||||
qty := floatFromMap(out, "sales_qty_"+suffix)
|
||||
stockQty := floatFromMap(out, "stock_qty")
|
||||
if _, exists := out["stock_turnover_"+suffix]; !exists && stockQty > 0 {
|
||||
out["stock_turnover_"+suffix] = qty / stockQty
|
||||
turnoverBase := floatFromMap(out, "avg_stock_"+suffix)
|
||||
if turnoverBase <= 0 {
|
||||
turnoverBase = stockQty
|
||||
}
|
||||
if turnoverBase > 0 {
|
||||
out["stock_turnover_"+suffix] = qty / turnoverBase
|
||||
} else {
|
||||
out["stock_turnover_"+suffix] = 0
|
||||
}
|
||||
if qty > 0 {
|
||||
out["avg_price_usd_"+suffix] = sales / qty
|
||||
}
|
||||
basePrice, hasBasePrice := productPerformanceUnitCostForSuffix(out, "base_price_usd", suffix)
|
||||
costPrice, hasCostPrice := productPerformanceUnitCostForSuffix(out, "cost_price_usd", suffix)
|
||||
if hasBasePrice {
|
||||
out["base_price_usd_"+suffix] = basePrice
|
||||
}
|
||||
if hasCostPrice {
|
||||
out["cost_price_usd_"+suffix] = costPrice
|
||||
}
|
||||
if qty > 0 && hasBasePrice {
|
||||
out["unit_profit_base_"+suffix] = (sales / qty) - basePrice
|
||||
avgPrice := sales / qty
|
||||
out["unit_profit_base_"+suffix] = avgPrice - basePrice
|
||||
out["gross_profit_base_usd_"+suffix] = sales - (qty * basePrice)
|
||||
} else {
|
||||
out["unit_profit_base_"+suffix] = 0
|
||||
out["gross_profit_base_usd_"+suffix] = 0
|
||||
}
|
||||
if qty > 0 && hasCostPrice {
|
||||
out["unit_profit_cost_"+suffix] = (sales / qty) - costPrice
|
||||
avgPrice := sales / qty
|
||||
out["unit_profit_cost_"+suffix] = avgPrice - costPrice
|
||||
out["gross_profit_cost_usd_"+suffix] = sales - (qty * costPrice)
|
||||
out["gross_profit_usd_"+suffix] = out["gross_profit_cost_usd_"+suffix]
|
||||
} else {
|
||||
out["unit_profit_cost_"+suffix] = 0
|
||||
out["gross_profit_cost_usd_"+suffix] = 0
|
||||
out["gross_profit_usd_"+suffix] = 0
|
||||
}
|
||||
if sales > 0 {
|
||||
if _, ok := out["gross_profit_base_usd_"+suffix]; ok {
|
||||
out["gross_margin_base_"+suffix] = floatFromMap(out, "gross_profit_base_usd_"+suffix) / sales
|
||||
}
|
||||
if _, ok := out["gross_profit_cost_usd_"+suffix]; ok {
|
||||
out["gross_margin_cost_"+suffix] = floatFromMap(out, "gross_profit_cost_usd_"+suffix) / sales
|
||||
}
|
||||
if _, ok := out["gross_profit_usd_"+suffix]; ok {
|
||||
out["gross_margin_"+suffix] = floatFromMap(out, "gross_profit_usd_"+suffix) / sales
|
||||
}
|
||||
out["gross_margin_base_"+suffix] = floatFromMap(out, "gross_profit_base_usd_"+suffix) / sales
|
||||
out["gross_margin_cost_"+suffix] = floatFromMap(out, "gross_profit_cost_usd_"+suffix) / sales
|
||||
out["gross_margin_"+suffix] = floatFromMap(out, "gross_profit_usd_"+suffix) / sales
|
||||
} else {
|
||||
out["gross_margin_base_"+suffix] = 0
|
||||
out["gross_margin_cost_"+suffix] = 0
|
||||
out["gross_margin_"+suffix] = 0
|
||||
}
|
||||
}
|
||||
orderUSD := floatFromMap(out, "order_usd")
|
||||
@@ -5743,7 +5800,11 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
||||
}
|
||||
if _, ok := out["performance_score"]; !ok {
|
||||
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
||||
if score, exists := productPerformanceOptionalFloat(out, "performance_score_90d"); exists {
|
||||
out["performance_score"] = score
|
||||
} else {
|
||||
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
||||
}
|
||||
}
|
||||
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
||||
score := productPerformanceOrderGroupScore(out)
|
||||
@@ -5751,16 +5812,53 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
||||
out["performance_score_180d"] = score
|
||||
out["performance_score_365d"] = score
|
||||
out["performance_score_total"] = score
|
||||
out["performance_score"] = score
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeProductPerformanceCostFields(row map[string]any) {
|
||||
normalize := func(costField, baseField string) {
|
||||
cost, hasCost := productPerformanceOptionalFloat(row, costField)
|
||||
base, hasBase := productPerformanceOptionalFloat(row, baseField)
|
||||
if !hasCost && !hasBase {
|
||||
return
|
||||
}
|
||||
cost, base = normalizeProductPerformanceCostPair(cost, base)
|
||||
if hasCost || cost > 0 {
|
||||
row[costField] = cost
|
||||
}
|
||||
if hasBase || base > 0 {
|
||||
row[baseField] = base
|
||||
}
|
||||
}
|
||||
normalize("cost_price_usd", "base_price_usd")
|
||||
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
||||
normalize("cost_price_usd_"+suffix, "base_price_usd_"+suffix)
|
||||
}
|
||||
}
|
||||
|
||||
func productPerformanceOptionalFloat(row map[string]any, field string) (float64, bool) {
|
||||
value, ok := row[field]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return floatFromAny(value), true
|
||||
}
|
||||
|
||||
func productPerformanceUnitCostForSuffix(row map[string]any, baseField, suffix string) (float64, bool) {
|
||||
hasSuffix := false
|
||||
if value, ok := row[baseField+"_"+suffix]; ok {
|
||||
return floatFromAny(value), true
|
||||
hasSuffix = true
|
||||
if n := floatFromAny(value); n > 0 {
|
||||
return n, true
|
||||
}
|
||||
}
|
||||
if value, ok := row[baseField]; ok {
|
||||
return floatFromAny(value), true
|
||||
}
|
||||
if hasSuffix {
|
||||
return 0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -6099,6 +6197,7 @@ func shouldAverageProductPerformanceField(field string) bool {
|
||||
strings.HasPrefix(field, "expected_margin") ||
|
||||
strings.HasPrefix(field, "sales_index") ||
|
||||
strings.HasPrefix(field, "performance_score") ||
|
||||
strings.HasPrefix(field, "customer_score") ||
|
||||
strings.HasPrefix(field, "stock_days") ||
|
||||
strings.HasPrefix(field, "stock_turnover")
|
||||
}
|
||||
@@ -6136,6 +6235,9 @@ func productPerformanceUsesCostWeight(field string) bool {
|
||||
}
|
||||
|
||||
func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
||||
if productPerformanceUsesScoreWeight(field) {
|
||||
return productPerformanceScoreWeight(row, productPerformanceScoreSuffix(field))
|
||||
}
|
||||
weightField := productPerformanceMetricWeightField(field)
|
||||
if weightField == "__product_cost_weight" {
|
||||
return productPerformanceCostWeight(row)
|
||||
@@ -6143,6 +6245,44 @@ func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
||||
return floatFromMap(row, weightField)
|
||||
}
|
||||
|
||||
func productPerformanceUsesScoreWeight(field string) bool {
|
||||
return strings.HasPrefix(field, "performance_score") ||
|
||||
strings.HasPrefix(field, "customer_score")
|
||||
}
|
||||
|
||||
func productPerformanceScoreSuffix(field string) string {
|
||||
switch {
|
||||
case strings.Contains(field, "_180d"):
|
||||
return "180d"
|
||||
case strings.Contains(field, "_365d"):
|
||||
return "365d"
|
||||
case strings.Contains(field, "_total"):
|
||||
return "total"
|
||||
default:
|
||||
return "90d"
|
||||
}
|
||||
}
|
||||
|
||||
func productPerformanceScoreWeight(row map[string]any, suffix string) float64 {
|
||||
if weight := floatFromMap(row, "sales_usd_"+suffix); weight > 0 {
|
||||
return weight
|
||||
}
|
||||
if weight := floatFromMap(row, "sales_qty_"+suffix); weight > 0 {
|
||||
return weight
|
||||
}
|
||||
stockQty := floatFromMap(row, "stock_qty")
|
||||
if stockQty <= 0 {
|
||||
return 1
|
||||
}
|
||||
if cost := floatFromMap(row, "cost_price_usd_"+suffix); cost > 0 {
|
||||
return stockQty * cost
|
||||
}
|
||||
if cost := floatFromMap(row, "cost_price_usd"); cost > 0 {
|
||||
return stockQty * cost
|
||||
}
|
||||
return stockQty
|
||||
}
|
||||
|
||||
func productPerformanceCostWeight(row map[string]any) float64 {
|
||||
if weight := floatFromMap(row, "sales_qty_total"); weight > 0 {
|
||||
return weight
|
||||
@@ -6156,6 +6296,9 @@ func productPerformanceCostWeight(row map[string]any) float64 {
|
||||
}
|
||||
|
||||
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
||||
if productPerformanceUsesScoreWeight(valueField) {
|
||||
return weightedAverageProductPerformanceScoreRows(rows, valueField, productPerformanceScoreSuffix(valueField))
|
||||
}
|
||||
var weighted, qty float64
|
||||
for _, row := range rows {
|
||||
value := floatFromMap(row, valueField)
|
||||
@@ -6177,6 +6320,30 @@ func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qt
|
||||
return 0
|
||||
}
|
||||
|
||||
func weightedAverageProductPerformanceScoreRows(rows []map[string]any, valueField, suffix string) float64 {
|
||||
var weighted, weight, sum, count float64
|
||||
for _, row := range rows {
|
||||
value, ok := productPerformanceOptionalFloat(row, valueField)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rowWeight := productPerformanceScoreWeight(row, suffix)
|
||||
if rowWeight > 0 {
|
||||
weighted += value * rowWeight
|
||||
weight += rowWeight
|
||||
}
|
||||
sum += value
|
||||
count++
|
||||
}
|
||||
if weight > 0 {
|
||||
return weighted / weight
|
||||
}
|
||||
if count > 0 {
|
||||
return sum / count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func averageProductPerformanceDistinctVariantField(rows []map[string]any, valueField string) float64 {
|
||||
seen := map[string]bool{}
|
||||
sum := 0.0
|
||||
@@ -6642,7 +6809,7 @@ func productPerformanceVariantKey(productCode, colorCode, yakaKodu string) strin
|
||||
}
|
||||
|
||||
func normalizeProductPerformanceCostPair(costPriceUSD, basePriceUSD float64) (float64, float64) {
|
||||
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD < basePriceUSD {
|
||||
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD > basePriceUSD {
|
||||
return basePriceUSD, costPriceUSD
|
||||
}
|
||||
return costPriceUSD, basePriceUSD
|
||||
|
||||
@@ -549,11 +549,11 @@ PriceDim AS (
|
||||
COALESCE(urun_ana_grubu, '') AS urun_ana_grubu,
|
||||
COALESCE(urun_alt_grubu, '') AS urun_alt_grubu,
|
||||
CASE
|
||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd < base_price_usd THEN base_price_usd
|
||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN base_price_usd
|
||||
ELSE cost_price_usd
|
||||
END AS cost_price_usd,
|
||||
CASE
|
||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd < base_price_usd THEN cost_price_usd
|
||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN cost_price_usd
|
||||
ELSE base_price_usd
|
||||
END AS base_price_usd,
|
||||
base_price_try
|
||||
|
||||
Reference in New Issue
Block a user