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:
2026-09-10 19:30:25 +00:00
co-authored by Claude Opus 5
parent 00bbb0e6b3
commit 3e326053a0
6 changed files with 223 additions and 13 deletions
@@ -72,6 +72,26 @@ public class QtcTrafficTests
Exchange1 = WaeQtc.Contact,
});
/// One line of traffic with a station, as a log row of its own. What is
/// reported does not matter to the counts, only that the line is there.
private static Qso Traffic(
OperatingPosition position,
string station,
bool isSent,
int number,
int minute) =>
position.Session.Add(new WaeQtc(
Callsign.Parse(station), isSent, 1, 1, "1300", Callsign.Parse("W1XX"), number)
.ApplyTo(new Qso
{
Id = Qso.NewId(),
TimestampUtc = new DateTime(2026, 11, 14, 13, minute, 0, DateTimeKind.Utc),
Call = Callsign.Parse(station),
Frequency = Frequency.FromKilohertz(14_080),
Mode = Modes.Rtty,
ContestName = "WAERTTY",
}));
[Fact]
public void OnCwEuropeReceivesAndTheRestOfTheWorldSends()
{
@@ -167,6 +187,100 @@ public class QtcTrafficTests
Assert.Equal(87, traffic.NextSeries());
}
/// Europe on CW and SSB only takes traffic down, so there is no pile of
/// contacts to report and the line says how many of the ten are left.
[Fact]
public void EuropeCountsWhatItHasTakenDown()
{
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Cw);
Contact(position, "K1ABC", receivedNumber: 5, sentNumber: 1, minute: 40);
Traffic(position, "NE1C", isSent: false, number: 7, minute: 41);
QtcCounts counts = traffic.CountsFor(Callsign.Parse("NE1C"));
Assert.Equal(QtcDirection.Receive, counts.Role);
Assert.Equal(1, counts.Exchanged);
Assert.Equal(9, counts.LeftWith);
Assert.Equal(0, counts.Available);
Assert.Equal("QTCs: 1 rcvd/9 left to exch.", counts.Text);
}
/// Outside Europe the traffic goes the other way, so what may still be
/// exchanged is capped by the contacts there are left to report.
[Fact]
public void OutsideEuropeWhatIsLeftIsCappedByWhatThereIsToReport()
{
(OperatingPosition position, QtcTraffic traffic) =
Station(ModeCategory.Cw, UnitedStates);
Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46);
Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47);
Contact(position, "DL9XYZ", receivedNumber: 7, sentNumber: 3, minute: 48);
QtcCounts counts = traffic.CountsFor(Callsign.Parse("DL9XYZ"));
Assert.Equal(QtcDirection.Send, counts.Role);
Assert.Equal(0, counts.Exchanged);
// the station in front of us is not reported to itself
Assert.Equal(2, counts.Available);
Assert.Equal(2, counts.LeftWith);
Assert.Equal("QTCs: 0 sent/2 left to exch/2 total QTCs avail", counts.Text);
}
/// A contact already reported to somebody is out of the pile, and a line
/// already sent to this station is off its ten.
[Fact]
public void ReportingContactsTakesThemOutOfTheCounts()
{
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
Contact(position, "DL1AAA", receivedNumber: 16, sentNumber: 1, minute: 46);
Contact(position, "DL2BBB", receivedNumber: 40, sentNumber: 2, minute: 47);
WaeQtc first = traffic.ToSend(Callsign.Parse("JA1XYZ"))[0];
position.Session.Add(first.ApplyTo(new Qso
{
Id = Qso.NewId(),
TimestampUtc = new DateTime(2026, 11, 14, 13, 50, 0, DateTimeKind.Utc),
Call = Callsign.Parse("JA1XYZ"),
Frequency = Frequency.FromKilohertz(14_080),
Mode = Modes.Rtty,
ContestName = "WAERTTY",
}));
QtcCounts counts = traffic.CountsFor(Callsign.Parse("JA1XYZ"));
Assert.Equal(QtcDirection.Either, counts.Role);
Assert.Equal(1, counts.Exchanged);
Assert.Equal(1, counts.Available);
Assert.Equal("QTCs: 1 exch/9 left to exch/1 total QTCs avail", counts.Text);
}
/// On RTTY traffic goes both ways, so what is left with a station is what
/// is free of its ten whether or not there is anything to report.
[Fact]
public void OnRttyWhatIsLeftIsNotCappedByWhatThereIsToReport()
{
(_, QtcTraffic traffic) = Station(ModeCategory.Digital);
QtcCounts counts = traffic.CountsFor(Callsign.Parse("JA1XYZ"));
Assert.Equal(0, counts.Available);
Assert.Equal(10, counts.LeftWith);
}
[Fact]
public void AStationThatHasHadItsTenIsFull()
{
(OperatingPosition position, QtcTraffic traffic) = Station(ModeCategory.Digital);
for (int at = 1; at <= 10; at++)
{
Traffic(position, "JA1XYZ", isSent: true, number: at, minute: at);
}
QtcCounts counts = traffic.CountsFor(Callsign.Parse("JA1XYZ"));
Assert.True(counts.IsFull);
Assert.Equal(0, counts.LeftWith);
}
[Theory]
[InlineData("1346", "DL2UH", "40")]
[InlineData("0001", "2E0GYI", "1")]