Say how many QTCs are left with the station being worked
WAE gives a point for every QTC line, so an operator needs to know what is still there to exchange. The entry window now carries N1MM's QTCinfo line in WAE: how many QTC lines have already gone with the station in the callsign box, how many more may go, and how many contacts are left to report to anybody. The QTC window shows the same line. The numbers come from the log rows, the way N1MM works them out: - exchanged is every QTC row with that call, both directions and all bands, and ten is the lot - available is every contact not reported to anybody yet, without the dupes and without the station itself, which is N1MM's QTCRecordsetCount - what is left with the station is what is free of its ten, and no more than the contacts there are to report when we are the one reporting Europe on CW and SSB only takes traffic down, so its line leaves the last number out, as N1MM's does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
This commit is contained in:
@@ -15,6 +15,28 @@ public enum QtcDirection
|
||||
Either,
|
||||
}
|
||||
|
||||
/// The QTC tally for one station, as N1MM writes it beside the callsign:
|
||||
/// how many lines have already gone with it, how many more may go, and how
|
||||
/// many logged contacts are left to report to anybody.
|
||||
public sealed record QtcCounts(QtcDirection Role, int Exchanged, int LeftWith, int Available)
|
||||
{
|
||||
/// N1MM's wording, one line per role. Europe on CW and SSB only takes
|
||||
/// traffic down, so there is no pile of contacts to report and the line
|
||||
/// leaves that number out.
|
||||
public string Text => Role switch
|
||||
{
|
||||
QtcDirection.Receive => $"QTCs: {Exchanged} rcvd/{LeftWith} left to exch.",
|
||||
QtcDirection.Send =>
|
||||
$"QTCs: {Exchanged} sent/{LeftWith} left to exch/{Available} total QTCs avail",
|
||||
QtcDirection.Either =>
|
||||
$"QTCs: {Exchanged} exch/{LeftWith} left to exch/{Available} total QTCs avail",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
/// N1MM puts this in the status bar as well when a station has had its ten.
|
||||
public bool IsFull => Exchanged >= WaeQtc.MostPerStation;
|
||||
}
|
||||
|
||||
/// The WAE QTC rules that do not need a window: who may exchange traffic with
|
||||
/// whom, which contacts are still worth reporting, how the series are numbered,
|
||||
/// and whether a line an operator has typed reads as a QTC.
|
||||
@@ -87,6 +109,46 @@ public sealed class QtcTraffic
|
||||
public int Remaining(Callsign other) =>
|
||||
Math.Max(0, WaeQtc.MostPerStation - ExchangedWith(other));
|
||||
|
||||
/// Which way traffic goes here whoever is worked: Europe takes it down on
|
||||
/// CW and SSB, the rest of the world reads it out, and on RTTY it goes
|
||||
/// both ways. `DirectionFor` answers the same question about one station;
|
||||
/// this one ignores the station, so the counts still read out when the
|
||||
/// callsign box is empty or holds a station with nothing left.
|
||||
public QtcDirection Role
|
||||
{
|
||||
get
|
||||
{
|
||||
if (position.Contest is not Wae contest)
|
||||
{
|
||||
return QtcDirection.None;
|
||||
}
|
||||
if (contest.Modes.Contains(ModeCategory.Digital))
|
||||
{
|
||||
return QtcDirection.Either;
|
||||
}
|
||||
return position.Me.Continent == "EU" ? QtcDirection.Receive : QtcDirection.Send;
|
||||
}
|
||||
}
|
||||
|
||||
/// How many logged contacts are still worth reporting to this station: the
|
||||
/// whole pile rather than one series of ten. This is N1MM's
|
||||
/// `QTCRecordsetCount`, which is zero for a European station on CW and SSB
|
||||
/// because it only takes traffic down.
|
||||
public int Available(Callsign other) =>
|
||||
Role == QtcDirection.Receive ? 0 : Reportable(other).Count();
|
||||
|
||||
/// The three numbers the entry window shows. `LeftWith` is what may still
|
||||
/// go with this station: what is free of its ten, and no more than the
|
||||
/// contacts there are to report when we are the one reporting.
|
||||
public QtcCounts CountsFor(Callsign other)
|
||||
{
|
||||
int exchanged = ExchangedWith(other);
|
||||
int free = Math.Max(0, WaeQtc.MostPerStation - exchanged);
|
||||
int available = Available(other);
|
||||
int left = Role == QtcDirection.Send ? Math.Min(free, available) : free;
|
||||
return new QtcCounts(Role, exchanged, left, available);
|
||||
}
|
||||
|
||||
/// Series are numbered across the whole contest, not per station: the
|
||||
/// hundredth series a station sends is `100/10` whoever gets it.
|
||||
public int NextSeries()
|
||||
@@ -111,18 +173,7 @@ public sealed class QtcTraffic
|
||||
{
|
||||
return [];
|
||||
}
|
||||
HashSet<string> reported = position.Log.Qsos
|
||||
.Where(q => q.Exchange1.Equals(WaeQtc.Sent, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(q => ReportedKey(q.ReceivedReport, q.ReceivedNumber))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
List<Qso> worth = position.Log.Qsos
|
||||
.Where(q => !WaeQtc.IsQtc(q))
|
||||
.Where(q => q.ContactType.Length == 0)
|
||||
.Where(q => !q.Call.Text.Equals(other.Text, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(q => !reported.Contains(ReportedKey(q.Call.Text, q.SentNumber)))
|
||||
.OrderBy(q => q.TimestampUtc)
|
||||
.Take(room)
|
||||
.ToList();
|
||||
List<Qso> worth = [.. Reportable(other).OrderBy(q => q.TimestampUtc).Take(room)];
|
||||
int series = NextSeries();
|
||||
return [.. worth.Select(q => new WaeQtc(
|
||||
other,
|
||||
@@ -196,6 +247,22 @@ public sealed class QtcTraffic
|
||||
private static bool IsClockTime(string text) =>
|
||||
int.Parse(text[..2]) < 24 && int.Parse(text[2..]) < 60;
|
||||
|
||||
/// The contacts not reported to anybody yet, without the dupes and without
|
||||
/// the station in front of us. The caller puts them in order. What N1MM's
|
||||
/// `QTCRecordsetCount` leaves out is the dupes.
|
||||
private IEnumerable<Qso> Reportable(Callsign other)
|
||||
{
|
||||
HashSet<string> reported = position.Log.Qsos
|
||||
.Where(q => q.Exchange1.Equals(WaeQtc.Sent, StringComparison.OrdinalIgnoreCase))
|
||||
.Select(q => ReportedKey(q.ReceivedReport, q.ReceivedNumber))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
return position.Log.Qsos
|
||||
.Where(q => !WaeQtc.IsQtc(q))
|
||||
.Where(q => q.ContactType.Length == 0)
|
||||
.Where(q => !q.Call.Text.Equals(other.Text, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(q => !reported.Contains(ReportedKey(q.Call.Text, q.SentNumber)));
|
||||
}
|
||||
|
||||
private static string ReportedKey(string call, int number) => $"{call}|{number}";
|
||||
|
||||
private string ContinentOf(Callsign call) =>
|
||||
|
||||
Reference in New Issue
Block a user