ADIF has no record for QTC traffic, which is why N1MM leaves it out and why a WAE log cannot be moved between databases as ADIF. The plain export now writes the contacts alone, as N1MM does: it used to write the QTC rows as contacts, with the time of the traffic in RST_SENT and the reported callsign in RST_RCVD. File > Export > Export ADIF with QTC traffic writes the whole log instead. A QTC row goes out with the station, time, band and mode, and the traffic in application-defined fields — APP_NONEMM_QTC and friends, the prefix the ADIF specification keeps for a program's own data — with the line in the comment as well so the file can be read without this program. The report columns are left off that record, because on a QTC row they hold the time and callsign of the contact being reported. Reading such a file back rebuilds the rows, so a WAE log survives the round trip whole. The item is only in the menu for a contest with traffic. Another program reading that file sees each QTC row as an ordinary contact with the station the traffic was exchanged with and ignores the extra fields. There is no way around that in ADIF, which is why the plain export is still the one to hand over. Contacts now also carry their row kind in APP_NONEMM_EXCHANGE1. Without it a WAE log read back holds contacts with no kind at all, which N1MM's own queries do not find. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
210 lines
7.4 KiB
C#
210 lines
7.4 KiB
C#
using Nonemm.Contests.Rules;
|
|
using Nonemm.Core;
|
|
|
|
namespace Nonemm.Contests.Tests;
|
|
|
|
public class WaeTests
|
|
{
|
|
private static readonly Wae Cw = new(ModeCategory.Cw);
|
|
private static readonly Wae Rtty = new(ModeCategory.Digital);
|
|
|
|
private static ContestLog LogFor(Contest contest, StationInfo me) =>
|
|
new(contest, me, TestLog.CountryFile);
|
|
|
|
private static Qso On(string call, double kilohertz, Mode? mode = null) =>
|
|
TestLog.Contact(call, kilohertz: kilohertz, mode: mode ?? Modes.Cw, exchange: WaeQtc.Contact);
|
|
|
|
[Fact]
|
|
public void EveryContactOnAContestBandIsOnePoint()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Assert.Equal(1, log.Judge(On("JA1XYZ", 14_025)).Points);
|
|
}
|
|
|
|
/// WAE does not run on the WARC bands.
|
|
[Fact]
|
|
public void AContactOnAWarcBandScoresNothing()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Assert.Equal(0, log.Judge(On("JA1XYZ", 18_080)).Points);
|
|
}
|
|
|
|
[Fact]
|
|
public void EuropeDoesNotWorkEuropeOnCw()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Verdict verdict = log.Judge(On("IK2XYZ", 14_025));
|
|
|
|
Assert.Equal(0, verdict.Points);
|
|
Assert.Empty(verdict.NewMultipliers);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnRttyEverybodyWorksEverybody()
|
|
{
|
|
ContestLog log = LogFor(Rtty, TestLog.Germany);
|
|
Verdict verdict = log.Judge(On("IK2XYZ", 14_080, Modes.Rtty));
|
|
|
|
Assert.Equal(1, verdict.Points);
|
|
Assert.Single(verdict.NewMultipliers);
|
|
}
|
|
|
|
/// A multiplier is worth four on 80 metres, three on 40 and two higher up.
|
|
[Theory]
|
|
[InlineData(3_530, 4)]
|
|
[InlineData(7_030, 3)]
|
|
[InlineData(14_030, 2)]
|
|
[InlineData(21_030, 2)]
|
|
[InlineData(28_030, 2)]
|
|
public void AMultiplierIsWorthMoreOnTheLowBands(double kilohertz, int weight)
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Multiplier multiplier = Assert.Single(log.Judge(On("JA1XYZ", kilohertz)).NewMultipliers);
|
|
|
|
Assert.Equal(weight, multiplier.Weight);
|
|
Assert.Equal("JA1", multiplier.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheWeightGoesIntoTheScore()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
log.Add(On("JA1XYZ", 3_530));
|
|
|
|
Assert.Equal(1, log.Tally.Points);
|
|
Assert.Equal(4, log.Tally.TotalMultipliers);
|
|
Assert.Equal(4, log.TotalScore);
|
|
}
|
|
|
|
/// The countries with call areas count each area separately.
|
|
[Theory]
|
|
[InlineData("K1ABC", "K1")]
|
|
[InlineData("W6XYZ", "K6")]
|
|
[InlineData("JA1XYZ", "JA1")]
|
|
[InlineData("PY2ABC", "PY2")]
|
|
public void ACallAreaIsAMultiplierOfItsOwn(string call, string expected)
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Multiplier multiplier = Assert.Single(log.Judge(On(call, 14_025)).NewMultipliers);
|
|
|
|
Assert.Equal(expected, multiplier.Value);
|
|
}
|
|
|
|
/// Asiatic Russia goes by the call area digit whatever the prefix on the
|
|
/// air, so RM0W and UA0AB are the same multiplier.
|
|
[Theory]
|
|
[InlineData("RM0W")]
|
|
[InlineData("UA0AB")]
|
|
public void AsiaticRussiaCountsByCallArea(string call)
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Multiplier multiplier = Assert.Single(log.Judge(On(call, 14_025)).NewMultipliers);
|
|
|
|
Assert.Equal("UA0", multiplier.Value);
|
|
}
|
|
|
|
/// A callsign the country file cannot place still counts, with an empty
|
|
/// value, so the log scores the same here as in N1MM. All the unplaceable
|
|
/// calls worked on a band share the one multiplier between them.
|
|
[Fact]
|
|
public void ACallWithNoCountryCountsOncePerBand()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
Assert.Equal("", Assert.Single(log.Judge(On("D1M", 14_025)).NewMultipliers).Value);
|
|
log.Add(On("D1M", 14_025));
|
|
Assert.Empty(log.Judge(On("D2X", 14_025)).NewMultipliers);
|
|
}
|
|
|
|
/// N1MM's score window gives contacts, traffic sent and traffic received a
|
|
/// row of their own on every band, and the multipliers in a band row carry
|
|
/// that band's weight: four on 80, three on 40, two on 20, 15 and 10.
|
|
[Fact]
|
|
public void TheScoreSplitsByBandAndByKindOfRow()
|
|
{
|
|
ContestLog log = LogFor(Cw, TestLog.Germany);
|
|
log.Add(On("JA1XYZ", 3_525));
|
|
log.Add(On("K1ABC", 14_025));
|
|
log.Add(new WaeQtc(
|
|
Callsign.Parse("JA1XYZ"), IsSent: false, 1, 10, "1435", Callsign.Parse("W1AW"), 42)
|
|
.ApplyTo(On("JA1XYZ", 3_525)));
|
|
|
|
Assert.Equal(4, log.Rows[new ScoreKey("80M", WaeQtc.Contact)].TotalMultipliers);
|
|
Assert.Equal(1, log.Rows[new ScoreKey("80M", WaeQtc.Contact)].Points);
|
|
Assert.Equal(2, log.Rows[new ScoreKey("20M", WaeQtc.Contact)].TotalMultipliers);
|
|
|
|
ScoreTally traffic = log.Rows[new ScoreKey("80M", WaeQtc.Received)];
|
|
Assert.Equal(1, traffic.Qsos);
|
|
Assert.Equal(1, traffic.Points);
|
|
Assert.Equal(0, traffic.TotalMultipliers);
|
|
|
|
Assert.Equal(log.Tally.Points, log.Rows.Values.Sum(r => r.Points));
|
|
Assert.Equal(log.Tally.TotalMultipliers, log.Rows.Values.Sum(r => r.TotalMultipliers));
|
|
}
|
|
|
|
[Fact]
|
|
public void AQtcIsAPointAndNothingElse()
|
|
{
|
|
ContestLog log = LogFor(Rtty, TestLog.Germany);
|
|
Qso qtc = new WaeQtc(
|
|
Callsign.Parse("JA1XYZ"), IsSent: true, 1, 10, "1435", Callsign.Parse("DL9XYZ"), 42)
|
|
.ApplyTo(On("JA1XYZ", 14_080, Modes.Rtty));
|
|
|
|
Verdict verdict = log.Judge(qtc);
|
|
Assert.Equal(1, verdict.Points);
|
|
Assert.Empty(verdict.NewMultipliers);
|
|
}
|
|
|
|
/// The station is worked once and then reported to ten times over; those
|
|
/// rows must not read as dupes of the contact.
|
|
[Fact]
|
|
public void QtcTrafficWithAStationAlreadyWorkedIsNotADupe()
|
|
{
|
|
ContestLog log = LogFor(Rtty, TestLog.Germany);
|
|
log.Add(On("JA1XYZ", 14_080, Modes.Rtty));
|
|
Qso qtc = new WaeQtc(
|
|
Callsign.Parse("JA1XYZ"), IsSent: true, 1, 10, "1435", Callsign.Parse("DL9XYZ"), 42)
|
|
.ApplyTo(On("JA1XYZ", 14_080, Modes.Rtty));
|
|
|
|
Assert.False(log.Judge(qtc).IsDupe);
|
|
log.Add(qtc);
|
|
Assert.Equal(2, log.Tally.Points);
|
|
Assert.Equal(2, log.Tally.TotalMultipliers);
|
|
}
|
|
|
|
/// ADIF has no QTC record, so the export writes contacts alone. N1MM says
|
|
/// the same in its manual: "the ADIF file will not contain QTCs, only
|
|
/// QSOs".
|
|
[Fact]
|
|
public void TheContactsAreTheRowsWithoutTheTraffic()
|
|
{
|
|
ContestLog log = LogFor(Rtty, TestLog.Germany);
|
|
log.Add(On("JA1XYZ", 14_080, Modes.Rtty));
|
|
log.Add(new WaeQtc(
|
|
Callsign.Parse("JA1XYZ"), IsSent: true, 1, 10, "1435", Callsign.Parse("DL9XYZ"), 42)
|
|
.ApplyTo(On("JA1XYZ", 14_080, Modes.Rtty)));
|
|
|
|
Assert.Equal(2, log.Qsos.Count);
|
|
Qso only = Assert.Single(log.Contacts);
|
|
Assert.Equal(WaeQtc.Contact, only.Exchange1);
|
|
}
|
|
|
|
[Fact]
|
|
public void AQtcGoesIntoTheLogTheWayN1mmKeepsIt()
|
|
{
|
|
WaeQtc qtc = new(
|
|
Callsign.Parse("H2X"), IsSent: true, 9, 10, "0107", Callsign.Parse("OH1SIC"), 7);
|
|
Qso row = qtc.ApplyTo(On("H2X", 14_099, Modes.Rtty));
|
|
|
|
Assert.Equal("SQTC", row.Exchange1);
|
|
Assert.Equal("9/10", row.MiscText);
|
|
Assert.Equal("0107", row.SentReport);
|
|
Assert.Equal("OH1SIC", row.ReceivedReport);
|
|
Assert.Equal(7, row.SentNumber);
|
|
Assert.Equal(qtc, WaeQtc.From(row));
|
|
}
|
|
|
|
[Fact]
|
|
public void AnOrdinaryContactIsNotAQtc() =>
|
|
Assert.Null(WaeQtc.From(On("JA1XYZ", 14_025)));
|
|
}
|