@springfield/ham-radio-utils 4.1.0 → 4.1.2

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
+ ]
@@ -4,11 +4,18 @@ interface LicenseClass {
4
4
  name: string;
5
5
  }
6
6
  export declare class BandPlan {
7
- private bands;
8
- private licenseClasses;
7
+ private readonly bands;
8
+ private readonly licenseClasses;
9
+ private readonly frsLicenseClassId;
9
10
  constructor();
10
11
  /**
11
- * Finds the spectrum band that contains the given frequency
12
+ * Finds the spectrum band that contains the given frequency.
13
+ *
14
+ * Channelized allocations (FRS, GMRS, weather) interleave in the same
15
+ * MHz range. An exact channel match is preferred over a surrounding
16
+ * band envelope so FRS interstitial frequencies are not treated as GMRS
17
+ * repeater inputs.
18
+ *
12
19
  * @param frequency The frequency in Hz to find the band for
13
20
  * @returns The spectrum band containing the frequency, or undefined if no band contains it
14
21
  */
@@ -23,7 +30,9 @@ export declare class BandPlan {
23
30
  * Returns whether the given license class may transmit on the frequency.
24
31
  *
25
32
  * Looks up the band that contains the frequency and checks that band's
26
- * privileges list. Frequencies outside every known band return false.
33
+ * privileges list. FRS is a license-free service, so any band that
34
+ * includes the FRS privilege is allowed for every operator. Frequencies
35
+ * outside every known band return false.
27
36
  *
28
37
  * @param frequencyHz - Frequency in Hz
29
38
  * @param licenseClassId - Springfield license-class ID
@@ -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;IAED;;;;;;;;;OASG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAQjE;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,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAEvD,cAIC;IAED;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAS/D;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtD;IAED;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAcjE;CACF"}
@@ -3,17 +3,30 @@ import licenseClassesData from '../db/license-classes.json' with { type: 'json'
3
3
  export class BandPlan {
4
4
  bands;
5
5
  licenseClasses;
6
+ frsLicenseClassId;
6
7
  constructor() {
7
8
  this.bands = bandsData;
8
9
  this.licenseClasses = new Map(licenseClassesData.map((licenseClass) => [licenseClass.id, licenseClass]));
10
+ this.frsLicenseClassId = [...this.licenseClasses.values()].find((licenseClass) => licenseClass.name === 'FRS')?.id;
9
11
  }
10
12
  /**
11
- * Finds the spectrum band that contains the given frequency
13
+ * Finds the spectrum band that contains the given frequency.
14
+ *
15
+ * Channelized allocations (FRS, GMRS, weather) interleave in the same
16
+ * MHz range. An exact channel match is preferred over a surrounding
17
+ * band envelope so FRS interstitial frequencies are not treated as GMRS
18
+ * repeater inputs.
19
+ *
12
20
  * @param frequency The frequency in Hz to find the band for
13
21
  * @returns The spectrum band containing the frequency, or undefined if no band contains it
14
22
  */
15
23
  findBandByFrequency(frequency) {
16
- return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);
24
+ const frequencyHz = Math.round(frequency);
25
+ const channelMatch = this.bands.find((band) => band.channels?.some((channel) => channel.frequency === frequencyHz));
26
+ if (channelMatch) {
27
+ return channelMatch;
28
+ }
29
+ return this.bands.find((band) => frequencyHz >= band.lowerFrequency && frequencyHz <= band.upperFrequency);
17
30
  }
18
31
  /**
19
32
  * Finds a license class by its ID
@@ -27,7 +40,9 @@ export class BandPlan {
27
40
  * Returns whether the given license class may transmit on the frequency.
28
41
  *
29
42
  * Looks up the band that contains the frequency and checks that band's
30
- * privileges list. Frequencies outside every known band return false.
43
+ * privileges list. FRS is a license-free service, so any band that
44
+ * includes the FRS privilege is allowed for every operator. Frequencies
45
+ * outside every known band return false.
31
46
  *
32
47
  * @param frequencyHz - Frequency in Hz
33
48
  * @param licenseClassId - Springfield license-class ID
@@ -38,7 +53,11 @@ export class BandPlan {
38
53
  if (!band) {
39
54
  return false;
40
55
  }
41
- return band.privileges.includes(licenseClassId);
56
+ const privileges = band.privileges;
57
+ if (this.frsLicenseClassId && privileges.includes(this.frsLicenseClassId)) {
58
+ return true;
59
+ }
60
+ return privileges.includes(licenseClassId);
42
61
  }
43
62
  }
44
63
  //# 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;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"]}
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;IACF,KAAK,CAAiB;IACtB,cAAc,CAA4B;IAC1C,iBAAiB,CAAqB;IAEvD;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;QAC7H,IAAI,CAAC,iBAAiB,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;IACrH,CAAC;IAED;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,SAAiB;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC;QAEpH,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC;QACtB,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,IAAI,CAAC,cAAc,IAAI,WAAW,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7G,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAU;QAC1B,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;;;;;;OAWG;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,MAAM,UAAU,GAAG,IAAI,CAAC,UAAsB,CAAC;QAE/C,IAAI,IAAI,CAAC,iBAAiB,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC1E,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,UAAU,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC7C,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 readonly bands: SpectrumBand[];\n private readonly licenseClasses: Map<string, LicenseClass>;\n private readonly frsLicenseClassId: string | undefined;\n\n constructor() {\n this.bands = bandsData as SpectrumBand[];\n this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));\n this.frsLicenseClassId = [...this.licenseClasses.values()].find((licenseClass) => licenseClass.name === 'FRS')?.id;\n }\n\n /**\n * Finds the spectrum band that contains the given frequency.\n *\n * Channelized allocations (FRS, GMRS, weather) interleave in the same\n * MHz range. An exact channel match is preferred over a surrounding\n * band envelope so FRS interstitial frequencies are not treated as GMRS\n * repeater inputs.\n *\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 const frequencyHz = Math.round(frequency);\n const channelMatch = this.bands.find((band) => band.channels?.some((channel) => channel.frequency === frequencyHz));\n\n if (channelMatch) {\n return channelMatch;\n }\n\n return this.bands.find((band) => frequencyHz >= band.lowerFrequency && frequencyHz <= 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. FRS is a license-free service, so any band that\n * includes the FRS privilege is allowed for every operator. Frequencies\n * 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 const privileges = band.privileges as string[];\n\n if (this.frsLicenseClassId && privileges.includes(this.frsLicenseClassId)) {\n return true;\n }\n\n return privileges.includes(licenseClassId);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springfield/ham-radio-utils",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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
+ ]
@@ -8,21 +8,36 @@ interface LicenseClass {
8
8
  }
9
9
 
10
10
  export class BandPlan {
11
- private bands: SpectrumBand[];
12
- private licenseClasses: Map<string, LicenseClass>;
11
+ private readonly bands: SpectrumBand[];
12
+ private readonly licenseClasses: Map<string, LicenseClass>;
13
+ private readonly frsLicenseClassId: string | undefined;
13
14
 
14
15
  constructor() {
15
16
  this.bands = bandsData as SpectrumBand[];
16
17
  this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));
18
+ this.frsLicenseClassId = [...this.licenseClasses.values()].find((licenseClass) => licenseClass.name === 'FRS')?.id;
17
19
  }
18
20
 
19
21
  /**
20
- * Finds the spectrum band that contains the given frequency
22
+ * Finds the spectrum band that contains the given frequency.
23
+ *
24
+ * Channelized allocations (FRS, GMRS, weather) interleave in the same
25
+ * MHz range. An exact channel match is preferred over a surrounding
26
+ * band envelope so FRS interstitial frequencies are not treated as GMRS
27
+ * repeater inputs.
28
+ *
21
29
  * @param frequency The frequency in Hz to find the band for
22
30
  * @returns The spectrum band containing the frequency, or undefined if no band contains it
23
31
  */
24
32
  findBandByFrequency(frequency: number): SpectrumBand | undefined {
25
- return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);
33
+ const frequencyHz = Math.round(frequency);
34
+ const channelMatch = this.bands.find((band) => band.channels?.some((channel) => channel.frequency === frequencyHz));
35
+
36
+ if (channelMatch) {
37
+ return channelMatch;
38
+ }
39
+
40
+ return this.bands.find((band) => frequencyHz >= band.lowerFrequency && frequencyHz <= band.upperFrequency);
26
41
  }
27
42
 
28
43
  /**
@@ -38,7 +53,9 @@ export class BandPlan {
38
53
  * Returns whether the given license class may transmit on the frequency.
39
54
  *
40
55
  * Looks up the band that contains the frequency and checks that band's
41
- * privileges list. Frequencies outside every known band return false.
56
+ * privileges list. FRS is a license-free service, so any band that
57
+ * includes the FRS privilege is allowed for every operator. Frequencies
58
+ * outside every known band return false.
42
59
  *
43
60
  * @param frequencyHz - Frequency in Hz
44
61
  * @param licenseClassId - Springfield license-class ID
@@ -51,6 +68,12 @@ export class BandPlan {
51
68
  return false;
52
69
  }
53
70
 
54
- return (band.privileges as string[]).includes(licenseClassId);
71
+ const privileges = band.privileges as string[];
72
+
73
+ if (this.frsLicenseClassId && privileges.includes(this.frsLicenseClassId)) {
74
+ return true;
75
+ }
76
+
77
+ return privileges.includes(licenseClassId);
55
78
  }
56
79
  }