// Package playback 实现以 MA 码为维度的播放数据聚合与分账结算(二期 F09/F18)。 // 对应需求9(统一维度数据聚合)、需求21(可信播放数据与分账依据)。 // // MVP 阶段用内存存储;生产可替换为 ClickHouse(明细)+ 链上锚定(可信摘要)。 package playback import ( "fmt" "sync" "github.com/tcs-iptv/tcs/internal/model" ) // Store 播放事件存储与聚合。 type Store struct { mu sync.RWMutex events map[string][]model.PlaybackEvent // maCode -> events } // NewStore 创建播放数据存储。 func NewStore() *Store { return &Store{events: make(map[string][]model.PlaybackEvent)} } // Ingest 批量写入播放事件(幂等性由上层保证;此处仅追加)。 // 返回接收条数。 func (s *Store) Ingest(events []model.PlaybackEvent) int { s.mu.Lock() defer s.mu.Unlock() n := 0 for _, e := range events { if e.MACode == "" { continue } s.events[e.MACode] = append(s.events[e.MACode], e) n++ } return n } // Summary 按 MA 码聚合可信播放数据(需求9-AC2、需求21-AC1)。 func (s *Store) Summary(maCode string) model.PlaybackSummary { s.mu.RLock() defer s.mu.RUnlock() sum := model.PlaybackSummary{MACode: maCode, ByPlatform: map[string]model.PlatformMetric{}} for _, e := range s.events[maCode] { pm := sum.ByPlatform[e.PlatformID] switch e.EventType { case model.EventPlay: sum.TotalPlays++ pm.Plays++ case model.EventComplete: sum.TotalComplete++ pm.Complete++ } sum.TotalRevenue += e.RevenueCent pm.RevenueCent += e.RevenueCent sum.ByPlatform[e.PlatformID] = pm } return sum } // ComputeSettlement 基于聚合的可信播放收益执行分账(需求21-AC3)。 // 分账依据明确标注为"链上可信播放数据",保证 CP 与运营商口径一致。 func (s *Store) ComputeSettlement(maCode, period string, cfg model.RevenueShareConfig) (model.Settlement, error) { if cfg.CPShareBp+cfg.PlatformShareBp+cfg.HubFeeBp != 10000 { return model.Settlement{}, fmt.Errorf("playback: share config must sum to 10000bp, got %d", cfg.CPShareBp+cfg.PlatformShareBp+cfg.HubFeeBp) } sum := s.Summary(maCode) total := sum.TotalRevenue cp := total * int64(cfg.CPShareBp) / 10000 platform := total * int64(cfg.PlatformShareBp) / 10000 // 服务费取余数,保证三者之和精确等于 total(避免取整丢分) hub := total - cp - platform return model.Settlement{ MACode: maCode, Period: period, TotalRevenue: total, CPShare: cp, PlatformShare: platform, HubFee: hub, DataSource: "链上可信播放数据", }, nil }