73 lines
2.8 KiB
C#
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),
|
|
]);
|
|
}
|
|
}
|