f44c53c5bb
- internal/playback: 播放事件存储/MA码维度聚合/分账结算(CP60/平台34/服务费6) - service: ReportPlayback(链上状态门禁)/PlaybackSummary/ComputeSettlement - api: /data/playback, /data/playback-summary, /settlement/compute - 分账取余兜底无丢分; 未知/已下架MA码回传被拒 - 13项新测试通过; 端到端验证: 回传3条→聚合40元→分账24/13.6/2.4
84 lines
2.6 KiB
Go
84 lines
2.6 KiB
Go
// 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
|
|
}
|