using Nonemm.Core; namespace Nonemm.Contests; /// How fast contacts are being made, in contacts an hour. The network status /// window shows two of these per station — over the last ten contacts and over /// the last hundred — and the score window shows the same numbers for this /// station. public static class Rates { /// Where N1MM stops dividing by a very short span. Under ten contacts the /// span counts as at least a minute, so three contacts in five seconds /// reads as 180 an hour rather than 2160. private const int ShortRun = 10; private static readonly TimeSpan Floor = TimeSpan.FromMinutes(1); /// The rate over the last `count` contacts before `nowUtc`. /// /// Counting stops at the first gap longer than `minimumOff`, so a rate /// after a night's rest is the rate since the operator came back rather /// than a hundred contacts spread over twelve hours. A log with no contact /// inside that gap is a rate of nothing. public static int PerHour( IEnumerable qsos, int count, DateTime nowUtc, TimeSpan minimumOff) { DateTime oldest = nowUtc; int counted = 0; foreach (Qso qso in qsos.OrderByDescending(q => q.TimestampUtc).Take(count)) { if (oldest - qso.TimestampUtc > minimumOff) { break; } oldest = qso.TimestampUtc; counted++; } if (counted == 0) { return 0; } TimeSpan span = nowUtc - oldest; if (counted < ShortRun && span < Floor) { span = Floor; } return span > TimeSpan.Zero ? (int)Math.Round(counted / span.TotalHours) : 0; } }