@justair/justair-library 7.4.0-alpha.5a16ace → 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 +1 -1
- 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 +0 -6
- package/dist/constants/pollutants.d.ts.map +1 -1
- 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 -3
- 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 +1 -22
- package/src/constants/tests/constantsBarrel.test.js +1 -2
- package/src/constants/tests/deviceTypes.test.js +48 -0
- package/src/constants/tests/pollutants.test.js +2 -10
- package/src/index.js +10 -8
- package/src/models/users.js +37 -0
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ The following parameters were added to `PARAMETERS` and are now included in `par
|
|
|
107
107
|
| `RH_AMB` | Ambient Relative Humidity | `%` |
|
|
108
108
|
| `DP_AMB` | Ambient Dew Point | `°F` |
|
|
109
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.
|
|
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
111
|
|
|
112
112
|
### Object shape
|
|
113
113
|
|
|
@@ -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"}
|
|
@@ -568,12 +568,6 @@ export namespace PARAMETERS {
|
|
|
568
568
|
export { impacts_31 as impacts };
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
|
-
export const MET_INTERNAL_TO_AMBIENT_PARAMETERS: Readonly<{
|
|
572
|
-
T: "T_AMB";
|
|
573
|
-
RH: "RH_AMB";
|
|
574
|
-
DP: "DP_AMB";
|
|
575
|
-
}>;
|
|
576
|
-
export const MET_INTERNAL_PARAMETERS_TO_HIDE: readonly string[];
|
|
577
571
|
export namespace HEAVY_METALS {
|
|
578
572
|
export namespace AS {
|
|
579
573
|
let id_32: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pollutants.d.ts","sourceRoot":"","sources":["../../src/constants/pollutants.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pollutants.d.ts","sourceRoot":"","sources":["../../src/constants/pollutants.js"],"names":[],"mappings":""}
|
|
@@ -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
|
@@ -101,10 +101,11 @@ import { validateDownloadTimezone as validateDataDownloadTimezone } from "./util
|
|
|
101
101
|
import { normalizeDownloadTimezone as normalizeDataDownloadTimezone } from "./utils/dataDownloadValidation.js";
|
|
102
102
|
import { PARAMETERS } from "./constants/pollutants.js";
|
|
103
103
|
import { HEAVY_METALS } from "./constants/pollutants.js";
|
|
104
|
-
import { MET_INTERNAL_TO_AMBIENT_PARAMETERS } from "./constants/pollutants.js";
|
|
105
|
-
import { MET_INTERNAL_PARAMETERS_TO_HIDE } from "./constants/pollutants.js";
|
|
106
104
|
import { ALERT_LEVEL_WEIGHTS } from "./constants/alertLevels.js";
|
|
107
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";
|
|
108
109
|
import { splitName } from "./utils/splitName.js";
|
|
109
110
|
import { DEFAULT_QUIET_HOURS } from "./utils/quietHours.js";
|
|
110
111
|
import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
|
|
@@ -115,5 +116,5 @@ import { validateQuietHours } from "./utils/quietHours.js";
|
|
|
115
116
|
import { getZoneMinutes } from "./utils/quietHours.js";
|
|
116
117
|
import { isInQuietWindow } from "./utils/quietHours.js";
|
|
117
118
|
import { resolveQuietHours } from "./utils/quietHours.js";
|
|
118
|
-
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,
|
|
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 };
|
|
119
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":"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;
|
|
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
|
@@ -154,9 +154,7 @@ export const PARAMETERS = {
|
|
|
154
154
|
// ---- T/RH/DP and their _AMB pairs (JA-3058). Does not apply to WD/WS below. ----
|
|
155
155
|
// Bare ids (T/RH/DP) are enclosure-internal, for corrections and fog detection only. The _AMB
|
|
156
156
|
// ids are true ambient and the only ones safe for public display or Heat Index. Never substitute
|
|
157
|
-
// one for the other.
|
|
158
|
-
// exempt from the public-display filter until JA-3061 re-points them. See "Ambient vs internal
|
|
159
|
-
// met parameters" in CLAUDE.md before wiring up.
|
|
157
|
+
// one for the other. See "Ambient vs internal met parameters" in CLAUDE.md before wiring up.
|
|
160
158
|
DP: {
|
|
161
159
|
id: "DP",
|
|
162
160
|
label: "Dew Point",
|
|
@@ -329,25 +327,6 @@ export const PARAMETERS = {
|
|
|
329
327
|
},
|
|
330
328
|
};
|
|
331
329
|
|
|
332
|
-
// Enclosure-internal parameter id -> its ambient counterpart (JA-3058). Lets consumers map
|
|
333
|
-
// between the two kinds on data instead of hardcoding the six met ids. Kept in sync with
|
|
334
|
-
// PARAMETERS by a test in tests/pollutants.test.js.
|
|
335
|
-
//
|
|
336
|
-
// This is a pairing map, not a hide-list. For the hide-list use
|
|
337
|
-
// MET_INTERNAL_PARAMETERS_TO_HIDE below, which leaves DP out on purpose.
|
|
338
|
-
export const MET_INTERNAL_TO_AMBIENT_PARAMETERS = Object.freeze({
|
|
339
|
-
T: "T_AMB",
|
|
340
|
-
RH: "RH_AMB",
|
|
341
|
-
DP: "DP_AMB",
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// The internal met ids that are safe to hide from public surfaces today. DP is deliberately
|
|
345
|
-
// absent: five suppliers (Kunak, QuantAQ, Davis, HOBOlink, Aeroqual) still write outdoor dew
|
|
346
|
-
// point into DP and nothing writes DP_AMB yet, so hiding DP now would drop dew point from those
|
|
347
|
-
// dashboards entirely. DP joins this list in JA-3061. See "Ambient vs internal met parameters"
|
|
348
|
-
// in CLAUDE.md.
|
|
349
|
-
export const MET_INTERNAL_PARAMETERS_TO_HIDE = Object.freeze(["T", "RH"]);
|
|
350
|
-
|
|
351
330
|
// Parameters analyzed from physical air samples in a laboratory.
|
|
352
331
|
// These correspond to SampleSites.parameters and are not measured by real-time sensors.
|
|
353
332
|
export const HEAVY_METALS = {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// importing PARAMETERS / HEAVY_METALS through the barrel must see the exact
|
|
5
5
|
// same objects as before.
|
|
6
6
|
import * as barrel from '../index.js';
|
|
7
|
-
import { PARAMETERS, HEAVY_METALS
|
|
7
|
+
import { PARAMETERS, HEAVY_METALS } from '../pollutants.js';
|
|
8
8
|
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';
|
|
@@ -12,7 +12,6 @@ import { DATA_DOWNLOAD_TYPES } from '../dataDownloads.js';
|
|
|
12
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
|
-
expect(barrel.MET_INTERNAL_TO_AMBIENT_PARAMETERS).toBe(MET_INTERNAL_TO_AMBIENT_PARAMETERS);
|
|
16
15
|
});
|
|
17
16
|
|
|
18
17
|
test('barrel exports the alert-level constants', () => {
|
|
@@ -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
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PARAMETERS, HEAVY_METALS
|
|
1
|
+
import { PARAMETERS, HEAVY_METALS } from "../pollutants.js";
|
|
2
2
|
|
|
3
3
|
const REQUIRED_FIELDS = ["id", "label", "name", "unit", "type"];
|
|
4
4
|
const VALID_TYPES = [
|
|
@@ -74,15 +74,7 @@ describe("PARAMETERS", () => {
|
|
|
74
74
|
const ambientKeys = Object.keys(PARAMETERS).filter((key) => key.endsWith("_AMB"));
|
|
75
75
|
|
|
76
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("MET_INTERNAL_TO_AMBIENT_PARAMETERS matches the _AMB ids actually defined", () => {
|
|
81
|
-
expect(Object.values(MET_INTERNAL_TO_AMBIENT_PARAMETERS).sort()).toEqual(ambientKeys.sort());
|
|
82
|
-
Object.entries(MET_INTERNAL_TO_AMBIENT_PARAMETERS).forEach(([bare, ambient]) => {
|
|
83
|
-
expect(PARAMETERS[bare]).toBeDefined();
|
|
84
|
-
expect(ambient).toBe(`${bare}_AMB`);
|
|
85
|
-
});
|
|
77
|
+
expect([...ambientKeys].sort()).toEqual(["DP_AMB", "RH_AMB", "T_AMB"]);
|
|
86
78
|
});
|
|
87
79
|
|
|
88
80
|
test.each(ambientKeys)("%s pairs with its bare parameter on unit and type", (key) => {
|
package/src/index.js
CHANGED
|
@@ -65,15 +65,15 @@ import {
|
|
|
65
65
|
} from "./models/changeStreamCheckpoints.js";
|
|
66
66
|
import Database from "./config/db.js"; // Import the new Database class
|
|
67
67
|
import CustomLogger from "./config/logger.js";
|
|
68
|
-
import {
|
|
69
|
-
PARAMETERS,
|
|
70
|
-
HEAVY_METALS,
|
|
71
|
-
MET_INTERNAL_TO_AMBIENT_PARAMETERS,
|
|
72
|
-
MET_INTERNAL_PARAMETERS_TO_HIDE,
|
|
73
|
-
} from "./constants/pollutants.js";
|
|
68
|
+
import { PARAMETERS, HEAVY_METALS } from "./constants/pollutants.js";
|
|
74
69
|
import { ALERT_LEVEL_WEIGHTS, getAlertWeight } from "./constants/alertLevels.js";
|
|
75
70
|
import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
|
|
76
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";
|
|
77
77
|
import {
|
|
78
78
|
monitorEpisodesSchema,
|
|
79
79
|
MonitorEpisode,
|
|
@@ -261,11 +261,13 @@ export {
|
|
|
261
261
|
// Pollutant constants
|
|
262
262
|
PARAMETERS,
|
|
263
263
|
HEAVY_METALS,
|
|
264
|
-
MET_INTERNAL_TO_AMBIENT_PARAMETERS,
|
|
265
|
-
MET_INTERNAL_PARAMETERS_TO_HIDE,
|
|
266
264
|
// AQI alert-level hierarchy (single source of truth)
|
|
267
265
|
ALERT_LEVEL_WEIGHTS,
|
|
268
266
|
getAlertWeight,
|
|
267
|
+
// V2 device-type vocabulary (single source of truth) — JA-3110 / JA-3335
|
|
268
|
+
DEVICE_TYPES,
|
|
269
|
+
DEVICE_TYPE_VALUES,
|
|
270
|
+
isAqiCapableDeviceType,
|
|
269
271
|
// Utilities
|
|
270
272
|
splitName,
|
|
271
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
|
|