using System.Globalization; using Nonemm.Core; namespace Nonemm.Contests; /// One row of N1MM's score query: the contacts of one band and mode, or of all /// of them when `Band` is `ScoreBreakdown.TotalBand`. `Kind` is empty except in /// WAE, which gives contacts and traffic a row each. public sealed record ScoreBreakdownRow( double Band, string Mode, string Kind, int Qsos, int Points, IReadOnlyList Multipliers); /// The score the way N1MM's database query splits it: a row per band and mode /// of the contact, with the multiplier flags added up, and a total row last /// when there is any contact. /// `ContestLog.Rows` splits by band alone, which is what the score window shows. public static class ScoreBreakdown { /// The band N1MM gives the total row. public const double TotalBand = 900_000_000; public const string TotalMode = "Both"; /// Ordered the way the query orders them: by the band as text, so `10.0` /// comes before `3.5`, then by mode. Only contacts in the claimed score /// are counted. public static IReadOnlyList For(IEnumerable qsos, Contest contest) { List counted = qsos.Where(q => q.IsInScore).ToList(); List rows = counted .Where(q => q.Mode.Name.Length > 0) .GroupBy(q => (Band: Rounded(q), Mode: q.Mode.Name, Kind: contest.RowKindFor(q))) .OrderBy(g => BandText(g.Key.Band), StringComparer.Ordinal) .ThenBy(g => g.Key.Mode, StringComparer.Ordinal) .ThenBy(g => g.Key.Kind, StringComparer.Ordinal) .Select(g => Row(g.Key.Band, g.Key.Mode, g.Key.Kind, g)) .ToList(); // the query's total is a group of the same rows, so an empty log has none if (counted.Count > 0) { rows.Add(Row(TotalBand, TotalMode, contest.HasQtcTraffic ? "All" : "", counted)); } return rows; } /// The band as the database stores it: the megahertz label to one decimal. private static double Rounded(Qso qso) => Math.Round(qso.Band?.MegahertzLabel ?? 0, 1, MidpointRounding.AwayFromZero); /// What SQLite writes for a real number cast to text. private static string BandText(double band) => band.ToString("0.0", CultureInfo.InvariantCulture); private static ScoreBreakdownRow Row(double band, string mode, string kind, IEnumerable qsos) { List rows = [.. qsos]; return new ScoreBreakdownRow( band, mode, kind, rows.Count, rows.Sum(q => q.Points), [ rows.Count(q => q.IsMultiplier1), rows.Count(q => q.IsMultiplier2), rows.Count(q => q.IsMultiplier3), ]); } }