-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtemperature.js
122 lines (104 loc) · 3.61 KB
/
temperature.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
module.exports = function (cycleDays) {
const temperatureDays = cycleDays
.filter(day => typeof day.temperature === 'number')
.map(day => {
return {
originalCycleDay: day,
temp: rounded(day.temperature, 0.05)
}
})
function getLtl(i) {
const daysBefore = temperatureDays.slice(0, i).slice(-6)
const temps = daysBefore.map(day => day.temp)
return Math.max(...temps)
}
for (let i = 0; i < temperatureDays.length; i++) {
// need at least 6 low temps before we can detect a first high measurement
if (i < 6) continue
// is the temp a candidate for a first high measurement?
const ltl = getLtl(i)
const temp = temperatureDays[i].temp
if (temp <= ltl) continue
const shift = checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl)
if (shift.detected) {
shift.firstHighMeasurementDay = temperatureDays[i].originalCycleDay
return shift
}
}
return { detected: false }
}
function checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl) {
// need at least 3 high temps to form a high temperature level
if (i > temperatureDays.length - 3) {
return { detected: false }
}
const nextDaysAfterPotentialFhm = temperatureDays.slice(i + 1, i + 4)
return (
getResultForRegularRule(nextDaysAfterPotentialFhm.slice(0, 2), ltl)) ||
getResultForFirstExceptionRule(nextDaysAfterPotentialFhm, ltl) ||
getResultForSecondExceptionRule(nextDaysAfterPotentialFhm, ltl) ||
{ detected: false }
}
function getResultForRegularRule(nextDaysAfterPotentialFhm, ltl) {
if (!nextDaysAfterPotentialFhm.every(day => day.temp > ltl)) return false
const thirdDay = nextDaysAfterPotentialFhm[1]
if (isLessThan0Point2(thirdDay.temp - ltl)) return false
return {
detected: true,
rule: 0,
ltl,
evaluationCompleteDay: thirdDay.originalCycleDay
}
}
function getResultForFirstExceptionRule(nextDaysAfterPotentialFhm, ltl) {
if (nextDaysAfterPotentialFhm.length < 3) return false
if (!nextDaysAfterPotentialFhm.every(day => day.temp > ltl)) return false
const fourthDay = nextDaysAfterPotentialFhm[2]
if (fourthDay.temp <= ltl) return false
return {
detected: true,
rule: 1,
ltl,
evaluationCompleteDay: fourthDay.originalCycleDay
}
}
function getResultForSecondExceptionRule(nextDaysAfterPotentialFhm, ltl) {
if (nextDaysAfterPotentialFhm.length < 3) return false
if (secondOrThirdTempIsAtOrBelowLtl(nextDaysAfterPotentialFhm, ltl)) {
const fourthDay = nextDaysAfterPotentialFhm[2]
if (isBiggerOrEqual0Point2(fourthDay.temp - ltl)) {
return {
detected: true,
rule: 2,
ltl,
evaluationCompleteDay: fourthDay.originalCycleDay
}
}
}
return false
}
function secondOrThirdTempIsAtOrBelowLtl(nextDaysAfterPotentialFhm, ltl) {
const secondIsLow = nextDaysAfterPotentialFhm[0].temp <= ltl
const thirdIsLow = nextDaysAfterPotentialFhm[1].temp <= ltl
if ((secondIsLow || thirdIsLow) && !(secondIsLow && thirdIsLow)) {
return true
} else {
return false
}
}
function rounded(val, step) {
const inverted = 1 / step
// we round the difference because of JS decimal weirdness
return Math.round(val * inverted) / inverted
}
// since we're dealing with floats, there is some imprecision in comparisons,
// so we add an error margin that is definitely much smaller than any possible
// actual difference between values
// see https://floating-point-gui.de/errors/comparison/ for background
const errorMargin = 0.0001
function isLessThan0Point2(val) {
return val < (0.2 - errorMargin)
}
function isBiggerOrEqual0Point2(val) {
return val >= (0.2 - errorMargin)
}