package domain import "sort" // levelPoints 按档位编码取分值;分值全部来自规则包,代码不内置任何数字。 func (c RecordBonusConfig) levelPoints(code string) (float64, bool) { for _, l := range c.Levels { if l.Code == code { return l.Points, true } } return 0, false } // RecordBonusPoints 把「已审核通过」的破纪录登记按报名项聚合: // 同一人同时破多级时,max 取最高档、sum 累加,口径由配置决定。 // 未配置的档位视为无加分,避免脏 level 凭空加分。 func RecordBonusPoints(cfg RecordBonusConfig, breaks []RecordBreak) []RecordBonus { if !cfg.Enabled || len(breaks) == 0 { return nil } type group struct { first RecordBreak levels []string points []float64 } order := make([]int64, 0, len(breaks)) groups := make(map[int64]*group) for _, b := range breaks { if b.State != RecordBreakApproved { continue } pts, ok := cfg.levelPoints(b.Level) if !ok { continue } g, exists := groups[b.EntryID] if !exists { g = &group{first: b} groups[b.EntryID] = g order = append(order, b.EntryID) } seen := false for _, code := range g.levels { if code == b.Level { seen = true break } } if !seen { g.levels = append(g.levels, b.Level) g.points = append(g.points, pts) } } out := make([]RecordBonus, 0, len(order)) for _, entryID := range order { g := groups[entryID] bonus := RecordBonus{ EntryID: entryID, EventID: g.first.EventID, AthleteID: g.first.AthleteID, TeamID: g.first.TeamID, Levels: g.levels, } if cfg.Mode == RecordBonusSum { for _, p := range g.points { bonus.Points += p } } else { for _, p := range g.points { if p > bonus.Points { bonus.Points = p } } } out = append(out, bonus) } sort.SliceStable(out, func(i, j int) bool { return out[i].EntryID < out[j].EntryID }) return out } // recordBonusLedger 把破纪录加分写成流水。 // 加分与成绩分相互独立:不乘项目权重、也不乘组别权重(case 6 的口径)。 // 只处理本组别的报名项:上层按组别逐个调用 ScoreEvent,不加组别过滤会让同一笔加分每组各记一次。 func recordBonusLedger(in EventScoreInput) ([]LedgerEntry, error) { if in.RecordBonus == nil || !in.RecordBonus.Enabled || len(in.RecordBreaks) == 0 { return nil, nil } breaks := make([]RecordBreak, 0, len(in.RecordBreaks)) for _, b := range in.RecordBreaks { if in.Event.ID != 0 && b.EventID != 0 && b.EventID != in.Event.ID { continue } breaks = append(breaks, b) } bonuses := RecordBonusPoints(*in.RecordBonus, breaks) if len(bonuses) == 0 { return nil, nil } byID := make(map[int64]Entry, len(in.Entries)) for _, e := range in.Entries { if in.Category.ID != 0 && e.CategoryID != in.Category.ID { continue } byID[e.ID] = e } var out []LedgerEntry for _, b := range bonuses { if b.Points == 0 { continue } e, ok := byID[b.EntryID] if !ok { continue } athleteID := b.AthleteID if athleteID == 0 { athleteID = e.AthleteID } for _, s := range e.resolvedShares(in.Options.UnionAttribution) { out = append(out, LedgerEntry{ TeamID: s.TeamID, AthleteID: athleteID, EventID: in.Event.ID, CategoryID: in.Category.ID, EntryID: e.ID, SourceType: SourceRecordBonus, SourceID: e.ID, Points: b.Points * s.Weight, }) } } return out, nil }