The three differences left after replaying the log were ours on purpose. A log has to score the same in both programs, so they now follow N1MM: - ARRL DX and CQ WW RTTY count the 14 Canadian areas N1MM lists as `48SDC14P`, which keeps Newfoundland apart from Labrador. A station sending the postal code is counted as the area the contest names. - A maritime mobile is scored from the call it signs. CQ WW says it counts for the zone alone; N1MM gives it the country, and so do we. - A callsign the country file cannot place counts as a multiplier with an empty value, so every unplaceable call worked on a band shares one multiplier. 29 of the 37 contest instances in the log now agree with N1MM on every contact and on the totals. What is left is the country file of the day and rows whose cached points N1MM never worked out again after a call was corrected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
6.4 KiB
Opening a Nonemm log in N1MM
Checked on 2026-08-27 against N1MM Logger+ 1.0.11031 by writing a log here, copying it to a Windows machine and opening it there. N1MM read the contest, its categories and the contacts.
Three things had to be right, and none of them is obvious from the schema.
The Contest table needs a row for the contest
ContestInstance says which contest a log is; the Contest table says what that
contest is. N1MM reads it in Contest.FromRow when it opens a log and throws
InvalidOperationException: No current row if the row is missing, which reaches
the operator as "A runtime error occurred".
So the definition row is written whenever a contest is opened, not only when it
is created — a log made before this was understood gets its row the next time it
is opened. ContestDefinitions.For builds the row from the contest's own rules:
the display and Cabrillo names, the mode, the dupe type and the multiplier
names.
The overlay category cannot be empty
An empty ContestInstance.OverlayCategory is answered with "Invalid Overlay
Category:" and the log will not open. An entry with no overlay says N/A.
N1MM's list is not the Cabrillo specification's list. N1MM offers:
N/A, ROOKIE, BAND-LIMITED, TB-WIRES, OVER-50, HQ, NOVICE-TECH, EXPERT
so that is what the contest dialog offers.
The sent exchange omits the report
N1MM's contest dialog says "Omit RST: CQWW: 05". SentExchange for CQ WW is
14, not 599 14; for a serial number contest it is 001. The report is fixed
for the whole contest and the entry window fills it in per contact.
Getting the schema in the first place
N1MM ships no template database. ham.s3db is built at run time by applying SQL
migration files that N1MM writes out from string resources inside
N1MMLogger.net.exe. To read them: decompile the executable, parse
N1MMLogger.Net.Resources.resx, and take the untyped <data> entries named
DXLogDDL_0001_initial_schema_and_data through DXLogDDL_0004_updates. The QSO
table is DXLOG and the current PRAGMA user_version is 4.
src/Nonemm.Storage/Schema.sql is that schema, flattened to the state version 4
leaves behind.
One thing to avoid
Do not replace the database file under a running N1MM. It does not notice and throws on the next read.
A contact belongs to a ContestNR, not to the table key
ContestInstance has two numbers: ContestID, the table's primary key, and
ContestNR. DXLOG.ContestNR refers to the second one. N1MM's
ContestInstance.SQLWhereString reads:
" ContestNR = " + this.ContestNR + " "
In a fresh log the two numbers match, so keying on either works. In a log N1MM
has been using for a while they drift apart. In one real log of 56284 contacts,
joining DXLOG.ContestNR to ContestInstance.ContestID disagreed with the
contact's own ContestName 52029 times; joining it to ContestInstance.ContestNR
disagreed 109 times.
So ContestInstance.ContestNumber here is ContestNR, and ContestID is
allocated separately when a contest is created.
N1MM names a contest per mode
CQ WW is CQWWCW, CQWWSSB or CQWWRTTY, never plain CQWW. The full list
lives in the Contest table of any N1MM database. Nonemm writes the same names,
and N1mmContestNames turns one back into a contest and a mode when a log is
opened. The name carries the mode, so it beats whatever ModeCategory says.
Older N1MM versions wrote the CW running of CQ WW as plain CQWW. That name is
still in the registry, so those logs open.
Which column an exchange lands in
N1MM keeps the received exchange of ARRL DX, IARU and WAE in Sect, not in
Exchange1, whatever the entry window calls the box. A contest that reads the
wrong column scores every contact as if the operator had typed nothing, so the
rule reads the column N1MM writes and the entry window points its box at the
same place.
N1MM also writes a single space into a text column it has nothing for, so
Continent comes back as " " rather than empty. Reading it as a value rather
than as empty makes every contact look like another continent. The store trims
what it reads.
Scoring checked against a real log
om5m.s3db in the repo root is a station's own N1MM log: 78 contest instances
from 2019 to 2026, 57,000 contacts, 28 contest names. Replaying it is how the
scoring rules are checked — every contact is scored again from our rules and
compared with the points and multiplier flags N1MM wrote.
29 of the 37 instances whose contest we have rules for agree on every contact and on the totals. What the check found and fixed:
| What | Was |
|---|---|
| ARRL DX and IARU read the wrong column | no multipliers at all, and IARU scored every contact 1 point |
a stored " " was read as a value |
181 contacts in one log scored as another continent |
| the Canadian area lists | ARRL DX and CQ WW RTTY count the 14 areas N1MM calls 48SDC14P, Newfoundland apart from Labrador, and the postal codes map onto them |
| a maritime mobile scored nothing | it is scored from the call it signs, which is the country N1MM logs for it |
| a call the country file cannot place brought no multiplier | it counts, with an empty value, and all such calls share the one multiplier per band |
| a contact in a mode the contest does not run | scored as if it were in the contest; N1MM scores it zero and keeps its multiplier |
What still differs, and why. None of it is a rule:
- The country file of the day. A 2019 log holds calls that today's
wl_cty.datplaces elsewhere —TO7Owas Mayotte then and is France now, China was not split by call area yet — and the multiplier moves to another contact with it. This is nearly all of the difference in the 2019 CQ WW SSB log and all of it in the 2021 ones. - Stale cached points. N1MM caches points in the row and does not always work them out again after a callsign is corrected, so a handful of rows hold a number that its own rules do not produce. Two IARU rows and about six CQ WW rows across 57,000 contacts.
- A one-contact log whose contact also sits in another instance of the same contest.
What N1MM does that the published rules do not say
Three of the fixes above follow N1MM rather than the rule as published, because a log has to score the same in both programs:
- ARRL DX counts
NFandLBas two multipliers. They are one province. - A maritime mobile counts for the country of the call it signs. CQ WW says it counts for the zone alone.
- A callsign that places nowhere counts as a multiplier with an empty value.