hoffmation-base 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/action/index.d.ts +1 -0
- package/lib/action/index.js +3 -1
- package/lib/action/soilSensorChangeAction.d.ts +16 -0
- package/lib/action/soilSensorChangeAction.js +15 -0
- package/lib/devices/device-cluster.js +7 -0
- package/lib/devices/devices.js +3 -0
- package/lib/devices/sharedFunctions/index.d.ts +1 -0
- package/lib/devices/sharedFunctions/index.js +1 -0
- package/lib/devices/sharedFunctions/soilSensor.d.ts +23 -0
- package/lib/devices/sharedFunctions/soilSensor.js +58 -0
- package/lib/devices/zigbee/BaseDevices/index.d.ts +1 -0
- package/lib/devices/zigbee/BaseDevices/index.js +1 -0
- package/lib/devices/zigbee/BaseDevices/zigbeeSoilSensor.d.ts +33 -0
- package/lib/devices/zigbee/BaseDevices/zigbeeSoilSensor.js +62 -0
- package/lib/devices/zigbee/index.d.ts +1 -0
- package/lib/devices/zigbee/index.js +1 -0
- package/lib/devices/zigbee/zigbeeCooloSoilSensor.d.ts +44 -0
- package/lib/devices/zigbee/zigbeeCooloSoilSensor.js +81 -0
- package/lib/enums/DeviceCapability.d.ts +1 -0
- package/lib/enums/DeviceCapability.js +1 -0
- package/lib/enums/commandType.d.ts +1 -0
- package/lib/enums/commandType.js +1 -0
- package/lib/enums/device-cluster-type.d.ts +2 -1
- package/lib/enums/device-cluster-type.js +1 -0
- package/lib/enums/deviceType.d.ts +1 -0
- package/lib/enums/deviceType.js +1 -0
- package/lib/interfaces/baseDevices/iSoilCollector.d.ts +23 -0
- package/lib/interfaces/baseDevices/iSoilCollector.js +2 -0
- package/lib/interfaces/baseDevices/iSoilSensor.d.ts +32 -0
- package/lib/interfaces/baseDevices/iSoilSensor.js +2 -0
- package/lib/interfaces/baseDevices/index.d.ts +3 -0
- package/lib/interfaces/baseDevices/index.js +3 -0
- package/lib/interfaces/baseDevices/undefinedSoilMoistureValue.d.ts +6 -0
- package/lib/interfaces/baseDevices/undefinedSoilMoistureValue.js +9 -0
- package/lib/interfaces/iPersist.d.ts +18 -1
- package/lib/interfaces/iSoilMoistureSample.d.ts +13 -0
- package/lib/interfaces/iSoilMoistureSample.js +2 -0
- package/lib/interfaces/iWeatherDaySummary.d.ts +12 -0
- package/lib/interfaces/index.d.ts +1 -0
- package/lib/interfaces/index.js +1 -0
- package/lib/services/dbo/postgreSqlPersist.d.ts +5 -1
- package/lib/services/dbo/postgreSqlPersist.js +102 -7
- package/lib/services/dbo/soil-moisture-row.d.ts +14 -0
- package/lib/services/dbo/soil-moisture-row.js +2 -0
- package/lib/services/dbo/weather-day-summary-row.d.ts +2 -0
- package/lib/services/weather/open-weather-day-summary.d.ts +4 -0
- package/lib/services/weather/weather-history-backfill.d.ts +6 -0
- package/lib/services/weather/weather-history-backfill.js +21 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { iAcDevice, iActuator, iAirQualityCollector, iBaseDevice, iBatteryDevice, iButtonSwitch, iHandle, iHeater, iHumidityCollector, iIlluminationSensor, iMotionSensor, iShutter, iTemperatureCollector, iZigbeeDevice } from './baseDevices';
|
|
1
|
+
import { iAcDevice, iActuator, iAirQualityCollector, iBaseDevice, iBatteryDevice, iButtonSwitch, iHandle, iHeater, iHumidityCollector, iIlluminationSensor, iMotionSensor, iShutter, iSoilCollector, iTemperatureCollector, iZigbeeDevice } from './baseDevices';
|
|
2
|
+
import { iSoilMoistureSample } from './iSoilMoistureSample';
|
|
2
3
|
import { iTemperatureMeasurement } from './iTemperatureMeasurement';
|
|
3
4
|
import { iRoomBase } from './iRoomBase';
|
|
4
5
|
import { ButtonPressType } from '../enums';
|
|
@@ -169,6 +170,22 @@ export interface iPersist {
|
|
|
169
170
|
* @param device - The device to persist data for
|
|
170
171
|
*/
|
|
171
172
|
persistAirQualitySensor(device: iAirQualityCollector): void;
|
|
173
|
+
/**
|
|
174
|
+
* Persists data of a soil sensor
|
|
175
|
+
* @param device - The device to persist data for
|
|
176
|
+
*/
|
|
177
|
+
persistSoilSensor(device: iSoilCollector): void;
|
|
178
|
+
/**
|
|
179
|
+
* Gets the recorded soil moisture readings of one sensor within the given window, in percent.
|
|
180
|
+
* An absent, unreachable or empty persistence is a defined state, not a failure: the answer is then an
|
|
181
|
+
* empty list. Readings without a usable value are dropped rather than replaced by a substitute value - a
|
|
182
|
+
* missing reading completed with 0 would read as bone dry soil and could ask for water that is not needed.
|
|
183
|
+
* @param deviceId - The ID of the device to load the readings for
|
|
184
|
+
* @param startDate - Start of the window (inclusive)
|
|
185
|
+
* @param endDate - End of the window (inclusive)
|
|
186
|
+
* @returns - The readings, newest first
|
|
187
|
+
*/
|
|
188
|
+
getSoilMoistureHistory(deviceId: string, startDate: Date, endDate: Date): Promise<iSoilMoistureSample[]>;
|
|
172
189
|
/**
|
|
173
190
|
* Persists data of a handle sensor
|
|
174
191
|
* @param device - The device to persist data for
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One recorded soil moisture reading in the persistence layer.
|
|
3
|
+
*/
|
|
4
|
+
export interface iSoilMoistureSample {
|
|
5
|
+
/**
|
|
6
|
+
* The measured soil moisture in percent
|
|
7
|
+
*/
|
|
8
|
+
soilMoisture: number;
|
|
9
|
+
/**
|
|
10
|
+
* The date of the measurement
|
|
11
|
+
*/
|
|
12
|
+
date: Date;
|
|
13
|
+
}
|
|
@@ -18,4 +18,16 @@ export interface iWeatherDaySummary {
|
|
|
18
18
|
* The day's maximum temperature in degrees celsius
|
|
19
19
|
*/
|
|
20
20
|
tempMax: number;
|
|
21
|
+
/**
|
|
22
|
+
* Total precipitation of the day in millimetres, or `undefined` when the day carries no reading for it.
|
|
23
|
+
*
|
|
24
|
+
* Optional on purpose, in two directions. Towards implementers: this aggregate is produced by whoever calls
|
|
25
|
+
* `persistWeatherDaySummary`, and a required field would stop existing code from compiling for a quantity
|
|
26
|
+
* the previous three consumers never asked for. Towards the record itself: the three fields above decide
|
|
27
|
+
* whether a day counts at all - a day whose cloud cover is missing is discarded, because a substitute would
|
|
28
|
+
* be fitted as if measured. Precipitation must not join that rule. A day without it is still a perfectly
|
|
29
|
+
* good day for the decisions that read cloud cover and temperature, and discarding it would tear holes into
|
|
30
|
+
* a history those decisions already depend on.
|
|
31
|
+
*/
|
|
32
|
+
precipitation?: number;
|
|
21
33
|
}
|
|
@@ -58,6 +58,7 @@ export * from './iProjectedSocBand';
|
|
|
58
58
|
export * from './iMorningReserveVerdict';
|
|
59
59
|
export * from './iDachsHistoryGateResult';
|
|
60
60
|
export * from './iBatteryLevelSample';
|
|
61
|
+
export * from './iSoilMoistureSample';
|
|
61
62
|
export * from './iActuatorStateSample';
|
|
62
63
|
export * from './iWeatherDaySummary';
|
|
63
64
|
export { iShutterCalibration } from './iShutterCalibration';
|
package/lib/interfaces/index.js
CHANGED
|
@@ -71,5 +71,6 @@ __exportStar(require("./iProjectedSocBand"), exports);
|
|
|
71
71
|
__exportStar(require("./iMorningReserveVerdict"), exports);
|
|
72
72
|
__exportStar(require("./iDachsHistoryGateResult"), exports);
|
|
73
73
|
__exportStar(require("./iBatteryLevelSample"), exports);
|
|
74
|
+
__exportStar(require("./iSoilMoistureSample"), exports);
|
|
74
75
|
__exportStar(require("./iActuatorStateSample"), exports);
|
|
75
76
|
__exportStar(require("./iWeatherDaySummary"), exports);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PoolConfig } from 'pg';
|
|
2
|
-
import { iAcDevice, iActuator, iActuatorStateSample, iBaseDevice, iBatteryDevice, iBatteryLevelSample, iButtonSwitch, iConsumptionWindowSample, iDesiredShutterPosition, iHandle, iHeater, iAirQualityCollector, iHumidityCollector, iIlluminationSensor, iMotionSensor, iPersist, iRoomBase, iShutter, iShutterCalibration, iTemperatureCollector, iTemperatureMeasurement, iWeatherDaySummary, iZigbeeDevice } from '../../interfaces';
|
|
2
|
+
import { iAcDevice, iActuator, iActuatorStateSample, iBaseDevice, iBatteryDevice, iBatteryLevelSample, iButtonSwitch, iConsumptionWindowSample, iDesiredShutterPosition, iHandle, iHeater, iAirQualityCollector, iHumidityCollector, iIlluminationSensor, iMotionSensor, iPersist, iRoomBase, iShutter, iShutterCalibration, iSoilCollector, iSoilMoistureSample, iTemperatureCollector, iTemperatureMeasurement, iWeatherDaySummary, iZigbeeDevice } from '../../interfaces';
|
|
3
3
|
import { CountToday, EnergyCalculation } from '../../models';
|
|
4
4
|
import { ButtonPressType } from '../../enums';
|
|
5
5
|
export declare class PostgreSqlPersist implements iPersist {
|
|
@@ -54,6 +54,10 @@ export declare class PostgreSqlPersist implements iPersist {
|
|
|
54
54
|
/** @inheritDoc */
|
|
55
55
|
persistAirQualitySensor(device: iAirQualityCollector): void;
|
|
56
56
|
/** @inheritDoc */
|
|
57
|
+
persistSoilSensor(device: iSoilCollector): void;
|
|
58
|
+
/** @inheritDoc */
|
|
59
|
+
getSoilMoistureHistory(deviceId: string, startDate: Date, endDate: Date): Promise<iSoilMoistureSample[]>;
|
|
60
|
+
/** @inheritDoc */
|
|
57
61
|
persistBatteryDevice(device: iBatteryDevice): void;
|
|
58
62
|
/** @inheritDoc */
|
|
59
63
|
persistZigbeeDevice(device: iZigbeeDevice): void;
|
|
@@ -246,7 +246,7 @@ class PostgreSqlPersist {
|
|
|
246
246
|
}
|
|
247
247
|
/** @inheritDoc */
|
|
248
248
|
async getWeatherDaySummaries(startDate, endDate) {
|
|
249
|
-
const dbResult = await this.query(`SELECT date, "cloudCover", "tempMin", "tempMax"
|
|
249
|
+
const dbResult = await this.query(`SELECT date, "cloudCover", "tempMin", "tempMax", "precipitation"
|
|
250
250
|
from hoffmation_schema."WeatherDaySummary"
|
|
251
251
|
WHERE date >= '${startDate.toISOString()}'
|
|
252
252
|
AND date <= '${endDate.toISOString()}'
|
|
@@ -268,13 +268,24 @@ class PostgreSqlPersist {
|
|
|
268
268
|
dropped++;
|
|
269
269
|
continue;
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
// Read after the completeness check and deliberately outside it, mirroring the write path: every row
|
|
272
|
+
// stored before this column existed carries a null here, and none of them may be dropped for it. An
|
|
273
|
+
// absent value stays absent rather than becoming a 0, which would read as a dry day.
|
|
274
|
+
const precipitation = PostgreSqlPersist.toFiniteNumber(entry.precipitation);
|
|
275
|
+
result.push({
|
|
276
|
+
date: date,
|
|
277
|
+
cloudCover: cloudCover,
|
|
278
|
+
tempMin: tempMin,
|
|
279
|
+
tempMax: tempMax,
|
|
280
|
+
precipitation: precipitation,
|
|
281
|
+
});
|
|
272
282
|
}
|
|
273
283
|
PostgreSqlPersist.logDroppedRows('getWeatherDaySummaries', dropped);
|
|
274
284
|
return result;
|
|
275
285
|
}
|
|
276
286
|
/** @inheritDoc */
|
|
277
287
|
persistWeatherDaySummary(summary) {
|
|
288
|
+
var _a;
|
|
278
289
|
// The values are bound, not written into the statement. Every other write in this file carries figures
|
|
279
290
|
// this process produced itself; these come from the weather service, and they are read back out of this
|
|
280
291
|
// table by a decision that switches an appliance. The fetcher already refuses what is not a number, but
|
|
@@ -282,15 +293,26 @@ class PostgreSqlPersist {
|
|
|
282
293
|
// aggregate is data. The update half binds the same three placeholders: it is the one an interpolation
|
|
283
294
|
// is most easily left behind in.
|
|
284
295
|
this.query(`
|
|
285
|
-
insert into hoffmation_schema."WeatherDaySummary" ("date", "cloudCover", "tempMin", "tempMax")
|
|
286
|
-
values ($1, $2, $3, $4) ON CONFLICT ("date")
|
|
296
|
+
insert into hoffmation_schema."WeatherDaySummary" ("date", "cloudCover", "tempMin", "tempMax", "precipitation")
|
|
297
|
+
values ($1, $2, $3, $4, $5) ON CONFLICT ("date")
|
|
287
298
|
DO
|
|
288
299
|
UPDATE SET
|
|
289
300
|
"cloudCover" = $2,
|
|
290
301
|
"tempMin" = $3,
|
|
291
|
-
"tempMax" = $4
|
|
302
|
+
"tempMax" = $4,
|
|
303
|
+
"precipitation" = COALESCE($5, hoffmation_schema."WeatherDaySummary"."precipitation")
|
|
292
304
|
;
|
|
293
|
-
`, [
|
|
305
|
+
`, [
|
|
306
|
+
summary.date.toISOString(),
|
|
307
|
+
summary.cloudCover,
|
|
308
|
+
summary.tempMin,
|
|
309
|
+
summary.tempMax,
|
|
310
|
+
// Undefined is bound as null on purpose - the column has to be able to say "not recorded", and a 0
|
|
311
|
+
// would say "it did not rain". The three columns above are overwritten unconditionally because the
|
|
312
|
+
// running day's forecast moves; this one keeps what is already stored when nothing new arrived, so a
|
|
313
|
+
// refetch that comes back without the field cannot erase a figure an earlier one delivered.
|
|
314
|
+
(_a = summary.precipitation) !== null && _a !== void 0 ? _a : null,
|
|
315
|
+
]);
|
|
294
316
|
}
|
|
295
317
|
/** @inheritDoc */
|
|
296
318
|
async initialize() {
|
|
@@ -506,6 +528,21 @@ BEGIN
|
|
|
506
528
|
|
|
507
529
|
END IF;
|
|
508
530
|
|
|
531
|
+
IF (SELECT to_regclass('hoffmation_schema."SoilSensorDeviceData"') IS NULL) Then
|
|
532
|
+
-- Deliberately without a foreign key on "DeviceInfo", for the same reason as the air quality table above:
|
|
533
|
+
-- creating one needs the REFERENCES privilege on that table, which an existing installation whose tables
|
|
534
|
+
-- were created by another role may not grant.
|
|
535
|
+
create table if not exists hoffmation_schema."SoilSensorDeviceData"
|
|
536
|
+
(
|
|
537
|
+
"deviceID" varchar(60) not null,
|
|
538
|
+
"soilMoisture" double precision,
|
|
539
|
+
date timestamp not null,
|
|
540
|
+
constraint soilsensordevicedata_pk
|
|
541
|
+
primary key ("deviceID", date)
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
END IF;
|
|
545
|
+
|
|
509
546
|
IF (SELECT to_regclass('hoffmation_schema."BatteryDeviceData"') IS NULL) Then
|
|
510
547
|
create table if not exists hoffmation_schema."BatteryDeviceData"
|
|
511
548
|
(
|
|
@@ -578,11 +615,22 @@ BEGIN
|
|
|
578
615
|
primary key,
|
|
579
616
|
"cloudCover" double precision,
|
|
580
617
|
"tempMin" double precision,
|
|
581
|
-
"tempMax" double precision
|
|
618
|
+
"tempMax" double precision,
|
|
619
|
+
"precipitation" double precision
|
|
582
620
|
);
|
|
583
621
|
|
|
584
622
|
END IF;
|
|
585
623
|
|
|
624
|
+
IF (SELECT COUNT(column_name) = 0
|
|
625
|
+
FROM information_schema.columns
|
|
626
|
+
WHERE table_name = 'WeatherDaySummary'
|
|
627
|
+
and column_name = 'precipitation') Then
|
|
628
|
+
-- The table predates this column, so an existing installation gets it added rather than created. Existing
|
|
629
|
+
-- rows keep a null, which is the honest answer: nothing was recorded for those days.
|
|
630
|
+
alter table hoffmation_schema."WeatherDaySummary"
|
|
631
|
+
add "precipitation" double precision;
|
|
632
|
+
END IF;
|
|
633
|
+
|
|
586
634
|
IF (SELECT COUNT(column_name) = 0
|
|
587
635
|
FROM information_schema.columns
|
|
588
636
|
WHERE table_name = 'EnergyCalculation'
|
|
@@ -715,6 +763,53 @@ $$;`);
|
|
|
715
763
|
`);
|
|
716
764
|
}
|
|
717
765
|
/** @inheritDoc */
|
|
766
|
+
persistSoilSensor(device) {
|
|
767
|
+
this.query(`
|
|
768
|
+
insert into hoffmation_schema."SoilSensorDeviceData" ("deviceID", "soilMoisture", "date")
|
|
769
|
+
values ('${device.id}', ${device.soilMoisture}, '${new Date().toISOString()}');
|
|
770
|
+
`);
|
|
771
|
+
}
|
|
772
|
+
/** @inheritDoc */
|
|
773
|
+
async getSoilMoistureHistory(deviceId, startDate, endDate) {
|
|
774
|
+
// The device id is bound rather than pasted in: it is a string this reader is handed, and a string inside
|
|
775
|
+
// quotes can close them. The dates travel the same way for consistency - bound as ISO text, which is what
|
|
776
|
+
// the naive column stores and hence the same comparison the statement makes with a literal.
|
|
777
|
+
const dbResult = await this.query(`SELECT "soilMoisture", date
|
|
778
|
+
from hoffmation_schema."SoilSensorDeviceData"
|
|
779
|
+
WHERE "deviceID" = $1
|
|
780
|
+
and date >= $2
|
|
781
|
+
AND date <= $3
|
|
782
|
+
ORDER BY DATE DESC`, [deviceId, startDate.toISOString(), endDate.toISOString()]);
|
|
783
|
+
if (dbResult === null || dbResult.length === 0) {
|
|
784
|
+
PostgreSqlPersist.logEmptyAnswer('getSoilMoistureHistory', dbResult, startDate, endDate);
|
|
785
|
+
return [];
|
|
786
|
+
}
|
|
787
|
+
const result = [];
|
|
788
|
+
let dropped = 0;
|
|
789
|
+
for (const entry of dbResult) {
|
|
790
|
+
// An absent value read as 0 would read as bone dry soil - the one reading that makes a watering
|
|
791
|
+
// decision act. A genuine 0 % survives; only an absent or unreadable one is dropped.
|
|
792
|
+
const soilMoisture = PostgreSqlPersist.toFiniteNumber(entry.soilMoisture);
|
|
793
|
+
// A reading without a timestamp cannot be placed: read as a Date an absent one lands on 1970-01-01,
|
|
794
|
+
// which looks like a reading at the far edge of the window rather than like a missing one.
|
|
795
|
+
const date = PostgreSqlPersist.fromNaiveTimestamp(entry.date);
|
|
796
|
+
if (soilMoisture === undefined || date === undefined) {
|
|
797
|
+
dropped++;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
// Outside 0..100 this is not a soil moisture. The sentinel the sensor carries before its first reading
|
|
801
|
+
// is -1, and the write path already refuses to store it - this is the second line of defence, for rows
|
|
802
|
+
// an older version may have written.
|
|
803
|
+
if (soilMoisture < 0 || soilMoisture > 100) {
|
|
804
|
+
dropped++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
result.push({ soilMoisture: soilMoisture, date: date });
|
|
808
|
+
}
|
|
809
|
+
PostgreSqlPersist.logDroppedRows('getSoilMoistureHistory', dropped);
|
|
810
|
+
return result;
|
|
811
|
+
}
|
|
812
|
+
/** @inheritDoc */
|
|
718
813
|
persistBatteryDevice(device) {
|
|
719
814
|
this.query(`
|
|
720
815
|
insert into hoffmation_schema."BatteryDeviceData" ("deviceID", "battery", "date")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The raw shape the driver hands back for one stored soil moisture reading.
|
|
3
|
+
*
|
|
4
|
+
* Same conversion rules as {@link BatteryLevelRow}: the numeric column may arrive as a string, and the date is
|
|
5
|
+
* a Date built from naive components because the column carries no zone.
|
|
6
|
+
*
|
|
7
|
+
* Not part of the published surface - a database row shape is not an interface anyone implements.
|
|
8
|
+
*/
|
|
9
|
+
export type SoilMoistureRow = {
|
|
10
|
+
/** The stored soil moisture in percent, as the driver hands it back */
|
|
11
|
+
soilMoisture: string | number | null;
|
|
12
|
+
/** When the reading was taken, built from naive components */
|
|
13
|
+
date: Date | null;
|
|
14
|
+
};
|
|
@@ -15,4 +15,6 @@ export type WeatherDaySummaryRow = {
|
|
|
15
15
|
tempMin: string | number | null;
|
|
16
16
|
/** Highest air temperature of that day in degrees celsius */
|
|
17
17
|
tempMax: string | number | null;
|
|
18
|
+
/** Total precipitation of that day in millimetres. Null for every row written before the column existed */
|
|
19
|
+
precipitation: string | number | null;
|
|
18
20
|
};
|
|
@@ -18,6 +18,12 @@ export declare class WeatherHistoryBackfill {
|
|
|
18
18
|
*/
|
|
19
19
|
private static readonly minTemperature;
|
|
20
20
|
private static readonly maxTemperature;
|
|
21
|
+
/**
|
|
22
|
+
* The most millimetres of precipitation a single day can carry. Wide on purpose, like the temperature band:
|
|
23
|
+
* it rejects what cannot be a daily rainfall at all, it does not second guess the weather service on a
|
|
24
|
+
* thunderstorm. The bound sits above the highest daily total ever recorded on the planet.
|
|
25
|
+
*/
|
|
26
|
+
private static readonly maxPrecipitation;
|
|
21
27
|
private static readonly host;
|
|
22
28
|
/**
|
|
23
29
|
* The past days of the window that were already fetched on the running day, and the running day that was
|
|
@@ -155,7 +155,7 @@ class WeatherHistoryBackfill {
|
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
static parseDaySummary(response, statusCode, date, day) {
|
|
158
|
-
var _a, _b, _c;
|
|
158
|
+
var _a, _b, _c, _d;
|
|
159
159
|
if (statusCode !== 200) {
|
|
160
160
|
// Neither the answer nor the request is logged: both carry the key and the location.
|
|
161
161
|
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, `WeatherHistoryBackfill: day summary for ${day} answered ${statusCode}`);
|
|
@@ -174,9 +174,22 @@ class WeatherHistoryBackfill {
|
|
|
174
174
|
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, `WeatherHistoryBackfill: incomplete day summary for ${day}`);
|
|
175
175
|
return undefined;
|
|
176
176
|
}
|
|
177
|
+
// Read AFTER the completeness check and deliberately outside it. The three fields above decide whether the
|
|
178
|
+
// day counts; precipitation must never be able to discard one. An endpoint that does not send the field,
|
|
179
|
+
// or a dry day it chooses to omit, would otherwise tear a hole into the history the start decision reads -
|
|
180
|
+
// a defect that would only show up as a gradually thinning window, weeks later. Absent stays absent all
|
|
181
|
+
// the way into the column; it is not filled with a 0, which would be an ordinary reading rather than a
|
|
182
|
+
// visibly missing one.
|
|
183
|
+
const precipitation = WeatherHistoryBackfill.plausibleReading((_d = parsed === null || parsed === void 0 ? void 0 : parsed.precipitation) === null || _d === void 0 ? void 0 : _d.total, 0, WeatherHistoryBackfill.maxPrecipitation);
|
|
177
184
|
const dayStart = new Date(date);
|
|
178
185
|
dayStart.setHours(0, 0, 0, 0);
|
|
179
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
date: dayStart,
|
|
188
|
+
cloudCover: cloudCover,
|
|
189
|
+
tempMin: tempMin,
|
|
190
|
+
tempMax: tempMax,
|
|
191
|
+
precipitation: precipitation,
|
|
192
|
+
};
|
|
180
193
|
}
|
|
181
194
|
/**
|
|
182
195
|
* Establishes one field of the answer as a reading: a real number within the band its quantity can occupy.
|
|
@@ -212,6 +225,12 @@ WeatherHistoryBackfill.defaultThrottleMs = 1500;
|
|
|
212
225
|
*/
|
|
213
226
|
WeatherHistoryBackfill.minTemperature = -95;
|
|
214
227
|
WeatherHistoryBackfill.maxTemperature = 60;
|
|
228
|
+
/**
|
|
229
|
+
* The most millimetres of precipitation a single day can carry. Wide on purpose, like the temperature band:
|
|
230
|
+
* it rejects what cannot be a daily rainfall at all, it does not second guess the weather service on a
|
|
231
|
+
* thunderstorm. The bound sits above the highest daily total ever recorded on the planet.
|
|
232
|
+
*/
|
|
233
|
+
WeatherHistoryBackfill.maxPrecipitation = 2000;
|
|
215
234
|
WeatherHistoryBackfill.host = 'api.openweathermap.org';
|
|
216
235
|
/**
|
|
217
236
|
* The past days of the window that were already fetched on the running day, and the running day that was
|