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