@springfield/ham-radio-utils 4.0.0 → 4.1.1

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.
@@ -3,29 +3,21 @@
3
3
  "id": "d1a5c7e8-3f9b-4a6c-8d2e-1b3f7a9c5e8d",
4
4
  "name": "160 Meter",
5
5
  "wavelength": 160,
6
- "lowerFrequency": 1800000000,
7
- "upperFrequency": 2000000000,
6
+ "lowerFrequency": 1800000,
7
+ "upperFrequency": 2000000,
8
8
  "frequencyDisplayBaseMultiplier": 1000000,
9
9
  "frequencyDisplayNumberDecimals": 3,
10
- "privileges": [
11
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
12
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
13
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
14
- ]
10
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
15
11
  },
16
12
  {
17
13
  "id": "e3b7a9c5-d1a5-4f8d-2e1b-c7e83f9b6a4c",
18
14
  "name": "80 Meter",
19
15
  "wavelength": 80,
20
- "lowerFrequency": 3500000000,
21
- "upperFrequency": 4000000000,
16
+ "lowerFrequency": 3500000,
17
+ "upperFrequency": 4000000,
22
18
  "frequencyDisplayBaseMultiplier": 1000000,
23
19
  "frequencyDisplayNumberDecimals": 3,
24
- "privileges": [
25
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
26
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
27
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
28
- ]
20
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
29
21
  },
30
22
  {
31
23
  "id": "f0a4c5e8-d1b3-c7e83f9b-a6c8-d2e1b9c5f7ea",
@@ -33,32 +25,29 @@
33
25
  "wavelength": 60,
34
26
  "lowerFrequency": 5332000,
35
27
  "upperFrequency": 5405000,
36
- "frequencyDisplayBaseMultiplier": 1000,
37
- "frequencyDisplayNumberDecimals": 1,
38
- "privileges": [
39
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
40
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
41
- ],
28
+ "frequencyDisplayBaseMultiplier": 1000000,
29
+ "frequencyDisplayNumberDecimals": 4,
30
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"],
42
31
  "channels": [
43
32
  {
44
33
  "name": "Channel 1",
45
- "frequency": 5332
34
+ "frequency": 5332000
46
35
  },
47
36
  {
48
37
  "name": "Channel 2",
49
- "frequency": 5348
38
+ "frequency": 5348000
50
39
  },
51
40
  {
52
41
  "name": "Channel 3",
53
- "frequency": 5358.5
42
+ "frequency": 5358500
54
43
  },
55
44
  {
56
45
  "name": "Channel 4",
57
- "frequency": 5373
46
+ "frequency": 5373000
58
47
  },
59
48
  {
60
49
  "name": "Channel 5",
61
- "frequency": 5405
50
+ "frequency": 5405000
62
51
  }
63
52
  ]
64
53
  },
@@ -66,28 +55,21 @@
66
55
  "id": "c5ea-f0a4-d1b39-c7ec83fa-a6cd82eb19cf",
67
56
  "name": "40 Meter",
68
57
  "wavelength": 40,
69
- "lowerFrequency": 7000000000,
70
- "upperFrequency": 7300000000,
58
+ "lowerFrequency": 7000000,
59
+ "upperFrequency": 7300000,
71
60
  "frequencyDisplayBaseMultiplier": 1000000,
72
61
  "frequencyDisplayNumberDecimals": 3,
73
- "privileges": [
74
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
75
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
76
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
77
- ]
62
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
78
63
  },
79
64
  {
80
65
  "id": "fa83ec-d12ab39-c75ea-a46cd82eb19cf",
81
66
  "name": "20 Meter",
82
67
  "wavelength": 20,
83
- "lowerFrequency": 14000000000,
84
- "upperFrequency": 14350000000,
68
+ "lowerFrequency": 14000000,
69
+ "upperFrequency": 14350000,
85
70
  "frequencyDisplayBaseMultiplier": 1000000,
86
71
  "frequencyDisplayNumberDecimals": 3,
87
- "privileges": [
88
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
89
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
90
- ]
72
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
91
73
  },
92
74
  {
93
75
  "id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
@@ -95,14 +77,9 @@
95
77
  "wavelength": 15,
96
78
  "lowerFrequency": 21000000,
97
79
  "upperFrequency": 21450000,
98
- "frequencyDisplayBaseMultiplier": 1000,
99
- "frequencyDisplayNumberDecimals": 0,
100
- "privileges": [
101
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
102
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
103
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d",
104
- "a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c"
105
- ]
80
+ "frequencyDisplayBaseMultiplier": 1000000,
81
+ "frequencyDisplayNumberDecimals": 3,
82
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d", "a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c"]
106
83
  },
107
84
  {
108
85
  "id": "p6o5n4m3-l2k1-j0i9-h8g7-f6e5-d4c3b2a1",
@@ -110,13 +87,9 @@
110
87
  "wavelength": 10,
111
88
  "lowerFrequency": 28000000,
112
89
  "upperFrequency": 29700000,
113
- "frequencyDisplayBaseMultiplier": 1000,
114
- "frequencyDisplayNumberDecimals": 0,
115
- "privileges": [
116
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
117
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
118
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
119
- ]
90
+ "frequencyDisplayBaseMultiplier": 1000000,
91
+ "frequencyDisplayNumberDecimals": 3,
92
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
120
93
  },
121
94
  {
122
95
  "id": "z1y2x3w4-v5u6-t7s8-r9q0-p1o2n3m4l5k6",
@@ -126,11 +99,7 @@
126
99
  "upperFrequency": 148000000,
127
100
  "frequencyDisplayBaseMultiplier": 1000000,
128
101
  "frequencyDisplayNumberDecimals": 4,
129
- "privileges": [
130
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
131
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
132
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
133
- ]
102
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
134
103
  },
135
104
  {
136
105
  "id": "k6l5m4n3-o2p1-q0r9-s8t7-u6v5-w4x3y2z1",
@@ -140,11 +109,7 @@
140
109
  "upperFrequency": 450000000,
141
110
  "frequencyDisplayBaseMultiplier": 1000000,
142
111
  "frequencyDisplayNumberDecimals": 4,
143
- "privileges": [
144
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
145
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
146
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
147
- ]
112
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
148
113
  },
149
114
  {
150
115
  "id": "03620a29-e66d-4940-ab67-3ef83558d5e2",
@@ -154,9 +119,7 @@
154
119
  "upperFrequency": 467725000,
155
120
  "frequencyDisplayBaseMultiplier": 1000000,
156
121
  "frequencyDisplayNumberDecimals": 4,
157
- "privileges": [
158
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd"
159
- ],
122
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd"],
160
123
  "channels": [
161
124
  {
162
125
  "name": "15RP",
@@ -197,13 +160,10 @@
197
160
  "name": "FRS/GMRS-1",
198
161
  "wavelength": 0.65,
199
162
  "lowerFrequency": 462550000,
200
- "upperFrequency": 462712500,
163
+ "upperFrequency": 462725000,
201
164
  "frequencyDisplayBaseMultiplier": 1000000,
202
165
  "frequencyDisplayNumberDecimals": 4,
203
- "privileges": [
204
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd",
205
- "a1a391d0-3708-4a26-94d3-00c07346b69b"
206
- ],
166
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd", "a1a391d0-3708-4a26-94d3-00c07346b69b"],
207
167
  "channels": [
208
168
  {
209
169
  "name": "1",
@@ -275,10 +235,7 @@
275
235
  "upperFrequency": 467712500,
276
236
  "frequencyDisplayBaseMultiplier": 1000000,
277
237
  "frequencyDisplayNumberDecimals": 4,
278
- "privileges": [
279
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd",
280
- "a1a391d0-3708-4a26-94d3-00c07346b69b"
281
- ],
238
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd", "a1a391d0-3708-4a26-94d3-00c07346b69b"],
282
239
  "channels": [
283
240
  {
284
241
  "name": "8",
@@ -318,9 +275,7 @@
318
275
  "upperFrequency": 162550000,
319
276
  "frequencyDisplayBaseMultiplier": 1000000,
320
277
  "frequencyDisplayNumberDecimals": 3,
321
- "privileges": [
322
- "9689b06f-dfb0-4ae6-8fdc-f9a4e6e1b88e"
323
- ],
278
+ "privileges": ["9689b06f-dfb0-4ae6-8fdc-f9a4e6e1b88e"],
324
279
  "channels": [
325
280
  {
326
281
  "name": "WX1",
@@ -332,7 +287,7 @@
332
287
  },
333
288
  {
334
289
  "name": "WX3",
335
- "frequency": 62475000
290
+ "frequency": 162475000
336
291
  },
337
292
  {
338
293
  "name": "WX4",
@@ -352,4 +307,4 @@
352
307
  }
353
308
  ]
354
309
  }
355
- ]
310
+ ]
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './utils/band-plan.js';
5
5
  export * from './utils/frequency-display.js';
6
6
  export * from './utils/memory-channel-utils.js';
7
7
  export * from './utils/memory-data-utils.js';
8
+ export * from './utils/operator-class-mapper.js';
8
9
  export * from './utils/schema-validator.js';
9
10
  export * from './utils/to-hex-words.js';
10
11
  export * from './utils/ui-logger.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './utils/band-plan.js';
5
5
  export * from './utils/frequency-display.js';
6
6
  export * from './utils/memory-channel-utils.js';
7
7
  export * from './utils/memory-data-utils.js';
8
+ export * from './utils/operator-class-mapper.js';
8
9
  export * from './utils/schema-validator.js';
9
10
  export * from './utils/to-hex-words.js';
10
11
  export * from './utils/ui-logger.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/operator-class-mapper.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\n"]}
@@ -19,6 +19,17 @@ export declare class BandPlan {
19
19
  * @returns The license class with the given ID, or undefined if not found
20
20
  */
21
21
  findPrivilegeById(id: string): LicenseClass | undefined;
22
+ /**
23
+ * Returns whether the given license class may transmit on the frequency.
24
+ *
25
+ * Looks up the band that contains the frequency and checks that band's
26
+ * privileges list. Frequencies outside every known band return false.
27
+ *
28
+ * @param frequencyHz - Frequency in Hz
29
+ * @param licenseClassId - Springfield license-class ID
30
+ * @returns true when the class has privilege on that frequency
31
+ */
32
+ hasPrivilege(frequencyHz: number, licenseClassId: string): boolean;
22
33
  }
23
34
  export {};
24
35
  //# sourceMappingURL=band-plan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"band-plan.d.ts","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI/D,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAA4B;IAElD,cAGC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE/D;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtD;CACF"}
1
+ {"version":3,"file":"band-plan.d.ts","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI/D,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAA4B;IAElD,cAGC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE/D;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtD;IAED;;;;;;;;;OASG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAQjE;CACF"}
@@ -23,5 +23,22 @@ export class BandPlan {
23
23
  findPrivilegeById(id) {
24
24
  return this.licenseClasses.get(id);
25
25
  }
26
+ /**
27
+ * Returns whether the given license class may transmit on the frequency.
28
+ *
29
+ * Looks up the band that contains the frequency and checks that band's
30
+ * privileges list. Frequencies outside every known band return false.
31
+ *
32
+ * @param frequencyHz - Frequency in Hz
33
+ * @param licenseClassId - Springfield license-class ID
34
+ * @returns true when the class has privilege on that frequency
35
+ */
36
+ hasPrivilege(frequencyHz, licenseClassId) {
37
+ const band = this.findBandByFrequency(frequencyHz);
38
+ if (!band) {
39
+ return false;
40
+ }
41
+ return band.privileges.includes(licenseClassId);
42
+ }
26
43
  }
27
44
  //# sourceMappingURL=band-plan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"band-plan.js","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,kBAAkB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC/D,OAAO,kBAAkB,MAAM,4BAA4B,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAOlF,MAAM,OAAO,QAAQ;IACX,KAAK,CAAiB;IACtB,cAAc,CAA4B;IAElD;QACE,IAAI,CAAC,KAAK,GAAG,SAA2B,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CAAE,kBAAqC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/H,CAAC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,SAAiB;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI,CAAC,cAAc,IAAI,SAAS,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;IACzG,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAU;QAC1B,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;CACF","sourcesContent":["import type { SpectrumBand } from '@springfield/ham-radio-api';\nimport bandsData from '../db/bands.json' with { type: 'json' };\nimport licenseClassesData from '../db/license-classes.json' with { type: 'json' };\n\ninterface LicenseClass {\n id: string;\n name: string;\n}\n\nexport class BandPlan {\n private bands: SpectrumBand[];\n private licenseClasses: Map<string, LicenseClass>;\n\n constructor() {\n this.bands = bandsData as SpectrumBand[];\n this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));\n }\n\n /**\n * Finds the spectrum band that contains the given frequency\n * @param frequency The frequency in Hz to find the band for\n * @returns The spectrum band containing the frequency, or undefined if no band contains it\n */\n findBandByFrequency(frequency: number): SpectrumBand | undefined {\n return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);\n }\n\n /**\n * Finds a license class by its ID\n * @param id The ID of the license class to find\n * @returns The license class with the given ID, or undefined if not found\n */\n findPrivilegeById(id: string): LicenseClass | undefined {\n return this.licenseClasses.get(id);\n }\n}\n"]}
1
+ {"version":3,"file":"band-plan.js","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,kBAAkB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC/D,OAAO,kBAAkB,MAAM,4BAA4B,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAOlF,MAAM,OAAO,QAAQ;IACX,KAAK,CAAiB;IACtB,cAAc,CAA4B;IAElD;QACE,IAAI,CAAC,KAAK,GAAG,SAA2B,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CAAE,kBAAqC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/H,CAAC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,SAAiB;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI,CAAC,cAAc,IAAI,SAAS,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;IACzG,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAU;QAC1B,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;;;;OASG;IACH,YAAY,CAAC,WAAmB,EAAE,cAAsB;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAEnD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAQ,IAAI,CAAC,UAAuB,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC;CACF","sourcesContent":["import type { SpectrumBand } from '@springfield/ham-radio-api';\nimport bandsData from '../db/bands.json' with { type: 'json' };\nimport licenseClassesData from '../db/license-classes.json' with { type: 'json' };\n\ninterface LicenseClass {\n id: string;\n name: string;\n}\n\nexport class BandPlan {\n private bands: SpectrumBand[];\n private licenseClasses: Map<string, LicenseClass>;\n\n constructor() {\n this.bands = bandsData as SpectrumBand[];\n this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));\n }\n\n /**\n * Finds the spectrum band that contains the given frequency\n * @param frequency The frequency in Hz to find the band for\n * @returns The spectrum band containing the frequency, or undefined if no band contains it\n */\n findBandByFrequency(frequency: number): SpectrumBand | undefined {\n return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);\n }\n\n /**\n * Finds a license class by its ID\n * @param id The ID of the license class to find\n * @returns The license class with the given ID, or undefined if not found\n */\n findPrivilegeById(id: string): LicenseClass | undefined {\n return this.licenseClasses.get(id);\n }\n\n /**\n * Returns whether the given license class may transmit on the frequency.\n *\n * Looks up the band that contains the frequency and checks that band's\n * privileges list. Frequencies outside every known band return false.\n *\n * @param frequencyHz - Frequency in Hz\n * @param licenseClassId - Springfield license-class ID\n * @returns true when the class has privilege on that frequency\n */\n hasPrivilege(frequencyHz: number, licenseClassId: string): boolean {\n const band = this.findBandByFrequency(frequencyHz);\n\n if (!band) {\n return false;\n }\n\n return (band.privileges as string[]).includes(licenseClassId);\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Maps a Callook.info / FCC operator-class name to a Springfield license-class ID.
3
+ *
4
+ * Callook returns values such as TECHNICIAN, GENERAL, and EXTRA. Club, military,
5
+ * and RACES records often have an empty operator class and map to undefined.
6
+ *
7
+ * @param operClass - Operator class string from Callook or an equivalent FCC source
8
+ * @returns The matching license-class ID, or undefined when no personal class applies
9
+ */
10
+ export declare function operatorClassToLicenseClassId(operClass: string): string | undefined;
11
+ //# sourceMappingURL=operator-class-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-class-mapper.d.ts","sourceRoot":"","sources":["../../src/utils/operator-class-mapper.ts"],"names":[],"mappings":"AAeA;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuBnF"}
@@ -0,0 +1,39 @@
1
+ import licenseClassesData from '../db/license-classes.json' with { type: 'json' };
2
+ const LICENSE_CLASSES = licenseClassesData;
3
+ const TECHNICIAN_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Technician').id;
4
+ const GENERAL_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'General').id;
5
+ const EXTRA_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Amateur Extra').id;
6
+ const ADVANCED_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Advanced (Grandfathered)').id;
7
+ const NOVICE_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Novice (Grandfathered)').id;
8
+ /**
9
+ * Maps a Callook.info / FCC operator-class name to a Springfield license-class ID.
10
+ *
11
+ * Callook returns values such as TECHNICIAN, GENERAL, and EXTRA. Club, military,
12
+ * and RACES records often have an empty operator class and map to undefined.
13
+ *
14
+ * @param operClass - Operator class string from Callook or an equivalent FCC source
15
+ * @returns The matching license-class ID, or undefined when no personal class applies
16
+ */
17
+ export function operatorClassToLicenseClassId(operClass) {
18
+ const normalized = operClass.trim().toUpperCase().replace(/\s+/g, ' ');
19
+ if (normalized.length === 0) {
20
+ return undefined;
21
+ }
22
+ switch (normalized) {
23
+ case 'TECHNICIAN':
24
+ case 'TECHNICIAN PLUS':
25
+ return TECHNICIAN_ID;
26
+ case 'GENERAL':
27
+ return GENERAL_ID;
28
+ case 'EXTRA':
29
+ case 'AMATEUR EXTRA':
30
+ return EXTRA_ID;
31
+ case 'ADVANCED':
32
+ return ADVANCED_ID;
33
+ case 'NOVICE':
34
+ return NOVICE_ID;
35
+ default:
36
+ return undefined;
37
+ }
38
+ }
39
+ //# sourceMappingURL=operator-class-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-class-mapper.js","sourceRoot":"","sources":["../../src/utils/operator-class-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,4BAA4B,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAOlF,MAAM,eAAe,GAAG,kBAAoC,CAAC;AAE7D,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,YAAY,CAAE,CAAC,EAAE,CAAC;AACrG,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,SAAS,CAAE,CAAC,EAAE,CAAC;AAC/F,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,eAAe,CAAE,CAAC,EAAE,CAAC;AACnG,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,0BAA0B,CAAE,CAAC,EAAE,CAAC;AACjH,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,wBAAwB,CAAE,CAAC,EAAE,CAAC;AAE7G;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,SAAiB;IAC7D,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,iBAAiB;YACpB,OAAO,aAAa,CAAC;QACvB,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC;QACpB,KAAK,OAAO,CAAC;QACb,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC;QAClB,KAAK,UAAU;YACb,OAAO,WAAW,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,SAAS,CAAC;QACnB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC","sourcesContent":["import licenseClassesData from '../db/license-classes.json' with { type: 'json' };\n\ninterface LicenseClass {\n id: string;\n name: string;\n}\n\nconst LICENSE_CLASSES = licenseClassesData as LicenseClass[];\n\nconst TECHNICIAN_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Technician')!.id;\nconst GENERAL_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'General')!.id;\nconst EXTRA_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Amateur Extra')!.id;\nconst ADVANCED_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Advanced (Grandfathered)')!.id;\nconst NOVICE_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Novice (Grandfathered)')!.id;\n\n/**\n * Maps a Callook.info / FCC operator-class name to a Springfield license-class ID.\n *\n * Callook returns values such as TECHNICIAN, GENERAL, and EXTRA. Club, military,\n * and RACES records often have an empty operator class and map to undefined.\n *\n * @param operClass - Operator class string from Callook or an equivalent FCC source\n * @returns The matching license-class ID, or undefined when no personal class applies\n */\nexport function operatorClassToLicenseClassId(operClass: string): string | undefined {\n const normalized = operClass.trim().toUpperCase().replace(/\\s+/g, ' ');\n\n if (normalized.length === 0) {\n return undefined;\n }\n\n switch (normalized) {\n case 'TECHNICIAN':\n case 'TECHNICIAN PLUS':\n return TECHNICIAN_ID;\n case 'GENERAL':\n return GENERAL_ID;\n case 'EXTRA':\n case 'AMATEUR EXTRA':\n return EXTRA_ID;\n case 'ADVANCED':\n return ADVANCED_ID;\n case 'NOVICE':\n return NOVICE_ID;\n default:\n return undefined;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springfield/ham-radio-utils",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "author": "Bryan Hunt",
5
5
  "license": "MIT",
6
6
  "readme": "README.md",
@@ -25,7 +25,9 @@
25
25
  "lint": "oxlint",
26
26
  "lint:fix": "oxlint --fix --fix-suggestions",
27
27
  "release": "semantic-release",
28
- "prepare": "husky"
28
+ "prepare": "husky",
29
+ "test": "yarn test:unit",
30
+ "test:unit": "tsx --test test/unit/**/*.test.ts"
29
31
  },
30
32
  "dependencies": {
31
33
  "@springfield/ham-radio-api": "^17.0.0",
@@ -50,6 +52,7 @@
50
52
  "oxfmt": "^0.64.0",
51
53
  "oxlint": "1.79.0",
52
54
  "semantic-release": "25.0.9",
55
+ "tsx": "4.23.12",
53
56
  "typescript": "7.0.2"
54
57
  },
55
58
  "resolutions": {
package/src/db/bands.json CHANGED
@@ -3,29 +3,21 @@
3
3
  "id": "d1a5c7e8-3f9b-4a6c-8d2e-1b3f7a9c5e8d",
4
4
  "name": "160 Meter",
5
5
  "wavelength": 160,
6
- "lowerFrequency": 1800000000,
7
- "upperFrequency": 2000000000,
6
+ "lowerFrequency": 1800000,
7
+ "upperFrequency": 2000000,
8
8
  "frequencyDisplayBaseMultiplier": 1000000,
9
9
  "frequencyDisplayNumberDecimals": 3,
10
- "privileges": [
11
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
12
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
13
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
14
- ]
10
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
15
11
  },
16
12
  {
17
13
  "id": "e3b7a9c5-d1a5-4f8d-2e1b-c7e83f9b6a4c",
18
14
  "name": "80 Meter",
19
15
  "wavelength": 80,
20
- "lowerFrequency": 3500000000,
21
- "upperFrequency": 4000000000,
16
+ "lowerFrequency": 3500000,
17
+ "upperFrequency": 4000000,
22
18
  "frequencyDisplayBaseMultiplier": 1000000,
23
19
  "frequencyDisplayNumberDecimals": 3,
24
- "privileges": [
25
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
26
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
27
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
28
- ]
20
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
29
21
  },
30
22
  {
31
23
  "id": "f0a4c5e8-d1b3-c7e83f9b-a6c8-d2e1b9c5f7ea",
@@ -33,32 +25,29 @@
33
25
  "wavelength": 60,
34
26
  "lowerFrequency": 5332000,
35
27
  "upperFrequency": 5405000,
36
- "frequencyDisplayBaseMultiplier": 1000,
37
- "frequencyDisplayNumberDecimals": 1,
38
- "privileges": [
39
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
40
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
41
- ],
28
+ "frequencyDisplayBaseMultiplier": 1000000,
29
+ "frequencyDisplayNumberDecimals": 4,
30
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"],
42
31
  "channels": [
43
32
  {
44
33
  "name": "Channel 1",
45
- "frequency": 5332
34
+ "frequency": 5332000
46
35
  },
47
36
  {
48
37
  "name": "Channel 2",
49
- "frequency": 5348
38
+ "frequency": 5348000
50
39
  },
51
40
  {
52
41
  "name": "Channel 3",
53
- "frequency": 5358.5
42
+ "frequency": 5358500
54
43
  },
55
44
  {
56
45
  "name": "Channel 4",
57
- "frequency": 5373
46
+ "frequency": 5373000
58
47
  },
59
48
  {
60
49
  "name": "Channel 5",
61
- "frequency": 5405
50
+ "frequency": 5405000
62
51
  }
63
52
  ]
64
53
  },
@@ -66,28 +55,21 @@
66
55
  "id": "c5ea-f0a4-d1b39-c7ec83fa-a6cd82eb19cf",
67
56
  "name": "40 Meter",
68
57
  "wavelength": 40,
69
- "lowerFrequency": 7000000000,
70
- "upperFrequency": 7300000000,
58
+ "lowerFrequency": 7000000,
59
+ "upperFrequency": 7300000,
71
60
  "frequencyDisplayBaseMultiplier": 1000000,
72
61
  "frequencyDisplayNumberDecimals": 3,
73
- "privileges": [
74
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
75
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
76
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
77
- ]
62
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
78
63
  },
79
64
  {
80
65
  "id": "fa83ec-d12ab39-c75ea-a46cd82eb19cf",
81
66
  "name": "20 Meter",
82
67
  "wavelength": 20,
83
- "lowerFrequency": 14000000000,
84
- "upperFrequency": 14350000000,
68
+ "lowerFrequency": 14000000,
69
+ "upperFrequency": 14350000,
85
70
  "frequencyDisplayBaseMultiplier": 1000000,
86
71
  "frequencyDisplayNumberDecimals": 3,
87
- "privileges": [
88
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
89
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
90
- ]
72
+ "privileges": ["f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
91
73
  },
92
74
  {
93
75
  "id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
@@ -95,14 +77,9 @@
95
77
  "wavelength": 15,
96
78
  "lowerFrequency": 21000000,
97
79
  "upperFrequency": 21450000,
98
- "frequencyDisplayBaseMultiplier": 1000,
99
- "frequencyDisplayNumberDecimals": 0,
100
- "privileges": [
101
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
102
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
103
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d",
104
- "a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c"
105
- ]
80
+ "frequencyDisplayBaseMultiplier": 1000000,
81
+ "frequencyDisplayNumberDecimals": 3,
82
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d", "a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c"]
106
83
  },
107
84
  {
108
85
  "id": "p6o5n4m3-l2k1-j0i9-h8g7-f6e5-d4c3b2a1",
@@ -110,13 +87,9 @@
110
87
  "wavelength": 10,
111
88
  "lowerFrequency": 28000000,
112
89
  "upperFrequency": 29700000,
113
- "frequencyDisplayBaseMultiplier": 1000,
114
- "frequencyDisplayNumberDecimals": 0,
115
- "privileges": [
116
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
117
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
118
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
119
- ]
90
+ "frequencyDisplayBaseMultiplier": 1000000,
91
+ "frequencyDisplayNumberDecimals": 3,
92
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
120
93
  },
121
94
  {
122
95
  "id": "z1y2x3w4-v5u6-t7s8-r9q0-p1o2n3m4l5k6",
@@ -126,11 +99,7 @@
126
99
  "upperFrequency": 148000000,
127
100
  "frequencyDisplayBaseMultiplier": 1000000,
128
101
  "frequencyDisplayNumberDecimals": 4,
129
- "privileges": [
130
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
131
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
132
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
133
- ]
102
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
134
103
  },
135
104
  {
136
105
  "id": "k6l5m4n3-o2p1-q0r9-s8t7-u6v5-w4x3y2z1",
@@ -140,11 +109,7 @@
140
109
  "upperFrequency": 450000000,
141
110
  "frequencyDisplayBaseMultiplier": 1000000,
142
111
  "frequencyDisplayNumberDecimals": 4,
143
- "privileges": [
144
- "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
145
- "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
146
- "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
147
- ]
112
+ "privileges": ["b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f", "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d", "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"]
148
113
  },
149
114
  {
150
115
  "id": "03620a29-e66d-4940-ab67-3ef83558d5e2",
@@ -154,9 +119,7 @@
154
119
  "upperFrequency": 467725000,
155
120
  "frequencyDisplayBaseMultiplier": 1000000,
156
121
  "frequencyDisplayNumberDecimals": 4,
157
- "privileges": [
158
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd"
159
- ],
122
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd"],
160
123
  "channels": [
161
124
  {
162
125
  "name": "15RP",
@@ -197,13 +160,10 @@
197
160
  "name": "FRS/GMRS-1",
198
161
  "wavelength": 0.65,
199
162
  "lowerFrequency": 462550000,
200
- "upperFrequency": 462712500,
163
+ "upperFrequency": 462725000,
201
164
  "frequencyDisplayBaseMultiplier": 1000000,
202
165
  "frequencyDisplayNumberDecimals": 4,
203
- "privileges": [
204
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd",
205
- "a1a391d0-3708-4a26-94d3-00c07346b69b"
206
- ],
166
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd", "a1a391d0-3708-4a26-94d3-00c07346b69b"],
207
167
  "channels": [
208
168
  {
209
169
  "name": "1",
@@ -275,10 +235,7 @@
275
235
  "upperFrequency": 467712500,
276
236
  "frequencyDisplayBaseMultiplier": 1000000,
277
237
  "frequencyDisplayNumberDecimals": 4,
278
- "privileges": [
279
- "27659a6b-78b6-47b1-8f91-f238ffd5ebcd",
280
- "a1a391d0-3708-4a26-94d3-00c07346b69b"
281
- ],
238
+ "privileges": ["27659a6b-78b6-47b1-8f91-f238ffd5ebcd", "a1a391d0-3708-4a26-94d3-00c07346b69b"],
282
239
  "channels": [
283
240
  {
284
241
  "name": "8",
@@ -318,9 +275,7 @@
318
275
  "upperFrequency": 162550000,
319
276
  "frequencyDisplayBaseMultiplier": 1000000,
320
277
  "frequencyDisplayNumberDecimals": 3,
321
- "privileges": [
322
- "9689b06f-dfb0-4ae6-8fdc-f9a4e6e1b88e"
323
- ],
278
+ "privileges": ["9689b06f-dfb0-4ae6-8fdc-f9a4e6e1b88e"],
324
279
  "channels": [
325
280
  {
326
281
  "name": "WX1",
@@ -332,7 +287,7 @@
332
287
  },
333
288
  {
334
289
  "name": "WX3",
335
- "frequency": 62475000
290
+ "frequency": 162475000
336
291
  },
337
292
  {
338
293
  "name": "WX4",
@@ -352,4 +307,4 @@
352
307
  }
353
308
  ]
354
309
  }
355
- ]
310
+ ]
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './utils/band-plan.js';
7
7
  export * from './utils/frequency-display.js';
8
8
  export * from './utils/memory-channel-utils.js';
9
9
  export * from './utils/memory-data-utils.js';
10
+ export * from './utils/operator-class-mapper.js';
10
11
  export * from './utils/schema-validator.js';
11
12
  export * from './utils/to-hex-words.js';
12
13
  export * from './utils/ui-logger.js';
@@ -33,4 +33,24 @@ export class BandPlan {
33
33
  findPrivilegeById(id: string): LicenseClass | undefined {
34
34
  return this.licenseClasses.get(id);
35
35
  }
36
+
37
+ /**
38
+ * Returns whether the given license class may transmit on the frequency.
39
+ *
40
+ * Looks up the band that contains the frequency and checks that band's
41
+ * privileges list. Frequencies outside every known band return false.
42
+ *
43
+ * @param frequencyHz - Frequency in Hz
44
+ * @param licenseClassId - Springfield license-class ID
45
+ * @returns true when the class has privilege on that frequency
46
+ */
47
+ hasPrivilege(frequencyHz: number, licenseClassId: string): boolean {
48
+ const band = this.findBandByFrequency(frequencyHz);
49
+
50
+ if (!band) {
51
+ return false;
52
+ }
53
+
54
+ return (band.privileges as string[]).includes(licenseClassId);
55
+ }
36
56
  }
@@ -0,0 +1,48 @@
1
+ import licenseClassesData from '../db/license-classes.json' with { type: 'json' };
2
+
3
+ interface LicenseClass {
4
+ id: string;
5
+ name: string;
6
+ }
7
+
8
+ const LICENSE_CLASSES = licenseClassesData as LicenseClass[];
9
+
10
+ const TECHNICIAN_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Technician')!.id;
11
+ const GENERAL_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'General')!.id;
12
+ const EXTRA_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Amateur Extra')!.id;
13
+ const ADVANCED_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Advanced (Grandfathered)')!.id;
14
+ const NOVICE_ID = LICENSE_CLASSES.find((licenseClass) => licenseClass.name === 'Novice (Grandfathered)')!.id;
15
+
16
+ /**
17
+ * Maps a Callook.info / FCC operator-class name to a Springfield license-class ID.
18
+ *
19
+ * Callook returns values such as TECHNICIAN, GENERAL, and EXTRA. Club, military,
20
+ * and RACES records often have an empty operator class and map to undefined.
21
+ *
22
+ * @param operClass - Operator class string from Callook or an equivalent FCC source
23
+ * @returns The matching license-class ID, or undefined when no personal class applies
24
+ */
25
+ export function operatorClassToLicenseClassId(operClass: string): string | undefined {
26
+ const normalized = operClass.trim().toUpperCase().replace(/\s+/g, ' ');
27
+
28
+ if (normalized.length === 0) {
29
+ return undefined;
30
+ }
31
+
32
+ switch (normalized) {
33
+ case 'TECHNICIAN':
34
+ case 'TECHNICIAN PLUS':
35
+ return TECHNICIAN_ID;
36
+ case 'GENERAL':
37
+ return GENERAL_ID;
38
+ case 'EXTRA':
39
+ case 'AMATEUR EXTRA':
40
+ return EXTRA_ID;
41
+ case 'ADVANCED':
42
+ return ADVANCED_ID;
43
+ case 'NOVICE':
44
+ return NOVICE_ID;
45
+ default:
46
+ return undefined;
47
+ }
48
+ }