Mark a dupe in the contact type column, as N1MM does
N1MM writes a contact type on every contact it logs: `D` for a station already worked, `N` for one the entry may not work at all, and empty for an ordinary contact. Nothing here wrote the column, so a dupe counted as a contact worth no points instead of dropping out of the score, and the two places that read the column did nothing: the QTC pile offered dupes that N1MM leaves out, and the statistics window could not leave dupes out either. The marking is in one place, `ContestLog.ApplyVerdict`, and every contest is marked the same way. What changes between contests is what counts as a dupe and which stations are workable, and both come from the contest, as they do in N1MM's `Contact.IsDupe`. No contest module in N1MM marks differently. Our own callsign is not workable either, which is what N1MM adds on top of the contest rule. Two details follow N1MM as well: QTC traffic keeps an empty column, because its `IsDupe` only marks a row while the program is logging contacts; and a mark that came with the contact stands, because N1MM marks a contact when it logs or edits one and never clears marks in a pass over the log. Editing a contact clears the column so it is worked out again, which is what N1MM does at the same point: an edit can move a contact to a band where it is no longer a dupe. A dupe is now out of the score altogether — contacts, points and multipliers — which is N1MM's `ScoreClass.AddContact`. The QSO count still counts WAE QTC rows as contacts; that is the other half of the scoring audit's item 14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157i7PiZyqBkquxS9A6FTdQ
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using Nonemm.Contests.Rules;
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Contests.Tests;
|
||||
|
||||
/// The contact type column N1MM writes in `Contact.IsDupe`: `D` for a station
|
||||
/// already worked, `N` for one this entry may not work, and empty for an
|
||||
/// ordinary contact. Every contest is marked the same way; what changes
|
||||
/// between them is what counts as a dupe and which stations are workable.
|
||||
public class ContactTypeTests
|
||||
{
|
||||
private static readonly DateTime Later = new(2026, 5, 30, 13, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static ContestLog CqWw() =>
|
||||
new(new CqWorldWide(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile);
|
||||
|
||||
private static Qso Japan(double kilohertz = 14_025, DateTime? at = null) =>
|
||||
TestLog.Contact("JA1XYZ", kilohertz: kilohertz, zone: 25, at: at);
|
||||
|
||||
[Fact]
|
||||
public void AFirstContactIsNotMarked() =>
|
||||
Assert.Equal("", CqWw().Add(Japan()).ContactType);
|
||||
|
||||
[Fact]
|
||||
public void TheSecondContactWithAStationIsMarkedAsADupe()
|
||||
{
|
||||
ContestLog log = CqWw();
|
||||
log.Add(Japan());
|
||||
|
||||
Assert.Equal("D", log.Add(Japan(at: Later)).ContactType);
|
||||
}
|
||||
|
||||
/// The dupe rule is the contest's, so a station worked again on another
|
||||
/// band is a contact rather than a dupe in a contest counted per band.
|
||||
[Fact]
|
||||
public void TheSameStationOnAnotherBandIsNotADupe()
|
||||
{
|
||||
ContestLog log = CqWw();
|
||||
log.Add(Japan());
|
||||
|
||||
Assert.Equal("", log.Add(Japan(kilohertz: 21_025, at: Later)).ContactType);
|
||||
}
|
||||
|
||||
/// N1MM leaves a dupe out of the score altogether — points, contacts and
|
||||
/// multipliers — rather than counting it as a contact worth nothing.
|
||||
[Fact]
|
||||
public void ADupeIsOutOfTheScore()
|
||||
{
|
||||
ContestLog log = CqWw();
|
||||
log.Add(Japan());
|
||||
log.Add(Japan(at: Later));
|
||||
|
||||
Assert.Equal(1, log.Tally.Qsos);
|
||||
Assert.Equal(3, log.Tally.Points);
|
||||
Assert.Equal(2, log.Tally.TotalMultipliers);
|
||||
}
|
||||
|
||||
/// WAE on CW is Europe against the rest of the world, so a European
|
||||
/// station working Europe may not work it at all. N1MM marks that `N`.
|
||||
[Fact]
|
||||
public void AStationTheEntryMayNotWorkIsMarked()
|
||||
{
|
||||
ContestLog log = new(new Wae(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile);
|
||||
|
||||
Assert.Equal("N", log.Add(TestLog.Contact("OM5M", exchange: WaeQtc.Contact)).ContactType);
|
||||
Assert.Equal("", log.Add(TestLog.Contact("JA1XYZ", exchange: WaeQtc.Contact)).ContactType);
|
||||
}
|
||||
|
||||
/// N1MM adds our own callsign to whatever the contest says is workable.
|
||||
[Fact]
|
||||
public void OurOwnCallsignIsNotWorkable() =>
|
||||
Assert.Equal("N", CqWw().Add(TestLog.Contact("DL1ABC")).ContactType);
|
||||
|
||||
/// QTC traffic is not a contact, and N1MM's QTC window writes an empty
|
||||
/// column on those rows.
|
||||
[Fact]
|
||||
public void TrafficIsNotMarked()
|
||||
{
|
||||
ContestLog log = new(new Wae(ModeCategory.Cw), TestLog.Germany, TestLog.CountryFile);
|
||||
log.Add(TestLog.Contact("JA1XYZ", exchange: WaeQtc.Contact));
|
||||
Qso traffic = new WaeQtc(
|
||||
Callsign.Parse("JA1XYZ"), IsSent: false, 1, 10, "1435", Callsign.Parse("W1AW"), 42)
|
||||
.ApplyTo(TestLog.Contact("JA1XYZ", at: Later));
|
||||
|
||||
Assert.Equal("", log.Add(traffic).ContactType);
|
||||
}
|
||||
|
||||
/// A mark that came with the contact stands, because N1MM never clears one
|
||||
/// in a pass over the log: a row from another station of the entry, or
|
||||
/// from a database N1MM wrote, keeps what it was given.
|
||||
[Fact]
|
||||
public void AMarkThatCameWithTheContactStands() =>
|
||||
Assert.Equal("D", CqWw().Add(Japan() with { ContactType = "D" }).ContactType);
|
||||
}
|
||||
Reference in New Issue
Block a user