The score summary now splits the way N1MM's does. `ContestLog` keeps a tally per band, and per kind of row where the contest has kinds, and the score window reads those instead of adding the rows up itself. Three things were wrong with the old sum: - The band rows counted a multiplier as one whatever the band. WAE weights it by band — `ScoreRow.AddContact` adds four on 3.5, three on 7 and two on 14, 21 and 28 — so the band rows did not add up to the total. - The band rows counted contacts left out of the claimed score. - There was no split by kind. `CWAE.LogInfo` and `ScoreClass.Description` give WAE a `Q/QTC` column and a row per band and kind, so contacts, traffic sent and traffic received are read apart. The Cabrillo writer now pads the record name to seven characters, which is what `ConvertCabrilloStructStdToString` does and why: `X-QSO:` is six characters and a space, so every record name puts the frequency in the same column. Nonemm wrote five, and the `X-QSO:` lines added last commit did not line up with the rest. WAE's own layouts follow `CWAE.CabrilloString`: three spaces after the sent serial rather than the one every other column gets, and the transmitter column only for a multi-operator entry. `QtcTraffic.ToSend` no longer offers a contact marked as a dupe, which is what `DBSupport.QTCRecordsetCount` leaves out with `Trim(ContactType) = ''`. Checked by running the program against a WAE SSB log of six contacts and a series of ten received: the band rows read 4, 3, 2 and 2 and add up to the 11 in the total row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
123 lines
4.0 KiB
C#
123 lines
4.0 KiB
C#
using Avalonia.Controls;
|
|
using Nonemm.Contests;
|
|
using Nonemm.Core;
|
|
|
|
namespace Nonemm.App.Windows;
|
|
|
|
/// Contacts, points and multipliers band by band.
|
|
public sealed partial class ScoreWindow : RefreshableWindow
|
|
{
|
|
private readonly AppSession session;
|
|
private int columnCount = 4;
|
|
|
|
public ScoreWindow(AppSession session)
|
|
{
|
|
this.session = session;
|
|
InitializeComponent();
|
|
Refresh();
|
|
}
|
|
|
|
|
|
public override void Refresh()
|
|
{
|
|
Table.Children.Clear();
|
|
Table.ColumnDefinitions.Clear();
|
|
Table.RowDefinitions.Clear();
|
|
if (session.Logging is null)
|
|
{
|
|
TotalText.Text = "no contest is open";
|
|
return;
|
|
}
|
|
|
|
// the log keeps the same split N1MM's score window shows, so the band
|
|
// rows carry the weighted multipliers and leave out what is not in the
|
|
// claimed score
|
|
List<KeyValuePair<ScoreKey, ScoreTally>> lines = session.Logging.Log.Rows
|
|
.OrderBy(r => Bands.Named(r.Key.Band)?.MegahertzLabel ?? double.MaxValue)
|
|
.ThenBy(r => r.Key.Kind, StringComparer.Ordinal)
|
|
.ToList();
|
|
bool hasKinds = session.Logging.Contest.HasQtcTraffic;
|
|
|
|
columnCount = hasKinds ? 5 : 4;
|
|
for (int column = 0; column < columnCount; column++)
|
|
{
|
|
Table.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
|
|
}
|
|
AddRow(0, "Band", "Q/QTC", "QSOs", "Points", "Mults", header: true);
|
|
|
|
int row = 1;
|
|
foreach ((ScoreKey key, ScoreTally line) in lines)
|
|
{
|
|
AddRow(
|
|
row++,
|
|
key.Band,
|
|
key.Kind,
|
|
line.Qsos.ToString(),
|
|
line.Points.ToString(),
|
|
line.TotalMultipliers.ToString());
|
|
}
|
|
AddRow(
|
|
row,
|
|
"Total",
|
|
hasKinds ? "All" : "",
|
|
session.Logging.Log.Tally.Qsos.ToString(),
|
|
session.Logging.Log.Tally.Points.ToString(),
|
|
session.Logging.Log.Tally.TotalMultipliers.ToString(),
|
|
header: true);
|
|
|
|
TimeSpan on = OperatingTime.On(
|
|
session.Logging.Log.Qsos,
|
|
session.Logging.Contest.MinimumOffTime);
|
|
TimeSpan off = OperatingTime.Off(
|
|
session.Logging.Log.Qsos,
|
|
session.Logging.Contest.MinimumOffTime);
|
|
TotalText.Text =
|
|
$"Claimed score {session.Logging.Log.TotalScore:N0}"
|
|
+ $" on {Hours(on)} off {Hours(off)}"
|
|
+ BandChanges();
|
|
}
|
|
|
|
/// The band changes made and the allowance, for a contest that limits
|
|
/// them, and nothing at all for the contests that do not.
|
|
private string BandChanges()
|
|
{
|
|
BandChangeRules rules = session.Logging!.Contest.BandChangesFor(session.Logging.Log.Entry);
|
|
return rules.IsCounted
|
|
? $" band changes {rules.CountedIn(session.Logging.Log.Qsos, DateTime.UtcNow)}/{rules.Max}"
|
|
: "";
|
|
}
|
|
|
|
/// Hours and minutes, which is how a sponsor asks for operating time.
|
|
private static string Hours(TimeSpan time) =>
|
|
$"{(int)time.TotalHours}:{time.Minutes:00}";
|
|
|
|
private void AddRow(
|
|
int row,
|
|
string band,
|
|
string kind,
|
|
string qsos,
|
|
string points,
|
|
string mults,
|
|
bool header = false)
|
|
{
|
|
Table.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
|
|
string[] cells = columnCount == 5
|
|
? [band, kind, qsos, points, mults]
|
|
: [band, qsos, points, mults];
|
|
for (int column = 0; column < cells.Length; column++)
|
|
{
|
|
TextBlock text = new()
|
|
{
|
|
Text = cells[column],
|
|
FontFamily = new Avalonia.Media.FontFamily("monospace"),
|
|
FontSize = 13,
|
|
Opacity = header ? 1.0 : 0.85,
|
|
Margin = new Avalonia.Thickness(0, 2, 8, 2),
|
|
};
|
|
Grid.SetRow(text, row);
|
|
Grid.SetColumn(text, column);
|
|
Table.Children.Add(text);
|
|
}
|
|
}
|
|
}
|