From 3a39b40edb1f23f3c99d803b5a108c140e77f4aa Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sat, 24 Oct 2026 05:24:56 +0000 Subject: [PATCH] Fill the zone box from the log and the country file on space Without a call history file, the CQ zone box stayed empty after the callsign. It now takes the zone from the call history file, else from the last logged contact with the call, else from the country file, as N1MM does. A box that already holds text is left alone. Co-Authored-By: Claude Sonnet 5.5 --- src/Nonemm.Session/OperatingPosition.cs | 41 +++++++++++++++---- .../OperatingPositionTests.cs | 26 ++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/Nonemm.Session/OperatingPosition.cs b/src/Nonemm.Session/OperatingPosition.cs index 8239a63..f78d705 100644 --- a/src/Nonemm.Session/OperatingPosition.cs +++ b/src/Nonemm.Session/OperatingPosition.cs @@ -341,20 +341,24 @@ public sealed class OperatingPosition return filled; } - /// Fills what the call history knows into the exchange boxes that are still - /// empty. What the other station actually sends wins, so a box with - /// something in it is left alone. Returns true when anything was filled. + /// Fills what is already known about the station into the exchange boxes + /// that are still empty. What the other station actually sends wins, so a + /// box with something in it is left alone. A zone box takes the call + /// history file first, then the zone logged on the last contact with the + /// call, then the zone the country file gives for the callsign, which is + /// what N1MM does. Returns true when anything was filled. public bool FillFromHistory() { CallHistoryEntry? known = Session.History.Find(Entry.Call); - if (known is null) - { - return false; - } bool filled = false; for (int at = 0; at < Entry.Exchange.Count; at++) { - string value = ExchangeSlots.ValueFrom(known, Entry.Exchange[at]); + ExchangeField field = Entry.Exchange[at]; + string value = known is null ? "" : ExchangeSlots.ValueFrom(known, field); + if (value.Length == 0) + { + value = KnownZone(field); + } if (value.Length == 0 || Entry[at + 1].Trim().Length > 0) { continue; @@ -369,6 +373,27 @@ public sealed class OperatingPosition return filled; } + /// The zone to suggest for a zone box: the one on the newest earlier + /// contact with this call that has one, else the country file's zone. + /// Empty for any other box. + private string KnownZone(ExchangeField field) + { + if (field.Kind is not (ExchangeFieldKind.CqZone or ExchangeFieldKind.ItuZone)) + { + return ""; + } + string logged = WorkedBefore() + .Select(qso => ExchangeSlots.ValueOf(qso, field.Slot)) + .FirstOrDefault(value => value.Length > 0) ?? ""; + if (logged.Length > 0) + { + return logged; + } + CountryLookup? their = Country(); + int zone = their is null ? 0 : field.Kind == ExchangeFieldKind.CqZone ? their.CqZone : their.ItuZone; + return zone > 0 ? zone.ToString() : ""; + } + /// Moves to the next box the operator types in. On top of the report boxes /// the entry itself steps over, a contest can say a box is not for this /// station: CQ WW RTTY asks only the US and Canada for a state. diff --git a/tests/Nonemm.Session.Tests/OperatingPositionTests.cs b/tests/Nonemm.Session.Tests/OperatingPositionTests.cs index 6ffae66..0505866 100644 --- a/tests/Nonemm.Session.Tests/OperatingPositionTests.cs +++ b/tests/Nonemm.Session.Tests/OperatingPositionTests.cs @@ -482,6 +482,32 @@ public class OperatingPositionTests Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone)); } + /// N1MM suggests the country's zone when the call is settled. + [Fact] + public void TheCountryFileZoneFillsTheZoneBox() + { + OperatingPosition session = Session(); + session.Entry.Call = "JA1XYZ"; + + Assert.True(session.FillFromHistory()); + Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone)); + } + + /// A zone copied on an earlier contact beats the country's own. + [Fact] + public void TheZoneLoggedBeforeBeatsTheCountryFileZone() + { + OperatingPosition session = Session(); + Type(session, "DL1ABC", "40"); + session.LogContact(); + session.Wipe(); + session.Entry.Call = "DL1ABC"; + + session.FillFromHistory(); + + Assert.Equal("40", session.Entry.ValueOf(ExchangeSlot.Zone)); + } + /// A file for a section contest often carries the state instead. [Fact] public void TheStateFillsASectionWhenTheFileHasNoSection()