@ham2k/lib-operation-data 0.8.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,89 @@
1
1
  # lib-operation-data
2
2
 
3
3
  Basic data related to Amateur Radio operation, such as bands, modes, etc
4
+
5
+ ## Building
6
+
7
+ This module is written in TypeScript and must be compiled before use.
8
+
9
+ ### Development
10
+
11
+ To build the module during development:
12
+
13
+ ```bash
14
+ npm run build
15
+ ```
16
+
17
+ This will compile TypeScript source files from `src/` to JavaScript in `dist/`, generating:
18
+ - JavaScript files (`.js`)
19
+ - TypeScript declaration files (`.d.ts`)
20
+ - Source maps (`.js.map`, `.d.ts.map`)
21
+
22
+ ### Testing
23
+
24
+ Run the test suite:
25
+
26
+ ```bash
27
+ npm test
28
+ ```
29
+
30
+ ## Distribution
31
+
32
+ ### Publishing to npm
33
+
34
+ Before publishing, ensure the module is built:
35
+
36
+ ```bash
37
+ npm run build
38
+ ```
39
+
40
+ The `prepublishOnly` script will automatically run the build before publishing, so you can also just:
41
+
42
+ ```bash
43
+ npm publish
44
+ ```
45
+
46
+ ### Package Structure
47
+
48
+ - **Source files**: Located in `src/`
49
+ - **Compiled output**: Generated in `dist/` (excluded from git)
50
+ - **Type definitions**: Automatically generated in `dist/` alongside compiled JavaScript
51
+
52
+ ### Module Exports
53
+
54
+ The package exports are configured in `package.json`:
55
+ - **Main entry**: `dist/index.js`
56
+ - **Type definitions**: `dist/index.d.ts`
57
+ - **ESM support**: Configured for ES2020 modules
58
+
59
+ ## Usage
60
+
61
+ ### Installation
62
+
63
+ ```bash
64
+ npm install @ham2k/lib-operation-data
65
+ ```
66
+
67
+ ### Import
68
+
69
+ ```javascript
70
+ import { BANDS, bandForFrequency, modeForFrequency } from '@ham2k/lib-operation-data'
71
+ ```
72
+
73
+ ### TypeScript
74
+
75
+ Type definitions are included, so TypeScript projects will automatically get type checking and autocomplete:
76
+
77
+ ```typescript
78
+ import { BANDS, Band, bandForFrequency } from '@ham2k/lib-operation-data'
79
+
80
+ const band: Band = bandForFrequency(14255) // '20m'
81
+ ```
82
+
83
+ ## Development Setup
84
+
85
+ 1. Clone the repository
86
+ 2. Install dependencies: `npm install`
87
+ 3. Make changes in `src/`
88
+ 4. Build: `npm run build`
89
+ 5. Test: `npm test`
package/jest.config.js ADDED
@@ -0,0 +1,21 @@
1
+ export default {
2
+ preset: 'ts-jest/presets/default-esm',
3
+ extensionsToTreatAsEsm: ['.ts'],
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.ts$': ['ts-jest', {
7
+ useESM: true,
8
+ tsconfig: {
9
+ module: 'ES2020',
10
+ moduleResolution: 'node'
11
+ }
12
+ }]
13
+ },
14
+ moduleNameMapper: {
15
+ '^(\\.{1,2}/.*)\\.js$': '$1',
16
+ '^(\\.{1,2}/.*)\\.json$': '$1'
17
+ },
18
+ testMatch: ['**/*.spec.ts', '**/*.test.ts'],
19
+ transformIgnorePatterns: []
20
+ }
21
+
package/package.json CHANGED
@@ -1,19 +1,31 @@
1
1
  {
2
2
  "name": "@ham2k/lib-operation-data",
3
- "version": "0.8.4",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
- "main": "./src/index.js",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
6
7
  "exports": {
7
- ".": "./src/index.js"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
8
12
  },
9
13
  "devDependencies": {
10
- "jest": "~29",
11
- "standard": "^17.0.0"
14
+ "@types/jest": "^30.0.0",
15
+ "@types/node": "^24.10.1",
16
+ "jest": "~30",
17
+ "standard": "^17.1.2",
18
+ "ts-jest": "^29.1.0",
19
+ "typescript": "^5.0.0"
12
20
  },
13
21
  "scripts": {
14
- "test": "node --experimental-vm-modules ./node_modules/.bin/jest"
22
+ "build": "tsc",
23
+ "test": "node --experimental-vm-modules ./node_modules/.bin/jest",
24
+ "prepublishOnly": "npm run build"
15
25
  },
16
26
  "standard": {
17
- "env": [ "jest" ]
27
+ "env": [
28
+ "jest"
29
+ ]
18
30
  }
19
31
  }
@@ -10,7 +10,7 @@
10
10
  "segments": [
11
11
  { "name": "CW", "mhz": [1800, 1843], "mode": "CW" },
12
12
  { "name": "Data", "mhz": [1800, 1810], "mode": "RTTY" },
13
- { "name": "FT8", "mhz": [1840, 1843], "mode": "FT8" },
13
+ { "name": "FT8", "mhz": [1840, 1843], "mode": "FT8", "priority": 100 },
14
14
  { "name": "Phone", "mhz": [1843, 2000], "mode": "SSB", "submode": "LSB" },
15
15
  { "name": "Australia", "mhz": [1800, 1875], "mode": null, "countries": ["au"] }
16
16
  ]
@@ -20,9 +20,9 @@
20
20
  "segments": [
21
21
  { "name": "CW", "mhz": [3500, 3600], "mode": "CW" },
22
22
  { "name": "Phone", "mhz": [3535, 3800], "mode": "SSB", "submode": "LSB", "regions": [ 3 ] },
23
- { "name": "FT8", "mhz": [3568, 3571], "mode": "FT8", "regions": [ 3 ] },
23
+ { "name": "FT8", "mhz": [3568, 3571], "mode": "FT8", "regions": [ 3 ], "priority": 100 },
24
24
  { "name": "Data", "mhz": [3570, 3600], "mode": "RTTY" },
25
- { "name": "FT8", "mhz": [3573, 3576], "mode": "FT8" },
25
+ { "name": "FT8", "mhz": [3573, 3576], "mode": "FT8", "priority": 100 },
26
26
  { "name": "Phone", "mhz": [3600, 4000], "mode": "SSB", "submode": "LSB" },
27
27
  { "name": "Australia", "mhz": [3500, 3800], "mode": null, "countries": ["au"] }
28
28
  ]
@@ -32,7 +32,7 @@
32
32
  "segments": [
33
33
  { "name": "60m Channel 1", "mhz": [5330.5, 5333.5] },
34
34
  { "name": "60m Channel 2", "mhz": [5346.5, 5349.5] },
35
- { "name": "60m Channel 3", "mhz": [5357, 5360], "mode": "FT8" },
35
+ { "name": "60m Channel 3", "mhz": [5357, 5360], "mode": "FT8", "priority": 100 },
36
36
  { "name": "60m Channel 4", "mhz": [5371.5, 5373.5] },
37
37
  { "name": "60m Channel 5", "mhz": [5403.5, 5406.5] }
38
38
  ]
@@ -42,9 +42,9 @@
42
42
  "segments": [
43
43
  { "name": "CW", "mhz": [7000, 7060], "mode": "CW" },
44
44
  { "name": "CW", "mhz": [7000, 7125], "mode": "CW", "countries": [ "us" ] },
45
- { "name": "FT4", "mhz": [7047.5, 7050.5], "mode": "FT4" },
45
+ { "name": "FT4", "mhz": [7047.5, 7050.5], "mode": "FT4", "priority": 100 },
46
46
  { "name": "Phone", "mhz": [7060, 7300], "mode": "SSB", "submode": "LSB" },
47
- { "name": "FT8", "mhz": [7074, 7077], "mode": "FT8" },
47
+ { "name": "FT8", "mhz": [7074, 7077], "mode": "FT8", "priority": 100 },
48
48
  { "name": "Data", "mhz": [7080, 7125], "mode": "RTTY", "countries": [ "us" ] },
49
49
  { "name": "Phone", "mhz": [7125, 7300], "mode": "SSB", "submode": "LSB", "countries": [ "us" ] },
50
50
  { "name": "Australia", "mhz": [7000, 7300], "mode": null, "countries": ["au"] }
@@ -55,20 +55,20 @@
55
55
  "segments": [
56
56
  { "name": "CW", "mhz": [10100, 10150], "mode": "CW" },
57
57
  { "name": "Data", "mhz": [10130, 10140], "mode": "RTTY" },
58
- { "name": "FT8", "mhz": [10136, 10139], "mode": "FT8" },
59
- { "name": "FT4", "mhz": [10140, 10143], "mode": "FT4" },
58
+ { "name": "FT8", "mhz": [10136, 10139], "mode": "FT8", "priority": 100 },
59
+ { "name": "FT4", "mhz": [10140, 10143], "mode": "FT4", "priority": 100 },
60
60
  { "name": "Countries with no plan on 30m", "mhz": [10100, 10150], "mode": null, "countries": ["au", "za"] }
61
61
  ]
62
62
  },
63
63
  "20m": {
64
64
  "mhz": [14000, 14350],
65
65
  "segments": [
66
- { "name": "CW", "mhz": [14000, 14101], "mode": "CW", "regions": [ 2 ] },
67
66
  { "name": "CW", "mhz": [14000, 14150], "mode": "CW" },
68
67
  { "name": "CW", "mhz": [14000, 14070], "mode": "CW", "regions": [ 1 ] },
68
+ { "name": "CW", "mhz": [14000, 14101], "mode": "CW", "regions": [ 2 ] },
69
69
  { "name": "Data", "mhz": [14070, 14095], "mode": "RTTY" },
70
- { "name": "FT8", "mhz": [14074, 14077], "mode": "FT8" },
71
- { "name": "FT4", "mhz": [14080, 14083], "mode": "FT4" },
70
+ { "name": "FT8", "mhz": [14074, 14077], "mode": "FT8", "priority": 100 },
71
+ { "name": "FT4", "mhz": [14080, 14083], "mode": "FT4", "priority": 100 },
72
72
  { "name": "Phone", "mhz": [14112, 14350], "mode": "SSB", "submode": "USB", "regions": [ 1, 2 ] },
73
73
  { "name": "Phone", "mhz": [14150, 14350], "mode": "SSB", "submode": "USB", "countries": [ "us" ] },
74
74
  { "name": "Australia", "mhz": [14000, 14350], "mode": null, "countries": ["au"] }
@@ -79,8 +79,8 @@
79
79
  "segments": [
80
80
  { "name": "CW", "mhz": [18068, 18110], "mode": "CW" },
81
81
  { "name": "Data", "mhz": [18100, 18105], "mode": "RTTY" },
82
- { "name": "FT8", "mhz": [18100, 18103], "mode": "FT8" },
83
- { "name": "FT4", "mhz": [18104, 18107], "mode": "FT4" },
82
+ { "name": "FT8", "mhz": [18100, 18103], "mode": "FT8", "priority": 100 },
83
+ { "name": "FT4", "mhz": [18104, 18107], "mode": "FT4", "priority": 100 },
84
84
  { "name": "Phone", "mhz": [18110, 18168], "mode": "SSB", "submode": "USB" },
85
85
  { "name": "Australia", "mhz": [18068, 18168], "mode": null, "countries": ["au"] }
86
86
  ]
@@ -90,8 +90,8 @@
90
90
  "segments": [
91
91
  { "name": "CW", "mhz": [21000, 21150], "mode": "CW" },
92
92
  { "name": "Data", "mhz": [21070, 21095], "mode": "RTTY" },
93
- { "name": "FT8", "mhz": [21074, 21077], "mode": "FT8" },
94
- { "name": "FT4", "mhz": [21140, 21143], "mode": "FT4" },
93
+ { "name": "FT8", "mhz": [21074, 21077], "mode": "FT8", "priority": 100 },
94
+ { "name": "FT4", "mhz": [21140, 21143], "mode": "FT4", "priority": 100 },
95
95
  { "name": "Phone", "mhz": [21150, 21450], "mode": "SSB", "submode": "USB" },
96
96
  { "name": "Australia", "mhz": [21000, 21450], "mode": null, "countries": ["au"] }
97
97
  ]
@@ -100,8 +100,8 @@
100
100
  "mhz": [24890, 24990],
101
101
  "segments": [
102
102
  { "name": "CW", "mhz": [24890, 24930], "mode": "CW" },
103
- { "name": "FT8", "mhz": [24915, 24918], "mode": "FT8" },
104
- { "name": "FT4", "mhz": [24919, 23922], "mode": "FT4" },
103
+ { "name": "FT8", "mhz": [24915, 24918], "mode": "FT8", "priority": 100 },
104
+ { "name": "FT4", "mhz": [24919, 23922], "mode": "FT4", "priority": 100 },
105
105
  { "name": "Data", "mhz": [24920, 24925], "mode": "RTTY" },
106
106
  { "name": "Phone", "mhz": [24930, 24990], "mode": "SSB", "submode": "USB" },
107
107
  { "name": "Australia", "mhz": [24890, 24990], "mode": null, "countries": ["au"] }
@@ -112,8 +112,8 @@
112
112
  "segments": [
113
113
  { "name": "CW", "mhz": [28000, 28190], "mode": "CW" },
114
114
  { "name": "Data", "mhz": [28070, 28150], "mode": "RTTY" },
115
- { "name": "FT8", "mhz": [28074, 28077], "mode": "FT8" },
116
- { "name": "FT4", "mhz": [28180, 28183], "mode": "FT4" },
115
+ { "name": "FT8", "mhz": [28074, 28077], "mode": "FT8", "priority": 100 },
116
+ { "name": "FT4", "mhz": [28180, 28183], "mode": "FT4", "priority": 100 },
117
117
  { "name": "Beacons", "mhz": [28190, 28300] },
118
118
  { "name": "Phone SSB", "mhz": [28300, 29000], "mode": "SSB", "submode": "USB" },
119
119
  { "name": "AM", "mhz": [29000, 29200], "mode": "AM" },
@@ -132,9 +132,9 @@
132
132
  { "name": "Phone", "mhz": [51000, 51110], "mode": "SSB", "submode": "USB" },
133
133
  { "name": "FM", "mhz": [51110, 52100], "mode": "FM" },
134
134
  { "name": "FM", "mhz": [51110, 52100], "mode": "FM" },
135
- { "name": "FT8", "mhz": [50313, 50316], "mode": "FT8" },
136
- { "name": "FT4", "mhz": [50318, 50321], "mode": "FT4" },
137
- { "name": "FT8", "mhz": [50323, 50326], "mode": "FT8" },
135
+ { "name": "FT8", "mhz": [50313, 50316], "mode": "FT8", "priority": 100 },
136
+ { "name": "FT4", "mhz": [50318, 50321], "mode": "FT4", "priority": 100 },
137
+ { "name": "FT8", "mhz": [50323, 50326], "mode": "FT8", "priority": 100 },
138
138
  { "name": "Australia", "mhz": [50000, 54000], "mode": null, "countries": ["au"] }
139
139
  ]
140
140
  },
@@ -143,8 +143,8 @@
143
143
  "segments": [
144
144
  { "name": "CW", "mhz": [144000, 144100], "mode": "CW" },
145
145
  { "name": "Data", "mhz": [144100, 144200], "mode": "RTTY" },
146
- { "name": "FT8", "mhz": [144174, 144177], "mode": "FT8" },
147
- { "name": "FT8", "mhz": [144170, 144173], "mode": "FT4" },
146
+ { "name": "FT8", "mhz": [144174, 144177], "mode": "FT8", "priority": 100 },
147
+ { "name": "FT4", "mhz": [144170, 144173], "mode": "FT4", "priority": 100 },
148
148
  { "name": "Phone", "mhz": [144200, 144275], "mode": "SSB", "submode": "USB" },
149
149
  { "name": "FM", "mhz": [144300, 148000], "mode": "FM" }
150
150
  ]
@@ -1,2 +1,3 @@
1
1
  export * from './lib/bands'
2
2
  export * from './lib/modes'
3
+
@@ -32,22 +32,24 @@ export const BANDS = [
32
32
  '1mm',
33
33
  'submm',
34
34
  'other'
35
- ]
35
+ ] as const
36
36
 
37
- export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m']
38
- export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m']
39
- export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm']
40
- export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm']
41
- export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm']
37
+ export type Band = typeof BANDS[number]
42
38
 
43
- export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm']
44
- export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m']
39
+ export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'] as const
40
+ export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m'] as const
41
+ export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm'] as const
42
+ export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm'] as const
43
+ export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm'] as const
45
44
 
46
- export function bandForFrequency (freq) {
45
+ export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm'] as const
46
+ export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m'] as const
47
+
48
+ export function bandForFrequency(freq: number): Band {
47
49
  return bandForExactFrequencyInMHz(freq) ?? bandForExactFrequencyInMHz(freq * 1000) ?? 'other'
48
50
  }
49
51
 
50
- function bandForExactFrequencyInMHz (freq) {
52
+ function bandForExactFrequencyInMHz(freq: number): Band | undefined {
51
53
  if (freq >= 130 && freq <= 140) return '2190m'
52
54
  else if (freq >= 450 && freq <= 499) return '630m'
53
55
  else if (freq >= 500 && freq <= 505) return '560m'
@@ -83,7 +85,7 @@ function bandForExactFrequencyInMHz (freq) {
83
85
  else return undefined
84
86
  }
85
87
 
86
- export function frequencyForBand(band, mode) {
88
+ export function frequencyForBand(band: Band, mode?: string): number | undefined {
87
89
  if (band === '2190m') return 135.7
88
90
  else if (band === '630m') return 472
89
91
  else if (band === '560m') return 501
@@ -169,3 +171,4 @@ export function frequencyForBand(band, mode) {
169
171
  else if (band === 'submm') return 300000000
170
172
  else return undefined
171
173
  }
174
+
@@ -12,6 +12,11 @@ describe('modeForFrequency', () => {
12
12
  expect(modeForFrequency(14140, { ituRegion: 2 })).toEqual('SSB')
13
13
  })
14
14
 
15
+ it('should handle different priorities', () => {
16
+ // ituRegion 2 should make CW be the match for 14074, but priority 100 for FT8 should make FT8 the match
17
+ expect(modeForFrequency(14074, { ituRegion: 2 })).toEqual('FT8')
18
+ })
19
+
15
20
  // The current bandplan data does not have any entity-specific segments
16
21
  // TODO: mock data for this test
17
22
  // it('should handle different bandplans for different entities', () => {
@@ -26,3 +31,4 @@ describe('modeForFrequency', () => {
26
31
  })
27
32
 
28
33
  })
34
+
@@ -1,10 +1,13 @@
1
1
  import BandPlans from '../data/bandPlans.json'
2
+ import type { BandPlansData, SegmentWithMetadata, ModeForFrequencyOptions } from '../types'
2
3
 
3
4
  export const SUPER_MODES = [
4
5
  'CW',
5
6
  'PHONE',
6
7
  'DATA'
7
- ]
8
+ ] as const
9
+
10
+ export type SuperMode = typeof SUPER_MODES[number]
8
11
 
9
12
  export const MAIN_MODES = [
10
13
  'CW',
@@ -14,7 +17,9 @@ export const MAIN_MODES = [
14
17
  'FT8',
15
18
  'FT4',
16
19
  'RTTY'
17
- ]
20
+ ] as const
21
+
22
+ export type MainMode = typeof MAIN_MODES[number]
18
23
 
19
24
  export const POPULAR_MODES = [
20
25
  'CW',
@@ -42,7 +47,9 @@ export const POPULAR_MODES = [
42
47
  'PSK',
43
48
  'SSTV',
44
49
  'WSPR',
45
- ]
50
+ ] as const
51
+
52
+ export type PopularMode = typeof POPULAR_MODES[number]
46
53
 
47
54
  export const ADIF_MODES = [
48
55
  'AM',
@@ -91,9 +98,11 @@ export const ADIF_MODES = [
91
98
  'VOI',
92
99
  'WINMOR',
93
100
  'WSPR'
94
- ]
101
+ ] as const
95
102
 
96
- export const ADIF_SUBMODES = {
103
+ export type AdifMode = typeof ADIF_MODES[number]
104
+
105
+ export const ADIF_SUBMODES: Record<string, readonly string[]> = {
97
106
  'CHIP': ['CHIP64', 'CHIP128'],
98
107
  'DIGITALVOICE': ['C4FM', 'DMR', 'DSTAR', 'FREEDV', 'M17'],
99
108
  'DOMINO': ['DOM-M', 'DOM4', 'DOM5', 'DOM8', 'DOM11', 'DOM16', 'DOM22', 'DOM44', 'DOM88', 'DOMINOEX', 'DOMINOF'],
@@ -116,18 +125,18 @@ export const ADIF_SUBMODES = {
116
125
  'THOR': ['THOR-M', 'THOR4', 'THOR5', 'THOR8', 'THOR11', 'THOR16', 'THOR22', 'THOR25X4', 'THOR50X1', 'THOR50X2', 'THOR100'],
117
126
  'THRB': ['THRBX', 'THRBX1', 'THRBX2', 'THRBX4', 'THROB1', 'THROB2', 'THROB4'],
118
127
  'TOR': ['AMTORFEC', 'GTOR', 'NAVTEX', 'SITORB']
119
- }
128
+ } as const
120
129
 
121
- export const ADIF_MODE_FOR_SUBMODE = {}
130
+ export const ADIF_MODE_FOR_SUBMODE: Record<string, string> = {}
122
131
  Object.keys(ADIF_SUBMODES).forEach(mode => {
123
132
  ADIF_SUBMODES[mode].forEach(submode => {
124
133
  ADIF_MODE_FOR_SUBMODE[submode] = mode
125
134
  })
126
135
  })
127
136
 
128
- export const ADIF_MODES_AND_SUBMODES = [... new Set(ADIF_MODES.concat(Object.values(ADIF_SUBMODES)).flat())].sort()
137
+ export const ADIF_MODES_AND_SUBMODES: readonly string[] = [...new Set([...ADIF_MODES, ...Object.values(ADIF_SUBMODES).flat()])].sort()
129
138
 
130
- export function adifModeAndSubmodeForMode(mode) {
139
+ export function adifModeAndSubmodeForMode(mode: string): string[] {
131
140
  if (ADIF_MODE_FOR_SUBMODE[mode]) {
132
141
  return [ADIF_MODE_FOR_SUBMODE[mode], mode]
133
142
  } else {
@@ -135,7 +144,7 @@ export function adifModeAndSubmodeForMode(mode) {
135
144
  }
136
145
  }
137
146
 
138
- export function superModeForMode(mode) {
147
+ export function superModeForMode(mode: string): SuperMode {
139
148
  if (ADIF_MODE_FOR_SUBMODE[mode]) mode = ADIF_MODE_FOR_SUBMODE[mode]
140
149
 
141
150
  if (mode === 'CW') {
@@ -147,41 +156,56 @@ export function superModeForMode(mode) {
147
156
  }
148
157
  }
149
158
 
150
- const AllSegments = Object.keys(BandPlans.bands).reduce((segments, band) => {
159
+ const bandPlansData = BandPlans as unknown as BandPlansData
160
+
161
+ const AllSegments: SegmentWithMetadata[] = Object.keys(bandPlansData.bands).reduce((segments, band) => {
151
162
  return segments.concat(
152
- BandPlans.bands[band].segments.map(segment => (
153
- {
154
- ...segment,
163
+ bandPlansData.bands[band].segments.map(segment => {
164
+ const mhz = Array.isArray(segment.mhz) && segment.mhz.length >= 2
165
+ ? [segment.mhz[0], segment.mhz[1]] as [number, number]
166
+ : [0, 0] as [number, number]
167
+ return {
168
+ name: segment.name,
169
+ mode: segment.mode,
170
+ submode: segment.submode,
171
+ priority: segment.priority,
172
+ mhz,
155
173
  band,
156
- width: segment.mhz[1] - segment.mhz[0],
157
- regions: (segment.regions || []).reduce((regions, region) => { regions[region] = true; return regions }, {}),
158
- entities: (segment.entities || []).reduce((entities, entity) => { entities[entity] = true; return entities }, {}),
159
- countries: (segment.countries || []).reduce((countries, country) => { countries[country] = true; return countries }, {}),
160
- }
161
- ))
174
+ width: mhz[1] - mhz[0],
175
+ regions: (segment.regions || []).reduce((regions: Record<string, boolean>, region) => { regions[region.toString()] = true; return regions }, {}),
176
+ entities: (segment.entities || []).reduce((entities: Record<string, boolean>, entity) => { entities[entity] = true; return entities }, {}),
177
+ countries: (segment.countries || []).reduce((countries: Record<string, boolean>, country) => { countries[country] = true; return countries }, {}),
178
+ } as SegmentWithMetadata
179
+ })
162
180
  )
163
- }, [])
181
+ }, [] as SegmentWithMetadata[])
182
+
183
+ export function modeForFrequency(frequency: number, options: ModeForFrequencyOptions = {}): string | null | undefined {
184
+ const { ituRegion, countryCode, entityPrefix } = options
164
185
 
165
- export function modeForFrequency(frequency, { ituRegion, countryCode, entityPrefix } = {}) {
166
186
  const segments = AllSegments.filter(segment => (
167
187
  (segment.mhz[0] <= frequency && segment.mhz[1] >= frequency) &&
168
188
  (!entityPrefix || Object.keys(segment.entities).length === 0 || segment.entities[entityPrefix]) &&
169
189
  (!countryCode || Object.keys(segment.countries).length === 0 || segment.countries[countryCode]) &&
170
- (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion])
190
+ (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion.toString()])
171
191
  ))
172
192
 
173
193
  const sortedSegments = segments.sort((a, b) => {
194
+ if ((a.priority ?? 0) < (b.priority ?? 0)) return 1
195
+ if ((a.priority ?? 0) > (b.priority ?? 0)) return -1
196
+
174
197
  if (entityPrefix && !a.entities[entityPrefix] && b.entities[entityPrefix]) return 1
175
198
  if (entityPrefix && a.entities[entityPrefix] && !b.entities[entityPrefix]) return -1
176
199
 
177
200
  if (countryCode && !a.countries[countryCode] && b.countries[countryCode]) return 1
178
201
  if (countryCode && a.countries[countryCode] && !b.countries[countryCode]) return -1
179
202
 
180
- if (ituRegion && !a.regions[ituRegion] && b.regions[ituRegion]) return 1
181
- if (ituRegion && a.regions[ituRegion] && !b.regions[ituRegion]) return -1
203
+ if (ituRegion && !a.regions[ituRegion.toString()] && b.regions[ituRegion.toString()]) return 1
204
+ if (ituRegion && a.regions[ituRegion.toString()] && !b.regions[ituRegion.toString()]) return -1
182
205
 
183
206
  return a.width - b.width
184
207
  })
185
208
 
186
209
  return sortedSegments[0]?.mode
187
210
  }
211
+
package/src/types.ts ADDED
@@ -0,0 +1,40 @@
1
+ export interface BandPlanSegment {
2
+ name: string
3
+ mhz: [number, number] | number[]
4
+ mode?: string | null
5
+ submode?: string
6
+ priority?: number
7
+ regions?: number[]
8
+ entities?: string[]
9
+ countries?: string[]
10
+ }
11
+
12
+ export interface BandPlan {
13
+ mhz: [number, number] | number[]
14
+ segments: BandPlanSegment[]
15
+ }
16
+
17
+ export interface BandPlansData {
18
+ sources: Array<{ url: string }>
19
+ bands: Record<string, BandPlan>
20
+ }
21
+
22
+ export interface SegmentWithMetadata {
23
+ name: string
24
+ mhz: [number, number]
25
+ mode?: string | null
26
+ submode?: string
27
+ priority?: number
28
+ band: string
29
+ width: number
30
+ regions: Record<string, boolean>
31
+ entities: Record<string, boolean>
32
+ countries: Record<string, boolean>
33
+ }
34
+
35
+ export interface ModeForFrequencyOptions {
36
+ ituRegion?: number
37
+ countryCode?: string
38
+ entityPrefix?: string
39
+ }
40
+
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
21
+