From f1f9c7773ab905893fea535f94d3b95acd5464f0 Mon Sep 17 00:00:00 2001 From: Julia Friesel <julia.friesel@gmail.com> Date: Thu, 5 Jul 2018 12:10:18 +0200 Subject: [PATCH] Expect cycle days, not numbers, in getTemperatureStatus --- lib/sympto/temperature.js | 160 +++++++++++++++----------------- test/sympto/temperature.spec.js | 106 +++++++++++++++++---- 2 files changed, 164 insertions(+), 102 deletions(-) diff --git a/lib/sympto/temperature.js b/lib/sympto/temperature.js index 86a83beb..4779bc0b 100644 --- a/lib/sympto/temperature.js +++ b/lib/sympto/temperature.js @@ -1,118 +1,106 @@ -export default function getTemperatureStatus(temperaturesOfCycle) { - // sensiplan rounds temps to the nearest 0.05 - const tempValues = temperaturesOfCycle.map(val => rounded(val, 0.05)) +export default function getTemperatureStatus(cycleDays) { + const temperatureDays = cycleDays + .filter(day => day.temperature && !day.temperature.exclude) + .map(day => { + return { + originalCycleDay: day, + temp: rounded(day.temperature.value, 0.05) + } + }) function getLtl(i) { - const sixTempsBefore = getSixTempsBefore(i) - return Math.max(...sixTempsBefore) - } - function getSixTempsBefore(i) { - return tempValues.slice(0, i).slice(-6) + const daysBefore = temperatureDays.slice(0, i).slice(-6) + const temps = daysBefore.map(day => day.temp) + return Math.max(...temps) } - 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 - if (i < 6) return acc - - // 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 - } + if (i < 6) continue // is the temp a candidate for a first high measurement? 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) - // if we don't have a winner, keep move on to the next candidates - if (!checkResult.isFirstHighMeasurement) return acc + const checkResult = checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl) - // if we do, remember the details and start collecting the high level temps - acc.detected = true - acc.high = [temp] - acc.rule = checkResult.rule - acc.ltl = ltl - acc.low = getSixTempsBefore(i) + if (checkResult.detected) { + checkResult.firstHighMeasurementDay = temperatureDays[i].originalCycleDay + return checkResult + } + } - return acc - }, { - detected: false - }) + return { detected: false } } -function rounded(val, step) { - // 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) { +function checkIfFirstHighMeasurement(temp, i, temperatureDays, ltl) { // need at least 3 high temps to form a high temperature level - if (i > temps.length - 3) { - return { isFirstHighMeasurement: false } - } - const nextTemps = temps.slice(i + 1, i + 4) - - if (regularRuleApplies(temp, nextTemps, ltl)) { - return { - isFirstHighMeasurement: true, - rule: 0, - ltl - } - } - - if (firstExceptionRuleApplies(temp, nextTemps, ltl)) { - return { - isFirstHighMeasurement: true, - rule: 1, - ltl - } + if (i > temperatureDays.length - 3) { + return { detected: false } } + const nextDays = temperatureDays.slice(i + 1, i + 4) - if (secondExceptionRuleApplies(temp, nextTemps, ltl)) { - return { - isFirstHighMeasurement: true, - rule: 2, - ltl - } - } + return ( + getResultForRegularRule(nextDays, ltl)) || + getResultForFirstExceptionRule(nextDays, ltl) || + getResultForSecondExceptionRule(nextDays, ltl) || + { detected: false } +} +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 { - isFirstHighMeasurement: false + detected: true, + rule: 0, + ltl, + evaluationCompleteDay: thirdDay.originalCycleDay } } -function regularRuleApplies(temp, nextTemps, ltl) { - if (!nextTemps.every(temp => temp > ltl)) return false - const thirdTemp = nextTemps[1] - if (rounded(thirdTemp - ltl, 0.1) < 0.2) return false - return true -} - -function firstExceptionRuleApplies(temp, nextTemps, ltl) { - if (nextTemps.length < 3) return false - if (!nextTemps.every(temp => temp > ltl)) return false - const fourthTemp = nextTemps[2] - if (fourthTemp > ltl) return true - return false +function getResultForFirstExceptionRule(nextDays, ltl) { + if (nextDays.length < 3) return false + if (!nextDays.every(day => day.temp > ltl)) return false + const fourthDay = nextDays[2] + if (fourthDay.temp <= ltl) return false + return { + detected: true, + rule: 1, + ltl, + evaluationCompleteDay: fourthDay.originalCycleDay + } } -function secondExceptionRuleApplies(temp, nextTemps, ltl) { - if (nextTemps.length < 3) return false - if (secondOrThirdTempIsAtOrBelowLtl(nextTemps, ltl)) { - const fourthTemp = nextTemps[2] - if (rounded(fourthTemp - ltl, 0.1) >= 0.2) return true +function getResultForSecondExceptionRule(nextDays, ltl) { + if (nextDays.length < 3) return false + if (secondOrThirdTempIsAtOrBelowLtl(nextDays, ltl)) { + const fourthDay = nextDays[2] + if (rounded(fourthDay.temp - ltl, 0.1) >= 0.2) { + return { + detected: true, + rule: 2, + ltl, + evaluationCompleteDay: fourthDay.originalCycleDay + } + } } return false } -function secondOrThirdTempIsAtOrBelowLtl(nextTemps, ltl) { - const secondIsLow = nextTemps[0] <= ltl - const thirdIsLow = nextTemps[1] <= ltl +function secondOrThirdTempIsAtOrBelowLtl(nextDays, ltl) { + const secondIsLow = nextDays[0].temp <= ltl + const thirdIsLow = nextDays[1].temp <= ltl if ((secondIsLow || thirdIsLow) && !(secondIsLow && thirdIsLow)) { return true } else { return false } -} \ No newline at end of file +} + +function rounded(val, step) { + const inverted = 1 / step + // we round the difference because of JS decimal weirdness + return Math.round(val * inverted) / inverted +} diff --git a/test/sympto/temperature.spec.js b/test/sympto/temperature.spec.js index 81060211..d5131b82 100644 --- a/test/sympto/temperature.spec.js +++ b/test/sympto/temperature.spec.js @@ -3,53 +3,78 @@ import getTemperatureStatus from '../../lib/sympto/temperature' const expect = chai.expect -describe.only('sympto', () => { +function turnIntoCycleDayObject(value, fakeDate) { + return { + temperature : { value }, + date: fakeDate + } +} + +describe('sympto', () => { describe('detect temperature shift', () => { describe('regular rule', () => { it('reports lower temperature status before shift', function () { const lowerTemps = [36.7, 36.57, 36.47, 36.49, 36.57] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(lowerTemps) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(tempShift) 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, + ltl: 36.6, + firstHighMeasurementDay: { + date: 7, + temperature: { value: 36.8 } + }, + evaluationCompleteDay: { + date: 9, + temperature: { value: 36.8 } + }, rule: 0 }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(tempShift) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(tempShift) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(noTempShift) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ - low: [36.4, 36.4, 36.6, 36.6, 36.4, 36.4], 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, rule: 0 }) @@ -57,6 +82,7 @@ describe.only('sympto', () => { 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ detected: false }) @@ -66,11 +92,19 @@ describe.only('sympto', () => { describe('1st exception rule', () => { 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(firstException) 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.75, 36.65], + firstHighMeasurementDay: { + date: 7, + temperature: { value: 36.8 } + }, + + evaluationCompleteDay: { + date: 10, + temperature : { value: 36.63 } + }, detected: true, rule: 1 }) @@ -78,38 +112,58 @@ describe.only('sympto', () => { 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(firstExceptionNoShift) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ detected: false }) + }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ - low: [36.4, 36.4, 36.6, 36.6, 36.4, 36.4], 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, rule: 1 }) }) + }) describe('2nd exception rule', () => { 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(secondException) 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.6, 36.8], + firstHighMeasurementDay: { + date: 7, + temperature: { value: 36.8 } + }, + + evaluationCompleteDay: { + date: 10, + temperature : { value: 36.8 } + }, detected: true, rule: 2 }) @@ -117,39 +171,59 @@ describe.only('sympto', () => { 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(secondException) 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.4, 36.8], + firstHighMeasurementDay: { + date: 7, + temperature: { value: 36.8 } + }, + + evaluationCompleteDay: { + date: 10, + temperature : { value: 36.8 } + }, detected: true, rule: 2 }) }) + 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ detected: false }) }) 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] + .map(turnIntoCycleDayObject) const status = getTemperatureStatus(temps) expect(status).to.eql({ - low: [36.6, 36.55, 36.8, 36.85, 36.4, 36.75], 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, rule: 2 }) }) + }) }) }) \ No newline at end of file -- GitLab