@justair/justair-library 7.3.0 → 7.4.0-alpha.9fd29bc
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 +12 -0
- package/dist/constants/deviceTypes.d.ts +8 -0
- package/dist/constants/deviceTypes.d.ts.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/pollutants.d.ts +100 -46
- package/dist/constants/tests/deviceTypes.test.d.ts +2 -0
- package/dist/constants/tests/deviceTypes.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/models/monitorEpisodes.d.ts +12 -12
- package/dist/models/users.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants/deviceTypes.js +39 -0
- package/src/constants/index.js +1 -0
- package/src/constants/pollutants.js +34 -0
- package/src/constants/tests/constantsBarrel.test.js +1 -1
- package/src/constants/tests/deviceTypes.test.js +48 -0
- package/src/constants/tests/pollutants.test.js +14 -0
- package/src/index.js +9 -0
- package/src/models/users.js +37 -0
package/README.md
CHANGED
|
@@ -97,6 +97,18 @@ The following parameters were added to `HEAVY_METALS` and are now included in `s
|
|
|
97
97
|
| `MN` | Manganese |
|
|
98
98
|
| `SE` | Selenium |
|
|
99
99
|
|
|
100
|
+
### New met parameters in 7.4.0
|
|
101
|
+
|
|
102
|
+
The following parameters were added to `PARAMETERS` and are now included in `parametersEnum`. Consuming services that display or filter parameters without a `type` filter (admin pickers, notification threshold UIs, reporting) will start showing them on install, even though no service writes them yet.
|
|
103
|
+
|
|
104
|
+
| id | Name | Unit |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `T_AMB` | Ambient Temperature | `°F` |
|
|
107
|
+
| `RH_AMB` | Ambient Relative Humidity | `%` |
|
|
108
|
+
| `DP_AMB` | Ambient Dew Point | `°F` |
|
|
109
|
+
|
|
110
|
+
These are **not** interchangeable with the bare `T` / `RH` / `DP` ids, which are enclosure-internal readings used only by correction equations and fog detection. Never fall back from one kind to the other. See "Ambient vs internal met parameters" in `CLAUDE.md` before wiring these up.
|
|
111
|
+
|
|
100
112
|
### Object shape
|
|
101
113
|
|
|
102
114
|
Each constant is an object with the following fields:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export namespace DEVICE_TYPES {
|
|
2
|
+
let MONITOR: string;
|
|
3
|
+
let SAMPLE_SITE: string;
|
|
4
|
+
let LINKED_DEVICE: string;
|
|
5
|
+
}
|
|
6
|
+
export const DEVICE_TYPE_VALUES: string[];
|
|
7
|
+
export function isAqiCapableDeviceType(deviceType: any): boolean;
|
|
8
|
+
//# sourceMappingURL=deviceTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceTypes.d.ts","sourceRoot":"","sources":["../../src/constants/deviceTypes.js"],"names":[],"mappings":";;;;;AA2BA,0CAA8D;AASvD,iEAEoC"}
|
|
@@ -279,7 +279,7 @@ export namespace PARAMETERS {
|
|
|
279
279
|
let impacts_15: any;
|
|
280
280
|
export { impacts_15 as impacts };
|
|
281
281
|
}
|
|
282
|
-
namespace
|
|
282
|
+
namespace DP_AMB {
|
|
283
283
|
let id_16: string;
|
|
284
284
|
export { id_16 as id };
|
|
285
285
|
let label_16: string;
|
|
@@ -297,7 +297,7 @@ export namespace PARAMETERS {
|
|
|
297
297
|
let impacts_16: any;
|
|
298
298
|
export { impacts_16 as impacts };
|
|
299
299
|
}
|
|
300
|
-
namespace
|
|
300
|
+
namespace RH {
|
|
301
301
|
let id_17: string;
|
|
302
302
|
export { id_17 as id };
|
|
303
303
|
let label_17: string;
|
|
@@ -315,7 +315,7 @@ export namespace PARAMETERS {
|
|
|
315
315
|
let impacts_17: any;
|
|
316
316
|
export { impacts_17 as impacts };
|
|
317
317
|
}
|
|
318
|
-
namespace
|
|
318
|
+
namespace RH_AMB {
|
|
319
319
|
let id_18: string;
|
|
320
320
|
export { id_18 as id };
|
|
321
321
|
let label_18: string;
|
|
@@ -333,7 +333,7 @@ export namespace PARAMETERS {
|
|
|
333
333
|
let impacts_18: any;
|
|
334
334
|
export { impacts_18 as impacts };
|
|
335
335
|
}
|
|
336
|
-
namespace
|
|
336
|
+
namespace T {
|
|
337
337
|
let id_19: string;
|
|
338
338
|
export { id_19 as id };
|
|
339
339
|
let label_19: string;
|
|
@@ -351,7 +351,7 @@ export namespace PARAMETERS {
|
|
|
351
351
|
let impacts_19: any;
|
|
352
352
|
export { impacts_19 as impacts };
|
|
353
353
|
}
|
|
354
|
-
namespace
|
|
354
|
+
namespace T_AMB {
|
|
355
355
|
let id_20: string;
|
|
356
356
|
export { id_20 as id };
|
|
357
357
|
let label_20: string;
|
|
@@ -369,7 +369,7 @@ export namespace PARAMETERS {
|
|
|
369
369
|
let impacts_20: any;
|
|
370
370
|
export { impacts_20 as impacts };
|
|
371
371
|
}
|
|
372
|
-
namespace
|
|
372
|
+
namespace WD {
|
|
373
373
|
let id_21: string;
|
|
374
374
|
export { id_21 as id };
|
|
375
375
|
let label_21: string;
|
|
@@ -387,7 +387,7 @@ export namespace PARAMETERS {
|
|
|
387
387
|
let impacts_21: any;
|
|
388
388
|
export { impacts_21 as impacts };
|
|
389
389
|
}
|
|
390
|
-
namespace
|
|
390
|
+
namespace WS {
|
|
391
391
|
let id_22: string;
|
|
392
392
|
export { id_22 as id };
|
|
393
393
|
let label_22: string;
|
|
@@ -405,7 +405,7 @@ export namespace PARAMETERS {
|
|
|
405
405
|
let impacts_22: any;
|
|
406
406
|
export { impacts_22 as impacts };
|
|
407
407
|
}
|
|
408
|
-
namespace
|
|
408
|
+
namespace GS {
|
|
409
409
|
let id_23: string;
|
|
410
410
|
export { id_23 as id };
|
|
411
411
|
let label_23: string;
|
|
@@ -423,7 +423,7 @@ export namespace PARAMETERS {
|
|
|
423
423
|
let impacts_23: any;
|
|
424
424
|
export { impacts_23 as impacts };
|
|
425
425
|
}
|
|
426
|
-
namespace
|
|
426
|
+
namespace R {
|
|
427
427
|
let id_24: string;
|
|
428
428
|
export { id_24 as id };
|
|
429
429
|
let label_24: string;
|
|
@@ -441,7 +441,7 @@ export namespace PARAMETERS {
|
|
|
441
441
|
let impacts_24: any;
|
|
442
442
|
export { impacts_24 as impacts };
|
|
443
443
|
}
|
|
444
|
-
namespace
|
|
444
|
+
namespace SR {
|
|
445
445
|
let id_25: string;
|
|
446
446
|
export { id_25 as id };
|
|
447
447
|
let label_25: string;
|
|
@@ -459,7 +459,7 @@ export namespace PARAMETERS {
|
|
|
459
459
|
let impacts_25: any;
|
|
460
460
|
export { impacts_25 as impacts };
|
|
461
461
|
}
|
|
462
|
-
namespace
|
|
462
|
+
namespace P {
|
|
463
463
|
let id_26: string;
|
|
464
464
|
export { id_26 as id };
|
|
465
465
|
let label_26: string;
|
|
@@ -477,7 +477,7 @@ export namespace PARAMETERS {
|
|
|
477
477
|
let impacts_26: any;
|
|
478
478
|
export { impacts_26 as impacts };
|
|
479
479
|
}
|
|
480
|
-
namespace
|
|
480
|
+
namespace PAR {
|
|
481
481
|
let id_27: string;
|
|
482
482
|
export { id_27 as id };
|
|
483
483
|
let label_27: string;
|
|
@@ -495,7 +495,7 @@ export namespace PARAMETERS {
|
|
|
495
495
|
let impacts_27: any;
|
|
496
496
|
export { impacts_27 as impacts };
|
|
497
497
|
}
|
|
498
|
-
namespace
|
|
498
|
+
namespace ARM {
|
|
499
499
|
let id_28: string;
|
|
500
500
|
export { id_28 as id };
|
|
501
501
|
let label_28: string;
|
|
@@ -513,9 +513,7 @@ export namespace PARAMETERS {
|
|
|
513
513
|
let impacts_28: any;
|
|
514
514
|
export { impacts_28 as impacts };
|
|
515
515
|
}
|
|
516
|
-
|
|
517
|
-
export namespace HEAVY_METALS {
|
|
518
|
-
export namespace AS {
|
|
516
|
+
namespace ARW {
|
|
519
517
|
let id_29: string;
|
|
520
518
|
export { id_29 as id };
|
|
521
519
|
let label_29: string;
|
|
@@ -526,14 +524,14 @@ export namespace HEAVY_METALS {
|
|
|
526
524
|
export { unit_29 as unit };
|
|
527
525
|
let type_29: string;
|
|
528
526
|
export { type_29 as type };
|
|
529
|
-
let rfc_29:
|
|
527
|
+
let rfc_29: any;
|
|
530
528
|
export { rfc_29 as rfc };
|
|
531
|
-
let origin_29:
|
|
529
|
+
let origin_29: any;
|
|
532
530
|
export { origin_29 as origin };
|
|
533
|
-
let impacts_29:
|
|
531
|
+
let impacts_29: any;
|
|
534
532
|
export { impacts_29 as impacts };
|
|
535
533
|
}
|
|
536
|
-
|
|
534
|
+
namespace ARD {
|
|
537
535
|
let id_30: string;
|
|
538
536
|
export { id_30 as id };
|
|
539
537
|
let label_30: string;
|
|
@@ -546,12 +544,12 @@ export namespace HEAVY_METALS {
|
|
|
546
544
|
export { type_30 as type };
|
|
547
545
|
let rfc_30: any;
|
|
548
546
|
export { rfc_30 as rfc };
|
|
549
|
-
let origin_30:
|
|
547
|
+
let origin_30: any;
|
|
550
548
|
export { origin_30 as origin };
|
|
551
|
-
let impacts_30:
|
|
549
|
+
let impacts_30: any;
|
|
552
550
|
export { impacts_30 as impacts };
|
|
553
551
|
}
|
|
554
|
-
|
|
552
|
+
namespace ARH {
|
|
555
553
|
let id_31: string;
|
|
556
554
|
export { id_31 as id };
|
|
557
555
|
let label_31: string;
|
|
@@ -562,14 +560,16 @@ export namespace HEAVY_METALS {
|
|
|
562
560
|
export { unit_31 as unit };
|
|
563
561
|
let type_31: string;
|
|
564
562
|
export { type_31 as type };
|
|
565
|
-
let rfc_31:
|
|
563
|
+
let rfc_31: any;
|
|
566
564
|
export { rfc_31 as rfc };
|
|
567
|
-
let origin_31:
|
|
565
|
+
let origin_31: any;
|
|
568
566
|
export { origin_31 as origin };
|
|
569
|
-
let impacts_31:
|
|
567
|
+
let impacts_31: any;
|
|
570
568
|
export { impacts_31 as impacts };
|
|
571
569
|
}
|
|
572
|
-
|
|
570
|
+
}
|
|
571
|
+
export namespace HEAVY_METALS {
|
|
572
|
+
export namespace AS {
|
|
573
573
|
let id_32: string;
|
|
574
574
|
export { id_32 as id };
|
|
575
575
|
let label_32: string;
|
|
@@ -587,7 +587,7 @@ export namespace HEAVY_METALS {
|
|
|
587
587
|
let impacts_32: string;
|
|
588
588
|
export { impacts_32 as impacts };
|
|
589
589
|
}
|
|
590
|
-
export namespace
|
|
590
|
+
export namespace BA {
|
|
591
591
|
let id_33: string;
|
|
592
592
|
export { id_33 as id };
|
|
593
593
|
let label_33: string;
|
|
@@ -598,14 +598,14 @@ export namespace HEAVY_METALS {
|
|
|
598
598
|
export { unit_33 as unit };
|
|
599
599
|
let type_33: string;
|
|
600
600
|
export { type_33 as type };
|
|
601
|
-
let rfc_33:
|
|
601
|
+
let rfc_33: any;
|
|
602
602
|
export { rfc_33 as rfc };
|
|
603
603
|
let origin_33: string;
|
|
604
604
|
export { origin_33 as origin };
|
|
605
605
|
let impacts_33: string;
|
|
606
606
|
export { impacts_33 as impacts };
|
|
607
607
|
}
|
|
608
|
-
export namespace
|
|
608
|
+
export namespace C6H6 {
|
|
609
609
|
let id_34: string;
|
|
610
610
|
export { id_34 as id };
|
|
611
611
|
let label_34: string;
|
|
@@ -623,7 +623,7 @@ export namespace HEAVY_METALS {
|
|
|
623
623
|
let impacts_34: string;
|
|
624
624
|
export { impacts_34 as impacts };
|
|
625
625
|
}
|
|
626
|
-
export namespace
|
|
626
|
+
export namespace BE {
|
|
627
627
|
let id_35: string;
|
|
628
628
|
export { id_35 as id };
|
|
629
629
|
let label_35: string;
|
|
@@ -634,15 +634,14 @@ export namespace HEAVY_METALS {
|
|
|
634
634
|
export { unit_35 as unit };
|
|
635
635
|
let type_35: string;
|
|
636
636
|
export { type_35 as type };
|
|
637
|
-
let rfc_35:
|
|
637
|
+
let rfc_35: number;
|
|
638
638
|
export { rfc_35 as rfc };
|
|
639
639
|
let origin_35: string;
|
|
640
640
|
export { origin_35 as origin };
|
|
641
641
|
let impacts_35: string;
|
|
642
642
|
export { impacts_35 as impacts };
|
|
643
643
|
}
|
|
644
|
-
export
|
|
645
|
-
export namespace CU {
|
|
644
|
+
export namespace CD {
|
|
646
645
|
let id_36: string;
|
|
647
646
|
export { id_36 as id };
|
|
648
647
|
let label_36: string;
|
|
@@ -653,14 +652,14 @@ export namespace HEAVY_METALS {
|
|
|
653
652
|
export { unit_36 as unit };
|
|
654
653
|
let type_36: string;
|
|
655
654
|
export { type_36 as type };
|
|
656
|
-
let rfc_36:
|
|
655
|
+
let rfc_36: number;
|
|
657
656
|
export { rfc_36 as rfc };
|
|
658
657
|
let origin_36: string;
|
|
659
658
|
export { origin_36 as origin };
|
|
660
659
|
let impacts_36: string;
|
|
661
660
|
export { impacts_36 as impacts };
|
|
662
661
|
}
|
|
663
|
-
export namespace
|
|
662
|
+
export namespace CR {
|
|
664
663
|
let id_37: string;
|
|
665
664
|
export { id_37 as id };
|
|
666
665
|
let label_37: string;
|
|
@@ -671,14 +670,14 @@ export namespace HEAVY_METALS {
|
|
|
671
670
|
export { unit_37 as unit };
|
|
672
671
|
let type_37: string;
|
|
673
672
|
export { type_37 as type };
|
|
674
|
-
let rfc_37:
|
|
673
|
+
let rfc_37: number;
|
|
675
674
|
export { rfc_37 as rfc };
|
|
676
675
|
let origin_37: string;
|
|
677
676
|
export { origin_37 as origin };
|
|
678
677
|
let impacts_37: string;
|
|
679
678
|
export { impacts_37 as impacts };
|
|
680
679
|
}
|
|
681
|
-
export namespace
|
|
680
|
+
export namespace CO_1 {
|
|
682
681
|
let id_38: string;
|
|
683
682
|
export { id_38 as id };
|
|
684
683
|
let label_38: string;
|
|
@@ -689,14 +688,15 @@ export namespace HEAVY_METALS {
|
|
|
689
688
|
export { unit_38 as unit };
|
|
690
689
|
let type_38: string;
|
|
691
690
|
export { type_38 as type };
|
|
692
|
-
let rfc_38:
|
|
691
|
+
let rfc_38: any;
|
|
693
692
|
export { rfc_38 as rfc };
|
|
694
693
|
let origin_38: string;
|
|
695
694
|
export { origin_38 as origin };
|
|
696
695
|
let impacts_38: string;
|
|
697
696
|
export { impacts_38 as impacts };
|
|
698
697
|
}
|
|
699
|
-
export
|
|
698
|
+
export { CO_1 as CO };
|
|
699
|
+
export namespace CU {
|
|
700
700
|
let id_39: string;
|
|
701
701
|
export { id_39 as id };
|
|
702
702
|
let label_39: string;
|
|
@@ -707,14 +707,14 @@ export namespace HEAVY_METALS {
|
|
|
707
707
|
export { unit_39 as unit };
|
|
708
708
|
let type_39: string;
|
|
709
709
|
export { type_39 as type };
|
|
710
|
-
let rfc_39:
|
|
710
|
+
let rfc_39: any;
|
|
711
711
|
export { rfc_39 as rfc };
|
|
712
712
|
let origin_39: string;
|
|
713
713
|
export { origin_39 as origin };
|
|
714
714
|
let impacts_39: string;
|
|
715
715
|
export { impacts_39 as impacts };
|
|
716
716
|
}
|
|
717
|
-
export namespace
|
|
717
|
+
export namespace FE {
|
|
718
718
|
let id_40: string;
|
|
719
719
|
export { id_40 as id };
|
|
720
720
|
let label_40: string;
|
|
@@ -732,7 +732,7 @@ export namespace HEAVY_METALS {
|
|
|
732
732
|
let impacts_40: string;
|
|
733
733
|
export { impacts_40 as impacts };
|
|
734
734
|
}
|
|
735
|
-
export namespace
|
|
735
|
+
export namespace PB {
|
|
736
736
|
let id_41: string;
|
|
737
737
|
export { id_41 as id };
|
|
738
738
|
let label_41: string;
|
|
@@ -743,14 +743,14 @@ export namespace HEAVY_METALS {
|
|
|
743
743
|
export { unit_41 as unit };
|
|
744
744
|
let type_41: string;
|
|
745
745
|
export { type_41 as type };
|
|
746
|
-
let rfc_41:
|
|
746
|
+
let rfc_41: number;
|
|
747
747
|
export { rfc_41 as rfc };
|
|
748
748
|
let origin_41: string;
|
|
749
749
|
export { origin_41 as origin };
|
|
750
750
|
let impacts_41: string;
|
|
751
751
|
export { impacts_41 as impacts };
|
|
752
752
|
}
|
|
753
|
-
export namespace
|
|
753
|
+
export namespace MN {
|
|
754
754
|
let id_42: string;
|
|
755
755
|
export { id_42 as id };
|
|
756
756
|
let label_42: string;
|
|
@@ -761,12 +761,66 @@ export namespace HEAVY_METALS {
|
|
|
761
761
|
export { unit_42 as unit };
|
|
762
762
|
let type_42: string;
|
|
763
763
|
export { type_42 as type };
|
|
764
|
-
let rfc_42:
|
|
764
|
+
let rfc_42: number;
|
|
765
765
|
export { rfc_42 as rfc };
|
|
766
766
|
let origin_42: string;
|
|
767
767
|
export { origin_42 as origin };
|
|
768
768
|
let impacts_42: string;
|
|
769
769
|
export { impacts_42 as impacts };
|
|
770
770
|
}
|
|
771
|
+
export namespace NI {
|
|
772
|
+
let id_43: string;
|
|
773
|
+
export { id_43 as id };
|
|
774
|
+
let label_43: string;
|
|
775
|
+
export { label_43 as label };
|
|
776
|
+
let name_43: string;
|
|
777
|
+
export { name_43 as name };
|
|
778
|
+
let unit_43: string;
|
|
779
|
+
export { unit_43 as unit };
|
|
780
|
+
let type_43: string;
|
|
781
|
+
export { type_43 as type };
|
|
782
|
+
let rfc_43: any;
|
|
783
|
+
export { rfc_43 as rfc };
|
|
784
|
+
let origin_43: string;
|
|
785
|
+
export { origin_43 as origin };
|
|
786
|
+
let impacts_43: string;
|
|
787
|
+
export { impacts_43 as impacts };
|
|
788
|
+
}
|
|
789
|
+
export namespace SE {
|
|
790
|
+
let id_44: string;
|
|
791
|
+
export { id_44 as id };
|
|
792
|
+
let label_44: string;
|
|
793
|
+
export { label_44 as label };
|
|
794
|
+
let name_44: string;
|
|
795
|
+
export { name_44 as name };
|
|
796
|
+
let unit_44: string;
|
|
797
|
+
export { unit_44 as unit };
|
|
798
|
+
let type_44: string;
|
|
799
|
+
export { type_44 as type };
|
|
800
|
+
let rfc_44: any;
|
|
801
|
+
export { rfc_44 as rfc };
|
|
802
|
+
let origin_44: string;
|
|
803
|
+
export { origin_44 as origin };
|
|
804
|
+
let impacts_44: string;
|
|
805
|
+
export { impacts_44 as impacts };
|
|
806
|
+
}
|
|
807
|
+
export namespace ZN {
|
|
808
|
+
let id_45: string;
|
|
809
|
+
export { id_45 as id };
|
|
810
|
+
let label_45: string;
|
|
811
|
+
export { label_45 as label };
|
|
812
|
+
let name_45: string;
|
|
813
|
+
export { name_45 as name };
|
|
814
|
+
let unit_45: string;
|
|
815
|
+
export { unit_45 as unit };
|
|
816
|
+
let type_45: string;
|
|
817
|
+
export { type_45 as type };
|
|
818
|
+
let rfc_45: any;
|
|
819
|
+
export { rfc_45 as rfc };
|
|
820
|
+
let origin_45: string;
|
|
821
|
+
export { origin_45 as origin };
|
|
822
|
+
let impacts_45: string;
|
|
823
|
+
export { impacts_45 as impacts };
|
|
824
|
+
}
|
|
771
825
|
}
|
|
772
826
|
//# sourceMappingURL=pollutants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deviceTypes.test.d.ts","sourceRoot":"","sources":["../../../src/constants/tests/deviceTypes.test.js"],"names":[],"mappings":""}
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,9 @@ import { PARAMETERS } from "./constants/pollutants.js";
|
|
|
103
103
|
import { HEAVY_METALS } from "./constants/pollutants.js";
|
|
104
104
|
import { ALERT_LEVEL_WEIGHTS } from "./constants/alertLevels.js";
|
|
105
105
|
import { getAlertWeight } from "./constants/alertLevels.js";
|
|
106
|
+
import { DEVICE_TYPES } from "./constants/deviceTypes.js";
|
|
107
|
+
import { DEVICE_TYPE_VALUES } from "./constants/deviceTypes.js";
|
|
108
|
+
import { isAqiCapableDeviceType } from "./constants/deviceTypes.js";
|
|
106
109
|
import { splitName } from "./utils/splitName.js";
|
|
107
110
|
import { DEFAULT_QUIET_HOURS } from "./utils/quietHours.js";
|
|
108
111
|
import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
|
|
@@ -113,5 +116,5 @@ import { validateQuietHours } from "./utils/quietHours.js";
|
|
|
113
116
|
import { getZoneMinutes } from "./utils/quietHours.js";
|
|
114
117
|
import { isInQuietWindow } from "./utils/quietHours.js";
|
|
115
118
|
import { resolveQuietHours } from "./utils/quietHours.js";
|
|
116
|
-
export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
|
|
119
|
+
export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, DEVICE_TYPES, DEVICE_TYPE_VALUES, isAqiCapableDeviceType, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
|
|
117
120
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAyJA;;;iBAEC;yBAzFwB,oBAAoB;qBADxB,gBAAgB;4BAjEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCACgB,4BAA4B;+BAA5B,4BAA4B;mCAChC,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAwCT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCACiC,gCAAgC;mCAAhC,gCAAgC;sCA5CtF,6BAA6B;+BAA7B,6BAA6B;wCAW7B,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;oCApBF,8BAA8B;mCA2D3D,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;+DAsB3B,mCAAmC;qCAAnC,mCAAmC;mEAAnC,mCAAmC;yEAAnC,mCAAmC;mEAAnC,mCAAmC;qEAAnC,mCAAmC;yEAAnC,mCAAmC;2EAAnC,mCAAmC;2BApFD,2BAA2B;6BAA3B,2BAA2B;oCAChB,4BAA4B;+BAA5B,4BAA4B;6BAOzE,4BAA4B;mCAA5B,4BAA4B;uCAA5B,4BAA4B;0BAgBT,sBAAsB;oCASzC,uBAAuB;iCA/BG,0BAA0B;mCA+BpD,uBAAuB;kCAAvB,uBAAuB;oCACM,qBAAqB;mCADlD,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const monitorEpisodesSchema: mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
2
2
|
timestamps: true;
|
|
3
3
|
}, {
|
|
4
|
-
user: mongoose.Types.ObjectId;
|
|
5
4
|
monitor: mongoose.Types.ObjectId;
|
|
5
|
+
user: mongoose.Types.ObjectId;
|
|
6
6
|
open: boolean;
|
|
7
7
|
startedAt: NativeDate;
|
|
8
8
|
alertCount: number;
|
|
@@ -10,8 +10,8 @@ export const monitorEpisodesSchema: mongoose.Schema<any, mongoose.Model<any, any
|
|
|
10
10
|
endedAt?: NativeDate;
|
|
11
11
|
peakStatus?: string;
|
|
12
12
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
13
|
-
user: mongoose.Types.ObjectId;
|
|
14
13
|
monitor: mongoose.Types.ObjectId;
|
|
14
|
+
user: mongoose.Types.ObjectId;
|
|
15
15
|
open: boolean;
|
|
16
16
|
startedAt: NativeDate;
|
|
17
17
|
alertCount: number;
|
|
@@ -23,8 +23,8 @@ export const monitorEpisodesSchema: mongoose.Schema<any, mongoose.Model<any, any
|
|
|
23
23
|
}, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
|
|
24
24
|
timestamps: true;
|
|
25
25
|
}> & Omit<{
|
|
26
|
-
user: mongoose.Types.ObjectId;
|
|
27
26
|
monitor: mongoose.Types.ObjectId;
|
|
27
|
+
user: mongoose.Types.ObjectId;
|
|
28
28
|
open: boolean;
|
|
29
29
|
startedAt: NativeDate;
|
|
30
30
|
alertCount: number;
|
|
@@ -38,8 +38,8 @@ export const monitorEpisodesSchema: mongoose.Schema<any, mongoose.Model<any, any
|
|
|
38
38
|
}, "id"> & mongoose.HydratedDocumentOverrides<{
|
|
39
39
|
id: string;
|
|
40
40
|
}>, unknown, {
|
|
41
|
-
user: mongoose.Types.ObjectId;
|
|
42
41
|
monitor: mongoose.Types.ObjectId;
|
|
42
|
+
user: mongoose.Types.ObjectId;
|
|
43
43
|
open: boolean;
|
|
44
44
|
startedAt: NativeDate;
|
|
45
45
|
alertCount: number;
|
|
@@ -54,8 +54,8 @@ export const monitorEpisodesSchema: mongoose.Schema<any, mongoose.Model<any, any
|
|
|
54
54
|
__v: number;
|
|
55
55
|
}>;
|
|
56
56
|
export const MonitorEpisode: mongoose.Model<{
|
|
57
|
-
user: mongoose.Types.ObjectId;
|
|
58
57
|
monitor: mongoose.Types.ObjectId;
|
|
58
|
+
user: mongoose.Types.ObjectId;
|
|
59
59
|
open: boolean;
|
|
60
60
|
startedAt: NativeDate;
|
|
61
61
|
alertCount: number;
|
|
@@ -65,8 +65,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
65
65
|
} & mongoose.DefaultTimestampProps, {}, {}, {
|
|
66
66
|
id: string;
|
|
67
67
|
}, mongoose.Document<unknown, {}, {
|
|
68
|
-
user: mongoose.Types.ObjectId;
|
|
69
68
|
monitor: mongoose.Types.ObjectId;
|
|
69
|
+
user: mongoose.Types.ObjectId;
|
|
70
70
|
open: boolean;
|
|
71
71
|
startedAt: NativeDate;
|
|
72
72
|
alertCount: number;
|
|
@@ -78,8 +78,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
78
78
|
}, {
|
|
79
79
|
timestamps: true;
|
|
80
80
|
}> & Omit<{
|
|
81
|
-
user: mongoose.Types.ObjectId;
|
|
82
81
|
monitor: mongoose.Types.ObjectId;
|
|
82
|
+
user: mongoose.Types.ObjectId;
|
|
83
83
|
open: boolean;
|
|
84
84
|
startedAt: NativeDate;
|
|
85
85
|
alertCount: number;
|
|
@@ -95,8 +95,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
95
95
|
}>, mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
96
96
|
timestamps: true;
|
|
97
97
|
}, {
|
|
98
|
-
user: mongoose.Types.ObjectId;
|
|
99
98
|
monitor: mongoose.Types.ObjectId;
|
|
99
|
+
user: mongoose.Types.ObjectId;
|
|
100
100
|
open: boolean;
|
|
101
101
|
startedAt: NativeDate;
|
|
102
102
|
alertCount: number;
|
|
@@ -104,8 +104,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
104
104
|
endedAt?: NativeDate;
|
|
105
105
|
peakStatus?: string;
|
|
106
106
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
107
|
-
user: mongoose.Types.ObjectId;
|
|
108
107
|
monitor: mongoose.Types.ObjectId;
|
|
108
|
+
user: mongoose.Types.ObjectId;
|
|
109
109
|
open: boolean;
|
|
110
110
|
startedAt: NativeDate;
|
|
111
111
|
alertCount: number;
|
|
@@ -117,8 +117,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
117
117
|
}, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
|
|
118
118
|
timestamps: true;
|
|
119
119
|
}> & Omit<{
|
|
120
|
-
user: mongoose.Types.ObjectId;
|
|
121
120
|
monitor: mongoose.Types.ObjectId;
|
|
121
|
+
user: mongoose.Types.ObjectId;
|
|
122
122
|
open: boolean;
|
|
123
123
|
startedAt: NativeDate;
|
|
124
124
|
alertCount: number;
|
|
@@ -132,8 +132,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
132
132
|
}, "id"> & mongoose.HydratedDocumentOverrides<{
|
|
133
133
|
id: string;
|
|
134
134
|
}>, unknown, {
|
|
135
|
-
user: mongoose.Types.ObjectId;
|
|
136
135
|
monitor: mongoose.Types.ObjectId;
|
|
136
|
+
user: mongoose.Types.ObjectId;
|
|
137
137
|
open: boolean;
|
|
138
138
|
startedAt: NativeDate;
|
|
139
139
|
alertCount: number;
|
|
@@ -147,8 +147,8 @@ export const MonitorEpisode: mongoose.Model<{
|
|
|
147
147
|
} & {
|
|
148
148
|
__v: number;
|
|
149
149
|
}>, {
|
|
150
|
-
user: mongoose.Types.ObjectId;
|
|
151
150
|
monitor: mongoose.Types.ObjectId;
|
|
151
|
+
user: mongoose.Types.ObjectId;
|
|
152
152
|
open: boolean;
|
|
153
153
|
startedAt: NativeDate;
|
|
154
154
|
alertCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"AA6DA,8BAiGE;AAiCF,wBAAmD;AAtLnD,iDAA4D;AAC5D,6CAAmD;AAUnD,4DAsBC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// V2 device-type vocabulary — single source of truth (JA-3110 / JA-3335).
|
|
2
|
+
//
|
|
3
|
+
// Every device a user can see or subscribe to is exactly one of three things,
|
|
4
|
+
// each backed by a different collection:
|
|
5
|
+
// monitor — a standalone Monitors doc (siteId: null). Reports live AQI.
|
|
6
|
+
// sample-site — a standalone SampleSites doc (siteId: null). Lab/filter
|
|
7
|
+
// sample data only, no live AQI, so it can never trigger an
|
|
8
|
+
// AQI alert.
|
|
9
|
+
// linked-device — a Sites doc joining one or more Monitors and/or one
|
|
10
|
+
// SampleSite at a single physical location. Reports live AQI
|
|
11
|
+
// when it links at least one monitor.
|
|
12
|
+
//
|
|
13
|
+
// Consumers: JustAirService fetchDevices/getDeviceById stamp these strings onto
|
|
14
|
+
// every device response; JustAirUserService validates the subscribe body
|
|
15
|
+
// against them and persists them in Users.subscribedDevices; JustAir-Client
|
|
16
|
+
// passes back verbatim the value it received. Keep the string values
|
|
17
|
+
// byte-identical to what the devices API emits — the FE does no mapping, and a
|
|
18
|
+
// casing drift here is a silent 404 on subscribe.
|
|
19
|
+
//
|
|
20
|
+
// DO NOT rename these values: they are persisted in Users.subscribedDevices.
|
|
21
|
+
|
|
22
|
+
export const DEVICE_TYPES = {
|
|
23
|
+
MONITOR: "monitor",
|
|
24
|
+
SAMPLE_SITE: "sample-site",
|
|
25
|
+
LINKED_DEVICE: "linked-device",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const DEVICE_TYPE_VALUES = Object.values(DEVICE_TYPES);
|
|
29
|
+
|
|
30
|
+
// Whether a device of this type can ever report AQI, and therefore whether a
|
|
31
|
+
// subscription to it can contribute to AQI alert delivery.
|
|
32
|
+
//
|
|
33
|
+
// Note the asymmetry: `sample-site` is unconditionally false, but a true result
|
|
34
|
+
// for `linked-device` is necessary and NOT sufficient — a Site is only actually
|
|
35
|
+
// AQI-capable when it links at least one monitor, which the type alone cannot
|
|
36
|
+
// tell you. Callers must still check the Site's monitorIds.
|
|
37
|
+
export const isAqiCapableDeviceType = (deviceType) =>
|
|
38
|
+
deviceType === DEVICE_TYPES.MONITOR ||
|
|
39
|
+
deviceType === DEVICE_TYPES.LINKED_DEVICE;
|
package/src/constants/index.js
CHANGED
|
@@ -151,6 +151,10 @@ export const PARAMETERS = {
|
|
|
151
151
|
origin: null,
|
|
152
152
|
impacts: null,
|
|
153
153
|
},
|
|
154
|
+
// ---- T/RH/DP and their _AMB pairs (JA-3058). Does not apply to WD/WS below. ----
|
|
155
|
+
// Bare ids (T/RH/DP) are enclosure-internal, for corrections and fog detection only. The _AMB
|
|
156
|
+
// ids are true ambient and the only ones safe for public display or Heat Index. Never substitute
|
|
157
|
+
// one for the other. See "Ambient vs internal met parameters" in CLAUDE.md before wiring up.
|
|
154
158
|
DP: {
|
|
155
159
|
id: "DP",
|
|
156
160
|
label: "Dew Point",
|
|
@@ -161,6 +165,16 @@ export const PARAMETERS = {
|
|
|
161
165
|
origin: null,
|
|
162
166
|
impacts: null,
|
|
163
167
|
},
|
|
168
|
+
DP_AMB: {
|
|
169
|
+
id: "DP_AMB",
|
|
170
|
+
label: "Ambient Dew Point",
|
|
171
|
+
name: "Ambient Dew Point (DP_AMB)",
|
|
172
|
+
unit: "°F",
|
|
173
|
+
type: "Weather",
|
|
174
|
+
rfc: null,
|
|
175
|
+
origin: null,
|
|
176
|
+
impacts: null,
|
|
177
|
+
},
|
|
164
178
|
RH: {
|
|
165
179
|
id: "RH",
|
|
166
180
|
label: "Humidity",
|
|
@@ -171,6 +185,16 @@ export const PARAMETERS = {
|
|
|
171
185
|
origin: null,
|
|
172
186
|
impacts: null,
|
|
173
187
|
},
|
|
188
|
+
RH_AMB: {
|
|
189
|
+
id: "RH_AMB",
|
|
190
|
+
label: "Ambient Humidity",
|
|
191
|
+
name: "Ambient Relative Humidity (RH_AMB)",
|
|
192
|
+
unit: "%",
|
|
193
|
+
type: "Weather",
|
|
194
|
+
rfc: null,
|
|
195
|
+
origin: null,
|
|
196
|
+
impacts: null,
|
|
197
|
+
},
|
|
174
198
|
T: {
|
|
175
199
|
id: "T",
|
|
176
200
|
label: "Temperature",
|
|
@@ -181,6 +205,16 @@ export const PARAMETERS = {
|
|
|
181
205
|
origin: null,
|
|
182
206
|
impacts: null,
|
|
183
207
|
},
|
|
208
|
+
T_AMB: {
|
|
209
|
+
id: "T_AMB",
|
|
210
|
+
label: "Ambient Temperature",
|
|
211
|
+
name: "Ambient Temperature (T_AMB)",
|
|
212
|
+
unit: "°F",
|
|
213
|
+
type: "Weather",
|
|
214
|
+
rfc: null,
|
|
215
|
+
origin: null,
|
|
216
|
+
impacts: null,
|
|
217
|
+
},
|
|
184
218
|
WD: {
|
|
185
219
|
id: "WD",
|
|
186
220
|
label: "Wind Direction",
|
|
@@ -9,7 +9,7 @@ import { ALERT_LEVEL_WEIGHTS, getAlertWeight } from '../alertLevels.js';
|
|
|
9
9
|
import { DEFAULT_TIMEZONE } from '../timezones.js';
|
|
10
10
|
import { DATA_DOWNLOAD_TYPES } from '../dataDownloads.js';
|
|
11
11
|
|
|
12
|
-
test('barrel re-exports
|
|
12
|
+
test('barrel re-exports the pollutants constants unchanged (identity, not copies)', () => {
|
|
13
13
|
expect(barrel.PARAMETERS).toBe(PARAMETERS);
|
|
14
14
|
expect(barrel.HEAVY_METALS).toBe(HEAVY_METALS);
|
|
15
15
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Pins the device-type vocabulary shared across JustAirService (which stamps
|
|
2
|
+
// deviceType onto every device response), JustAirUserService (which validates
|
|
3
|
+
// the subscribe body and persists it), and JustAir-Client (which passes it
|
|
4
|
+
// back verbatim). A casing drift in any value is a silent subscribe failure,
|
|
5
|
+
// so the exact strings are asserted rather than derived.
|
|
6
|
+
import * as barrel from '../index.js';
|
|
7
|
+
import {
|
|
8
|
+
DEVICE_TYPES,
|
|
9
|
+
DEVICE_TYPE_VALUES,
|
|
10
|
+
isAqiCapableDeviceType,
|
|
11
|
+
} from '../deviceTypes.js';
|
|
12
|
+
|
|
13
|
+
test('barrel exports the device-type constants', () => {
|
|
14
|
+
expect(barrel.DEVICE_TYPES).toBe(DEVICE_TYPES);
|
|
15
|
+
expect(barrel.DEVICE_TYPE_VALUES).toBe(DEVICE_TYPE_VALUES);
|
|
16
|
+
expect(barrel.isAqiCapableDeviceType).toBe(isAqiCapableDeviceType);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('DEVICE_TYPES values match the strings the devices API emits', () => {
|
|
20
|
+
expect(DEVICE_TYPES).toEqual({
|
|
21
|
+
MONITOR: 'monitor',
|
|
22
|
+
SAMPLE_SITE: 'sample-site',
|
|
23
|
+
LINKED_DEVICE: 'linked-device',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('DEVICE_TYPE_VALUES covers every type exactly once', () => {
|
|
28
|
+
expect(DEVICE_TYPE_VALUES).toEqual(['monitor', 'sample-site', 'linked-device']);
|
|
29
|
+
expect(new Set(DEVICE_TYPE_VALUES).size).toBe(DEVICE_TYPE_VALUES.length);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('isAqiCapableDeviceType', () => {
|
|
33
|
+
test('monitor and linked-device can report AQI', () => {
|
|
34
|
+
expect(isAqiCapableDeviceType('monitor')).toBe(true);
|
|
35
|
+
expect(isAqiCapableDeviceType('linked-device')).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('sample-site never reports AQI, so it never alerts', () => {
|
|
39
|
+
expect(isAqiCapableDeviceType('sample-site')).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('unknown and missing types are not AQI-capable', () => {
|
|
43
|
+
expect(isAqiCapableDeviceType('sampleSite')).toBe(false);
|
|
44
|
+
expect(isAqiCapableDeviceType(undefined)).toBe(false);
|
|
45
|
+
expect(isAqiCapableDeviceType(null)).toBe(false);
|
|
46
|
+
expect(isAqiCapableDeviceType('')).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -69,6 +69,20 @@ function sharedAssertions(constants) {
|
|
|
69
69
|
|
|
70
70
|
describe("PARAMETERS", () => {
|
|
71
71
|
sharedAssertions(PARAMETERS);
|
|
72
|
+
|
|
73
|
+
// JA-3060: Processing-Engine hardcodes these ids, so lock the ambient/internal pairing.
|
|
74
|
+
const ambientKeys = Object.keys(PARAMETERS).filter((key) => key.endsWith("_AMB"));
|
|
75
|
+
|
|
76
|
+
test("the _AMB parameter set is exactly T, RH and DP", () => {
|
|
77
|
+
expect([...ambientKeys].sort()).toEqual(["DP_AMB", "RH_AMB", "T_AMB"]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test.each(ambientKeys)("%s pairs with its bare parameter on unit and type", (key) => {
|
|
81
|
+
const bare = PARAMETERS[key.replace(/_AMB$/, "")];
|
|
82
|
+
expect(bare).toBeDefined();
|
|
83
|
+
expect(PARAMETERS[key].unit).toBe(bare.unit);
|
|
84
|
+
expect(PARAMETERS[key].type).toBe(bare.type);
|
|
85
|
+
});
|
|
72
86
|
});
|
|
73
87
|
|
|
74
88
|
describe("HEAVY_METALS", () => {
|
package/src/index.js
CHANGED
|
@@ -69,6 +69,11 @@ import { PARAMETERS, HEAVY_METALS } from "./constants/pollutants.js";
|
|
|
69
69
|
import { ALERT_LEVEL_WEIGHTS, getAlertWeight } from "./constants/alertLevels.js";
|
|
70
70
|
import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
|
|
71
71
|
import { DATA_DOWNLOAD_TYPES } from "./constants/dataDownloads.js";
|
|
72
|
+
import {
|
|
73
|
+
DEVICE_TYPES,
|
|
74
|
+
DEVICE_TYPE_VALUES,
|
|
75
|
+
isAqiCapableDeviceType,
|
|
76
|
+
} from "./constants/deviceTypes.js";
|
|
72
77
|
import {
|
|
73
78
|
monitorEpisodesSchema,
|
|
74
79
|
MonitorEpisode,
|
|
@@ -259,6 +264,10 @@ export {
|
|
|
259
264
|
// AQI alert-level hierarchy (single source of truth)
|
|
260
265
|
ALERT_LEVEL_WEIGHTS,
|
|
261
266
|
getAlertWeight,
|
|
267
|
+
// V2 device-type vocabulary (single source of truth) — JA-3110 / JA-3335
|
|
268
|
+
DEVICE_TYPES,
|
|
269
|
+
DEVICE_TYPE_VALUES,
|
|
270
|
+
isAqiCapableDeviceType,
|
|
262
271
|
// Utilities
|
|
263
272
|
splitName,
|
|
264
273
|
// Quiet hours (JA-3054): shared validation + window evaluation
|
package/src/models/users.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
validateQuietHours,
|
|
6
6
|
QUIET_HOURS_ERRORS,
|
|
7
7
|
} from "../utils/quietHours.js";
|
|
8
|
+
import { DEVICE_TYPE_VALUES } from "../constants/deviceTypes.js";
|
|
8
9
|
|
|
9
10
|
const COOKIE_CONSENT_CATEGORIES = ["analytics_performance"];
|
|
10
11
|
const COOKIE_CONSENT_VALUES = ["accept", "reject"];
|
|
@@ -41,6 +42,23 @@ const validateCookieConsent = (consent) => {
|
|
|
41
42
|
return null;
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
// One entry in Users.subscribedDevices (JA-3110 / JA-3335). deviceId points
|
|
46
|
+
// into a different collection per deviceType (Monitors / SampleSites / Sites),
|
|
47
|
+
// so there is deliberately no `ref` to declare here.
|
|
48
|
+
//
|
|
49
|
+
// `_id: false` is load-bearing, not cosmetic: it makes the pair itself the
|
|
50
|
+
// identity of an element, so `$addToSet` dedupes and re-subscribing is
|
|
51
|
+
// idempotent. With per-subdocument _ids every push would cast to a brand-new
|
|
52
|
+
// object, `$addToSet` would never match, and the array would accumulate a
|
|
53
|
+
// duplicate on every click of the subscribe button.
|
|
54
|
+
const subscribedDeviceSchema = mongoose.Schema(
|
|
55
|
+
{
|
|
56
|
+
deviceId: { type: mongoose.Types.ObjectId, required: true },
|
|
57
|
+
deviceType: { type: String, enum: DEVICE_TYPE_VALUES, required: true },
|
|
58
|
+
},
|
|
59
|
+
{ _id: false }
|
|
60
|
+
);
|
|
61
|
+
|
|
44
62
|
const usersSchema = mongoose.Schema(
|
|
45
63
|
{
|
|
46
64
|
name: String,
|
|
@@ -54,6 +72,22 @@ const usersSchema = mongoose.Schema(
|
|
|
54
72
|
alertPhone: String,
|
|
55
73
|
alertCount: { type: Number, default: 0 },
|
|
56
74
|
subscribedMonitors: [{ type: mongoose.Types.ObjectId, ref: "Monitors" }],
|
|
75
|
+
// Subscribed devices (JA-3110 / JA-3335) — the source of truth for what the
|
|
76
|
+
// user has added to "My Monitors", across all three device types.
|
|
77
|
+
//
|
|
78
|
+
// Relationship to subscribedMonitors above: that array stays monitor ids
|
|
79
|
+
// ONLY, and is the AQI-alert projection of this one. A subscribed
|
|
80
|
+
// linked-device contributes the monitorIds of its Site to it; a subscribed
|
|
81
|
+
// sample-site contributes nothing, having no live AQI. The two must be
|
|
82
|
+
// written together on every subscribe/unsubscribe path.
|
|
83
|
+
//
|
|
84
|
+
// Why the split matters: JustAirNotificationService reads subscribedMonitors
|
|
85
|
+
// and drops ids that do not resolve to a Monitor (statusAlerts.js
|
|
86
|
+
// `.filter(monitor => monitor)`). Writing a Site or SampleSite id in there
|
|
87
|
+
// therefore throws no error — it silently delivers no alerts. Keeping the
|
|
88
|
+
// polymorphic ids in their own field preserves that invariant for every
|
|
89
|
+
// library consumer.
|
|
90
|
+
subscribedDevices: [subscribedDeviceSchema],
|
|
57
91
|
currentAlertMode: {
|
|
58
92
|
type: String,
|
|
59
93
|
enum: [
|
|
@@ -151,6 +185,9 @@ usersSchema.pre("validate", function () {
|
|
|
151
185
|
});
|
|
152
186
|
|
|
153
187
|
usersSchema.index({ phone: 1 });
|
|
188
|
+
// Supports the "is this user subscribed to device X?" lookup behind the V2
|
|
189
|
+
// device-card subscribed state and the My Monitors map filter.
|
|
190
|
+
usersSchema.index({ "subscribedDevices.deviceId": 1 });
|
|
154
191
|
|
|
155
192
|
const Users = mongoose.model("Users", usersSchema);
|
|
156
193
|
|