fix: 补齐渠道全量合计筛选导出及有效端目标遗漏

This commit is contained in:
Yvan
2026-11-05 10:57:06 +08:00
parent 297a892518
commit 1aa15d3dc8
22 changed files with 545 additions and 88 deletions
+68 -5
View File
@@ -2,6 +2,7 @@ package business
import (
"context"
"math/big"
"sort"
"strings"
"time"
@@ -59,7 +60,13 @@ func prepareAnalytics(db *gorm.DB, scope business.AnalyticsScope, f business.Ana
return len(rows) == 0, true, nil
}
func analyticsQuery(db *gorm.DB, scope business.AnalyticsScope, f business.AnalyticsFilter, table string, mode int) *gorm.DB {
q := db.Table(table+" r").Where("r.stat_date >= ? AND r.stat_date <= ? AND r.dedup_mode = ? AND r.game_id > 0", f.Begin, f.End, mode)
q := db.Table(table+" r").Where("r.dedup_mode = ? AND r.game_id > 0", mode)
if f.Begin != "" {
q = q.Where("r.stat_date >= ?", f.Begin)
}
if f.End != "" {
q = q.Where("r.stat_date <= ?", f.End)
}
if len(f.Sources) > 0 {
q = q.Where("r.source_db IN ?", f.Sources)
}
@@ -165,6 +172,7 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
var global []portraitCountPO
globalPay := "0"
withGlobal := kind == business.ChannelAnalytics && scope.Viewer.All
metricFiltered := kind == business.ChannelAnalytics && (f.HasRegister != nil || f.HasPay != nil)
group, queryContext := errgroup.WithContext(ctx)
// 每个请求最多两个并行查询,复用同一存储客户端;结果读取完后再组装 DO。
group.SetLimit(2)
@@ -183,7 +191,7 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
}
return nil
})
if withGlobal {
if withGlobal && !metricFiltered {
group.Go(func() error {
q := analyticsQuery(queryDB, scope, f, "tab_sw_report_day_key", mode)
if err := q.Select("r.metric,COUNT(DISTINCT " + portraitIdentity(queryDB, scope.IDCardDedup) + ") AS count").Group("r.metric").Scan(&global).Error; err != nil {
@@ -208,6 +216,23 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
putAnalyticsCount(&rowFor(po).Counts, po.Metric, po.Count)
}
}
// Keep visible channels with no events in the selected period so that
// "no registration / no recharge" can find genuinely inactive channels.
if kind == business.ChannelAnalytics && len(f.GameNames) == 0 {
root := "p.id"
if scope.IncludeSub {
root = "CASE WHEN p.promote_level=1 THEN p.id WHEN p.top_promote_id>0 THEN p.top_promote_id ELSE p.id END"
}
var channels []analyticsAggPO
if err := analyticsPromotes(db, scope, f, "p").Select("DISTINCT p.source_db," + root + " AS entity_id").Scan(&channels).Error; err != nil {
return nil, business.ErrStorage
}
for _, po := range channels {
if po.ID != nil {
rowFor(po)
}
}
}
if kind == business.ChannelAnalytics && !scope.Viewer.All {
if err := filterAnalyticsRows(db, scope, f, rowMap); err != nil {
return nil, err
@@ -218,6 +243,33 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
return nil, err
}
}
if metricFiltered {
for key, row := range rowMap {
pay, ok := new(big.Rat).SetString(row.Pay)
if !ok {
return nil, business.ErrStorage
}
if f.HasRegister != nil && (row.Counts.Register > 0) != *f.HasRegister || f.HasPay != nil && (pay.Sign() > 0) != *f.HasPay {
delete(rowMap, key)
}
}
// Recompute the administrator's distinct-player totals over matched
// channel trees, before pagination. Summing per-row ratios is incorrect.
if withGlobal && len(rowMap) > 0 {
keys := make([]business.AnalyticsKey, 0, len(rowMap))
for key := range rowMap {
keys = append(keys, key)
}
q, id := analyticsRoot(db, kind, scope, f, "tab_sw_report_day_key", mode, false)
if err := q.Where("(r.source_db,"+id+") IN ?", analyticsPairs(keys)).Select("r.metric,COUNT(DISTINCT " + portraitIdentity(db, scope.IDCardDedup) + ") AS count").Group("r.metric").Scan(&global).Error; err != nil {
return nil, business.ErrStorage
}
q, id = analyticsRoot(db, kind, scope, f, "tab_sw_report_day", 0, true)
if err := q.Where("(r.source_db,"+id+") IN ?", analyticsPairs(keys)).Select("COALESCE(SUM(r.total_pay),0)").Scan(&globalPay).Error; err != nil {
return nil, business.ErrStorage
}
}
}
if withGlobal {
totals := toPortraitCounts(global)
result.GlobalCounts, result.GlobalPay = &totals, globalPay
@@ -227,15 +279,26 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
}
if kind == business.ChannelAnalytics {
location := time.FixedZone("Asia/Shanghai", 8*3600)
begin, _ := time.ParseInLocation("2006-01-02", f.Begin, location)
end, _ := time.ParseInLocation("2006-01-02", f.End, location)
root := "p.id"
if scope.IncludeSub {
root = "CASE WHEN p.promote_level=1 THEN p.id WHEN p.top_promote_id>0 THEN p.top_promote_id ELSE p.id END"
}
keys := []business.AnalyticsKey{}
for key := range rowMap {
keys = append(keys, key)
}
for start := 0; start < len(keys); start += 300 {
var count int64
if err := db.Table("sys_business_channel_effectiveness e").Joins("JOIN sys_business_effectiveness_state s ON s.id=1 AND s.rule_key=e.rule_key").Where("(e.source_db,e.promote_id) IN ? AND e.qualified_at>=? AND e.qualified_at<?", analyticsPairs(keys[start:min(start+300, len(keys))]), begin.Unix(), end.AddDate(0, 0, 1).Unix()).Count(&count).Error; err != nil {
q := analyticsPromotes(db, scope, f, "p").Joins("JOIN sys_business_channel_effectiveness e ON e.source_db=p.source_db AND e.promote_id=p.id").Joins("JOIN sys_business_effectiveness_state s ON s.id=1 AND s.rule_key=e.rule_key").Where("(p.source_db,"+root+") IN ? AND e.qualified_at IS NOT NULL", analyticsPairs(keys[start:min(start+300, len(keys))]))
if f.Begin != "" {
begin, _ := time.ParseInLocation("2006-01-02", f.Begin, location)
q = q.Where("e.qualified_at >= ?", begin.Unix())
}
if f.End != "" {
end, _ := time.ParseInLocation("2006-01-02", f.End, location)
q = q.Where("e.qualified_at < ?", end.AddDate(0, 0, 1).Unix())
}
if err := q.Count(&count).Error; err != nil {
return nil, business.ErrStorage
}
result.EffectiveCount += count