Expect cycle days, not numbers, in getTemperatureStatus

This commit is contained in:
Julia Friesel
2018-07-05 12:10:18 +02:00
parent 537b8c7647
commit f1f9c7773a
2 changed files with 164 additions and 102 deletions
+74 -86
View File
@@ -1,118 +1,106 @@
export default function getTemperatureStatus(temperaturesOfCycle) { export default function getTemperatureStatus(cycleDays) {
// sensiplan rounds temps to the nearest 0.05 const temperatureDays = cycleDays
const tempValues = temperaturesOfCycle.map(val => rounded(val, 0.05)) .filter(day => day.temperature && !day.temperature.exclude)
.map(day => {
return {
originalCycleDay: day,
temp: rounded(day.temperature.value, 0.05)
}
})
function getLtl(i) { function getLtl(i) {
const sixTempsBefore = getSixTempsBefore(i) const daysBefore = temperatureDays.slice(0, i).slice(-6)
return Math.max(...sixTempsBefore) const temps = daysBefore.map(day => day.temp)
} return Math.max(...temps)
function getSixTempsBefore(i) {
return tempValues.slice(0, i).slice(-6)
} }
return tempValues.reduce((acc, temp, i) => { for (let i = 0; i < temperatureDays.length; i++) {
// need at least 6 low temps before we can detect a first high measurement // need at least 6 low temps before we can detect a first high measurement
if (i < 6) return acc if (i < 6) continue
// if we've already detected a shift, we put it with the other high level temps
if(acc.detected) {
acc.high.push(temp)
return acc
}
// is the temp a candidate for a first high measurement? // is the temp a candidate for a first high measurement?
const ltl = getLtl(i) const ltl = getLtl(i)
if (temp <= ltl) return acc const temp = temperatureDays[i].temp
if (temp <= ltl) continue
const checkResult = checkIfFirstHighMeasurement(temp, i, tempValues, ltl) const checkResult = checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl)
// if we don't have a winner, keep move on to the next candidates
if (!checkResult.isFirstHighMeasurement) return acc
// if we do, remember the details and start collecting the high level temps if (checkResult.detected) {
acc.detected = true checkResult.firstHighMeasurementDay = temperatureDays[i].originalCycleDay
acc.high = [temp] return checkResult
acc.rule = checkResult.rule }
acc.ltl = ltl }
acc.low = getSixTempsBefore(i)
return acc return { detected: false }
}, {
detected: false
})
} }
function rounded(val, step) { function checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl) {
// we round the difference because of JS decimal weirdness
const inverted = 1 / step
return Math.round(val * inverted) / inverted
}
function checkIfFirstHighMeasurement(temp, i, temps, ltl) {
// need at least 3 high temps to form a high temperature level // need at least 3 high temps to form a high temperature level
if (i > temps.length - 3) { if (i > temperatureDays.length - 3) {
return { isFirstHighMeasurement: false } return { detected: false }
} }
const nextTemps = temps.slice(i + 1, i + 4) const nextDays = temperatureDays.slice(i + 1, i + 4)
if (regularRuleApplies(temp, nextTemps, ltl)) { return (
return { getResultForRegularRule(nextDays, ltl)) ||
isFirstHighMeasurement: true, getResultForFirstExceptionRule(nextDays, ltl) ||
rule: 0, getResultForSecondExceptionRule(nextDays, ltl) ||
ltl { detected: false }
} }
}
if (firstExceptionRuleApplies(temp, nextTemps, ltl)) {
return {
isFirstHighMeasurement: true,
rule: 1,
ltl
}
}
if (secondExceptionRuleApplies(temp, nextTemps, ltl)) {
return {
isFirstHighMeasurement: true,
rule: 2,
ltl
}
}
function getResultForRegularRule(nextDays, ltl) {
if (!nextDays.every(day => day.temp > ltl)) return false
const thirdDay = nextDays[1]
if (rounded(thirdDay.temp - ltl, 0.1) < 0.2) return false
return { return {
isFirstHighMeasurement: false detected: true,
rule: 0,
ltl,
evaluationCompleteDay: thirdDay.originalCycleDay
} }
} }
function regularRuleApplies(temp, nextTemps, ltl) { function getResultForFirstExceptionRule(nextDays, ltl) {
if (!nextTemps.every(temp => temp > ltl)) return false if (nextDays.length < 3) return false
const thirdTemp = nextTemps[1] if (!nextDays.every(day => day.temp > ltl)) return false
if (rounded(thirdTemp - ltl, 0.1) < 0.2) return false const fourthDay = nextDays[2]
return true if (fourthDay.temp <= ltl) return false
return {
detected: true,
rule: 1,
ltl,
evaluationCompleteDay: fourthDay.originalCycleDay
}
} }
function firstExceptionRuleApplies(temp, nextTemps, ltl) { function getResultForSecondExceptionRule(nextDays, ltl) {
if (nextTemps.length < 3) return false if (nextDays.length < 3) return false
if (!nextTemps.every(temp => temp > ltl)) return false if (secondOrThirdTempIsAtOrBelowLtl(nextDays, ltl)) {
const fourthTemp = nextTemps[2] const fourthDay = nextDays[2]
if (fourthTemp > ltl) return true if (rounded(fourthDay.temp - ltl, 0.1) >= 0.2) {
return false return {
} detected: true,
rule: 2,
function secondExceptionRuleApplies(temp, nextTemps, ltl) { ltl,
if (nextTemps.length < 3) return false evaluationCompleteDay: fourthDay.originalCycleDay
if (secondOrThirdTempIsAtOrBelowLtl(nextTemps, ltl)) { }
const fourthTemp = nextTemps[2] }
if (rounded(fourthTemp - ltl, 0.1) >= 0.2) return true
} }
return false return false
} }
function secondOrThirdTempIsAtOrBelowLtl(nextTemps, ltl) { function secondOrThirdTempIsAtOrBelowLtl(nextDays, ltl) {
const secondIsLow = nextTemps[0] <= ltl const secondIsLow = nextDays[0].temp <= ltl
const thirdIsLow = nextTemps[1] <= ltl const thirdIsLow = nextDays[1].temp <= ltl
if ((secondIsLow || thirdIsLow) && !(secondIsLow && thirdIsLow)) { if ((secondIsLow || thirdIsLow) && !(secondIsLow && thirdIsLow)) {
return true return true
} else { } else {
return false 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
}
+90 -16
View File
@@ -3,53 +3,78 @@ import getTemperatureStatus from '../../lib/sympto/temperature'
const expect = chai.expect const expect = chai.expect
describe.only('sympto', () => { function turnIntoCycleDayObject(value, fakeDate) {
return {
temperature : { value },
date: fakeDate
}
}
describe('sympto', () => {
describe('detect temperature shift', () => { describe('detect temperature shift', () => {
describe('regular rule', () => { describe('regular rule', () => {
it('reports lower temperature status before shift', function () { it('reports lower temperature status before shift', function () {
const lowerTemps = [36.7, 36.57, 36.47, 36.49, 36.57] const lowerTemps = [36.7, 36.57, 36.47, 36.49, 36.57]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(lowerTemps) const status = getTemperatureStatus(lowerTemps)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects temperature shift correctly', function () { it('detects temperature shift correctly', function () {
const tempShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8] const tempShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(tempShift) const status = getTemperatureStatus(tempShift)
expect(status).to.eql({ expect(status).to.eql({
low: [36.55, 36.45, 36.5, 36.55, 36.6, 36.55],
ltl: 36.6,
high: [36.8, 36.85, 36.8],
detected: true, detected: true,
ltl: 36.6,
firstHighMeasurementDay: {
date: 7,
temperature: { value: 36.8 }
},
evaluationCompleteDay: {
date: 9,
temperature: { value: 36.8 }
},
rule: 0 rule: 0
}) })
}) })
it('detects no temperature shift when there are no 6 low temps', function () { it('detects no temperature shift when there are no 6 low temps', function () {
const tempShift = [36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8] const tempShift = [36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(tempShift) const status = getTemperatureStatus(tempShift)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects no temperature shift if the shift is not high enough', function () { it('detects no temperature shift if the shift is not high enough', function () {
const tempShift = [36.57, 36.7, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8] const tempShift = [36.57, 36.7, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.8]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(tempShift) const status = getTemperatureStatus(tempShift)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects missing temperature shift correctly', function () { it('detects missing temperature shift correctly', function () {
const noTempShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77] const noTempShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(noTempShift) const status = getTemperatureStatus(noTempShift)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects shift after an earlier one was invalid', function () { it('detects shift after an earlier one was invalid', function () {
const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.7, 36.8, 36.9] const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.7, 36.8, 36.9]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ expect(status).to.eql({
low: [36.4, 36.4, 36.6, 36.6, 36.4, 36.4],
ltl: 36.6, ltl: 36.6,
high: [36.7, 36.8, 36.9], firstHighMeasurementDay: {
date: 10,
temperature: { value: 36.7 }
},
evaluationCompleteDay: {
date: 12,
temperature: { value: 36.9 }
},
detected: true, detected: true,
rule: 0 rule: 0
}) })
@@ -57,6 +82,7 @@ describe.only('sympto', () => {
it('detects 2 consecutive invalid shifts', function () { it('detects 2 consecutive invalid shifts', function () {
const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.6, 36.6, 36.7] const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.6, 36.6, 36.7]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
@@ -66,11 +92,19 @@ describe.only('sympto', () => {
describe('1st exception rule', () => { describe('1st exception rule', () => {
it('detects temperature shift', function () { it('detects temperature shift', function () {
const firstException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77, 36.63] const firstException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77, 36.63]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(firstException) const status = getTemperatureStatus(firstException)
expect(status).to.eql({ expect(status).to.eql({
low: [36.55, 36.45, 36.5, 36.55, 36.6, 36.55],
ltl: 36.6, ltl: 36.6,
high: [36.8, 36.85, 36.75, 36.65], firstHighMeasurementDay: {
date: 7,
temperature: { value: 36.8 }
},
evaluationCompleteDay: {
date: 10,
temperature : { value: 36.63 }
},
detected: true, detected: true,
rule: 1 rule: 1
}) })
@@ -78,38 +112,58 @@ describe.only('sympto', () => {
it('detects missing temperature shift correctly', function () { it('detects missing temperature shift correctly', function () {
const firstExceptionNoShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77, 36.57] const firstExceptionNoShift = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77, 36.57]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(firstExceptionNoShift) const status = getTemperatureStatus(firstExceptionNoShift)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects missing temperature shift with not enough high temps', function () { it('detects missing temperature shift with not enough high temps', function () {
const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77] const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.77]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects shift after an earlier one was invalid', function () { it('detects shift after an earlier one was invalid', function () {
const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.7, 36.7, 36.7, 36.7] const temps = [36.4, 36.4, 36.4, 36.4, 36.4, 36.4, 36.6, 36.6, 36.4, 36.4, 36.7, 36.7, 36.7, 36.7]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ expect(status).to.eql({
low: [36.4, 36.4, 36.6, 36.6, 36.4, 36.4],
ltl: 36.6, ltl: 36.6,
high: [36.7, 36.7, 36.7, 36.7], firstHighMeasurementDay: {
date: 10,
temperature: { value: 36.7 }
},
evaluationCompleteDay: {
date: 13,
temperature : { value: 36.7 }
},
detected: true, detected: true,
rule: 1 rule: 1
}) })
}) })
}) })
describe('2nd exception rule', () => { describe('2nd exception rule', () => {
it('detects temperature shift with exception temp eql ltl', function () { it('detects temperature shift with exception temp eql ltl', function () {
const secondException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.6, 36.8] const secondException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.6, 36.8]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(secondException) const status = getTemperatureStatus(secondException)
expect(status).to.eql({ expect(status).to.eql({
low: [36.55, 36.45, 36.5, 36.55, 36.6, 36.55],
ltl: 36.6, ltl: 36.6,
high: [36.8, 36.85, 36.6, 36.8], firstHighMeasurementDay: {
date: 7,
temperature: { value: 36.8 }
},
evaluationCompleteDay: {
date: 10,
temperature : { value: 36.8 }
},
detected: true, detected: true,
rule: 2 rule: 2
}) })
@@ -117,39 +171,59 @@ describe.only('sympto', () => {
it('detects temperature shift with exception temp lower than ltl', function () { it('detects temperature shift with exception temp lower than ltl', function () {
const secondException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.8] const secondException = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.8]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(secondException) const status = getTemperatureStatus(secondException)
expect(status).to.eql({ expect(status).to.eql({
low: [36.55, 36.45, 36.5, 36.55, 36.6, 36.55],
ltl: 36.6, ltl: 36.6,
high: [36.8, 36.85, 36.4, 36.8], firstHighMeasurementDay: {
date: 7,
temperature: { value: 36.8 }
},
evaluationCompleteDay: {
date: 10,
temperature : { value: 36.8 }
},
detected: true, detected: true,
rule: 2 rule: 2
}) })
}) })
it('detects missing temperature shift correctly', function () { it('detects missing temperature shift correctly', function () {
const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.77, 36.77] const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.77, 36.77]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects missing temperature shift when not enough high temps', function () { it('detects missing temperature shift when not enough high temps', function () {
const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4] const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ detected: false }) expect(status).to.eql({ detected: false })
}) })
it('detects shift after an earlier one was invalid', function () { it('detects shift after an earlier one was invalid', function () {
const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.77, 36.9, 36.9, 36.86, 37.04] const temps = [36.7, 36.57, 36.47, 36.49, 36.57, 36.62, 36.55, 36.8, 36.86, 36.4, 36.77, 36.9, 36.9, 36.86, 37.04]
.map(turnIntoCycleDayObject)
const status = getTemperatureStatus(temps) const status = getTemperatureStatus(temps)
expect(status).to.eql({ expect(status).to.eql({
low: [36.6, 36.55, 36.8, 36.85, 36.4, 36.75],
ltl: 36.85, ltl: 36.85,
high: [36.9, 36.9, 36.85, 37.05], firstHighMeasurementDay: {
date: 11,
temperature: { value: 36.9 }
},
evaluationCompleteDay: {
date: 14,
temperature : { value: 37.04 }
},
detected: true, detected: true,
rule: 2 rule: 2
}) })
}) })
}) })
}) })
}) })