83 lines
3.8 KiB
TypeScript
83 lines
3.8 KiB
TypeScript
// Shared helpers used by the cluster table and the band map — keeps the
|
|
// mode-inference logic and the status-cache key in one place so both
|
|
// surfaces always read the same data.
|
|
|
|
export function cleanSpotter(s: string): string {
|
|
if (!s) return '';
|
|
const i = s.indexOf('-');
|
|
return i > 0 ? s.slice(0, i) : s;
|
|
}
|
|
|
|
// inferSpotMode picks an ADIF mode for a cluster spot. Comment text is
|
|
// the strongest hint (skimmers say "FT8", "CW 24 WPM", "RTTY"); when it
|
|
// fails we fall back to the IARU R1 band-plan segment for the frequency.
|
|
// Returns '' when nothing matches — caller should leave the rig mode
|
|
// alone instead of guessing wrong.
|
|
export function inferSpotMode(comment: string, freqHz: number): string {
|
|
const c = (comment || '').toUpperCase();
|
|
if (/\bFT8\b/.test(c)) return 'FT8';
|
|
if (/\bFT4\b/.test(c)) return 'FT4';
|
|
if (/\bJS8\b/.test(c)) return 'JS8';
|
|
if (/\bQ65\b/.test(c)) return 'Q65';
|
|
if (/\bMSK144\b/.test(c)) return 'MSK144';
|
|
if (/\bJT65\b/.test(c)) return 'JT65';
|
|
if (/\bJT9\b/.test(c)) return 'JT9';
|
|
if (/\bRTTY\b/.test(c)) return 'RTTY';
|
|
if (/\bPSK(63|125|250|500)\b/.test(c)) return RegExp.$1 ? `PSK${RegExp.$1}` : 'PSK31';
|
|
if (/\bPSK31?\b/.test(c)) return 'PSK31';
|
|
if (/\bOLIVIA\b/.test(c)) return 'OLIVIA';
|
|
if (/\bMFSK\b/.test(c)) return 'MFSK16';
|
|
if (/\bCW\b/.test(c) || /\bWPM\b/.test(c)) return 'CW';
|
|
if (/\bFM\b/.test(c)) return 'FM';
|
|
if (/\bAM\b/.test(c)) return 'AM';
|
|
if (/\b(SSB|USB|LSB)\b/.test(c)) return 'SSB';
|
|
|
|
const mhz = freqHz / 1_000_000;
|
|
type Seg = [number, number, string];
|
|
const segs: Seg[] = [
|
|
[1.8, 1.838, 'CW'], [1.838, 1.84, 'FT8'], [1.84, 2.0, 'SSB'],
|
|
[3.5, 3.58, 'CW'], [3.573, 3.576, 'FT8'], [3.58, 3.6, 'DATA'], [3.6, 4.0, 'SSB'],
|
|
[5.3, 5.5, 'SSB'],
|
|
[7.0, 7.04, 'CW'], [7.074, 7.077, 'FT8'], [7.0475, 7.0485, 'FT4'],
|
|
[7.04, 7.1, 'DATA'], [7.1, 7.3, 'SSB'],
|
|
[10.1, 10.13, 'CW'], [10.13, 10.15, 'DATA'],
|
|
[14.0, 14.07, 'CW'], [14.074, 14.077, 'FT8'], [14.08, 14.0815, 'FT4'],
|
|
[14.07, 14.1, 'DATA'], [14.1, 14.35, 'SSB'],
|
|
[18.068, 18.095, 'CW'], [18.1, 18.103, 'FT8'], [18.095, 18.11, 'DATA'], [18.11, 18.168, 'SSB'],
|
|
[21.0, 21.07, 'CW'], [21.074, 21.077, 'FT8'], [21.14, 21.143, 'FT4'],
|
|
[21.07, 21.15, 'DATA'], [21.15, 21.45, 'SSB'],
|
|
[24.89, 24.915, 'CW'], [24.915, 24.917, 'FT8'], [24.915, 24.94, 'DATA'], [24.94, 24.99, 'SSB'],
|
|
[28.0, 28.07, 'CW'], [28.074, 28.077, 'FT8'], [28.18, 28.183, 'FT4'],
|
|
[28.07, 28.3, 'DATA'], [28.3, 29.7, 'SSB'],
|
|
[50.0, 50.1, 'CW'], [50.313, 50.316, 'FT8'], [50.318, 50.321, 'FT4'],
|
|
[50.1, 50.5, 'SSB'],
|
|
[144.0, 144.15, 'CW'], [144.174, 144.177, 'FT8'], [144.15, 144.5, 'SSB'],
|
|
];
|
|
for (const [lo, hi, m] of segs) {
|
|
if (mhz >= lo && mhz < hi) return m;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// spotModeCategory buckets the fine-grained mode from inferSpotMode into
|
|
// the three families the cluster filter exposes: 'SSB' (phone: SSB/FM/AM),
|
|
// 'CW', and 'DATA' (every digital mode). Returns '' when the mode is
|
|
// unknown so callers can decide how to treat un-categorisable spots.
|
|
export function spotModeCategory(mode: string): 'SSB' | 'CW' | 'DATA' | '' {
|
|
const m = (mode || '').toUpperCase();
|
|
if (m === '') return '';
|
|
if (m === 'CW') return 'CW';
|
|
if (m === 'SSB' || m === 'USB' || m === 'LSB' || m === 'FM' || m === 'AM') return 'SSB';
|
|
// Everything else inferSpotMode can return (FT8/FT4/JS8/RTTY/PSK*/…/DATA)
|
|
// is a digital mode.
|
|
return 'DATA';
|
|
}
|
|
|
|
// spotStatusKey is the cache key for ClusterSpotStatuses results. Must be
|
|
// computed identically in the fetcher and every reader — including the
|
|
// band map and the spot table — so a CW spot's status doesn't get looked
|
|
// up under an empty-mode key (which always misses → false "new-slot").
|
|
export function spotStatusKey(call: string, band: string, comment: string, freqHz: number): string {
|
|
return `${call}|${band}|${inferSpotMode(comment, freqHz)}`;
|
|
}
|