Files
Nonemm/src/Nonemm.Contests/ScoreBreakdown.cs
T

73 lines
2.8 KiB
C#

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<int> 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<ScoreBreakdownRow> For(IEnumerable<Qso> qsos, Contest contest)
{
List<Qso> counted = qsos.Where(q => q.IsInScore).ToList();
List<ScoreBreakdownRow> 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<Qso> qsos)
{
List<Qso> 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),
]);
}
}