@@ 0,0 1,159 @@
+--
+-- Copyright (c) 2024-2025 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to deal
+-- in the Software without restriction, including without limitation the rights
+-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-- copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-- SOFTWARE.
+--
+
+local mt = { }
+
+local function grid(name, year, duphash)
+ local out = { }
+
+ out['name'] = name -- contest name
+ out['year'] = year -- contest year
+ out['dups'] = hlog.set.new() -- set of dup hashes
+
+ -- set up an object with the data
+ local obj = {
+ data = out,
+ duphash = duphash,
+ }
+
+ setmetatable(obj, mt)
+
+ return obj
+end
+
+local function qso_load(self, qso, tx, rx)
+ local map = self.data
+
+ tx = tx ~= nil and tx or qso.tx.grid
+ rx = rx ~= nil and rx or qso.rx.grid
+
+ map['dups']:add(self.duphash(qso, tx, rx))
+end
+
+local function qso_done(self, qso, tx, rx)
+ local map = self.data
+
+ qso.additional['contest-id'] = map['name']
+ qso.additional['contest-year'] = map['year']
+
+ self:qso_load(qso, tx, rx)
+end
+
+-- is the passed in qso a duplicate with a previous qso?
+local function is_dup(self, qso, tx, rx)
+ local map = self.data
+
+ tx = tx ~= nil and tx or qso.tx.grid
+ rx = rx ~= nil and rx or qso.rx.grid
+
+ return map['dups']:has(self.duphash(qso, tx, rx))
+end
+
+local function export_prep(self, id, year, qpoints, qmult, qexchange)
+ local function do_add_entry(tx, rx, args)
+ local qp = args[1]
+ local all = args[2]
+ local ops = args[3]
+ local points = args[4]
+ local mults = args[5]
+ local qso = args[6]
+
+ local tx_exch, tx_exch_ok = qexchange(qso.tx, tx)
+ local rx_exch, rx_exch_ok = qexchange(qso.rx, rx)
+
+ -- accumulate points
+ if not self:is_dup(qso, tx, rx) and tx_exch_ok and rx_exch_ok then
+ points[1] = points[1] + qpoints(qso, tx, rx)
+ end
+
+ -- add the mult
+ local m = qmult(qso, tx, rx)
+ if m ~= nil then
+ mults:add(m)
+ end
+
+ -- add to list of all QSOs
+ table.insert(all, { qso, tx_exch, rx_exch, tx_exch_ok and rx_exch_ok })
+
+ -- stash for future dup detection
+ self:qso_done(qso, tx, rx)
+
+ -- keep track of the operators
+ ops:add(qso.tx.operator_call)
+ end
+
+ local function add_entry(id, year, station, all, ops, points, mults, qso)
+ if qso.additional['contest-id'] ~= id or
+ qso.additional['contest-year'] ~= year then
+ return
+ end
+
+ if qso.tx.band == nil then
+ print(string.format("Error: %s QSO lacks band info", qso.uuid))
+ return
+ end
+
+ assert(qso.tx.station_call ~= nil)
+ station:add(qso.tx.station_call)
+
+ hlog.utils.each_with_each(qso.tx.grid,
+ qso.rx.grid,
+ "+", do_add_entry,
+ { self, all, ops, points, mults, qso })
+ end
+
+ local station = hlog.set.new()
+ local all = {}
+ local ops = hlog.set.new()
+ local points = { 0 }
+ local mults = hlog.set.new()
+
+ for qso in hlog.index.history(true) do
+ add_entry(id, year, station, all, ops, points, mults, qso)
+ end
+
+ if station:count_items() ~= 1 then
+ error(string.format("Error: more than one station callsign used: %s",
+ station:tostring()))
+ end
+
+ station = hlog.utils.join_ipairs(station:as_array(), ",") -- should be only 1 element
+ points = points[1]
+
+ return all, station, ops, points, mults
+end
+
+local function tostring(self)
+ return "Grid{" .. self.data:tostring() .. "}"
+end
+
+mt.__index = {
+ qso_load = qso_load,
+ qso_done = qso_done,
+ is_dup = is_dup,
+ export_prep = export_prep,
+ tostring = tostring,
+}
+
+return {
+ grid = grid,
+}