Use new isCycleStartFunctions throughout cycle module
This commit is contained in:
+45
-94
@@ -5,6 +5,7 @@ const DAYS = joda.ChronoUnit.DAYS
|
||||
|
||||
export default function config(opts) {
|
||||
let bleedingDaysSortedByDate
|
||||
let cycleStartsSortedByDate
|
||||
let cycleDaysSortedByDate
|
||||
let maxBreakInBleeding
|
||||
let maxCycleLength
|
||||
@@ -14,57 +15,22 @@ export default function config(opts) {
|
||||
// we only want to require (and run) the db module
|
||||
// when not running the tests
|
||||
bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
|
||||
cycleStartsSortedByDate = require('../db').getCycleStartsSortedByDate()
|
||||
cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
|
||||
maxBreakInBleeding = 1
|
||||
maxCycleLength = 99
|
||||
minCyclesForPrediction = 3
|
||||
} else {
|
||||
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || []
|
||||
cycleStartsSortedByDate = opts.cycleStartsSortedByDate || []
|
||||
cycleDaysSortedByDate = opts.cycleDaysSortedByDate || []
|
||||
maxBreakInBleeding = opts.maxBreakInBleeding || 1
|
||||
maxCycleLength = opts.maxCycleLength || 99
|
||||
minCyclesForPrediction = opts.minCyclesForPrediction || 3
|
||||
}
|
||||
|
||||
function findLatestMensesStart(bleedingDays) {
|
||||
if (!bleedingDays.length) return null
|
||||
|
||||
// assumes bleeding days are ordered latest first, and
|
||||
// excluded values already removed
|
||||
const lastMensesStart = bleedingDays.find((day, i) => {
|
||||
return noBleedingDayWithinThreshold(day, bleedingDays.slice(i + 1))
|
||||
})
|
||||
|
||||
function noBleedingDayWithinThreshold(day, previousBleedingDays) {
|
||||
const localDate = LocalDate.parse(day.date)
|
||||
const threshold = localDate.minusDays(maxBreakInBleeding + 1).toString()
|
||||
return !previousBleedingDays.some(({ date }) => date >= threshold)
|
||||
}
|
||||
|
||||
return lastMensesStart
|
||||
}
|
||||
|
||||
function getLastMensesStartForDay(targetDateString) {
|
||||
// the index of the first bleeding day before the target day
|
||||
const index = bleedingDaysSortedByDate.findIndex(day => {
|
||||
return day.date <= targetDateString && !day.bleeding.exclude
|
||||
})
|
||||
|
||||
if (index < 0) return null
|
||||
|
||||
const prevBleedingDays = bleedingDaysSortedByDate.slice(index)
|
||||
return findLatestMensesStart(prevBleedingDays)
|
||||
}
|
||||
|
||||
function getFollowingMensesStartForDay(targetDateString) {
|
||||
const followingBleedingDays = bleedingDaysSortedByDate
|
||||
.filter(day => !day.bleeding.exclude)
|
||||
.reverse()
|
||||
|
||||
const firstBleedingDayAfterTargetDay = followingBleedingDays
|
||||
.find(day => day.date > targetDateString)
|
||||
|
||||
return firstBleedingDayAfterTargetDay
|
||||
return cycleStartsSortedByDate.find(start => start.date <= targetDateString)
|
||||
}
|
||||
|
||||
function getCycleDayNumber(targetDateString) {
|
||||
@@ -78,59 +44,44 @@ export default function config(opts) {
|
||||
return diffInDays + 1
|
||||
}
|
||||
|
||||
function getCyclesBefore(targetCycleStartDay) {
|
||||
return collectPreviousCycles([], targetCycleStartDay.date)
|
||||
}
|
||||
|
||||
function collectPreviousCycles(acc, startOfFollowingCycle) {
|
||||
const cycle = getPreviousCycle(startOfFollowingCycle)
|
||||
if (!cycle || !cycle.length) return acc
|
||||
acc.push(cycle)
|
||||
return collectPreviousCycles(acc, cycle[cycle.length - 1].date)
|
||||
}
|
||||
|
||||
function getPreviousCycle(dateString) {
|
||||
const startOfCycle = getLastMensesStartForDay(dateString)
|
||||
if (!startOfCycle) return null
|
||||
const dateBeforeStartOfCycle = LocalDate
|
||||
.parse(startOfCycle.date)
|
||||
.minusDays(1)
|
||||
.toString()
|
||||
|
||||
return getCycleForDay(dateBeforeStartOfCycle)
|
||||
}
|
||||
|
||||
function getCycleForDay(dayOrDate) {
|
||||
const dateString = typeof dayOrDate === 'string' ? dayOrDate : dayOrDate.date
|
||||
const cycleStart = getLastMensesStartForDay(dateString)
|
||||
if (!cycleStart) return null
|
||||
const cycleStartIndex = cycleDaysSortedByDate.indexOf(cycleStart)
|
||||
const nextMensesStart = getFollowingMensesStartForDay(dateString)
|
||||
const i = cycleDaysSortedByDate.indexOf(cycleStart)
|
||||
const earlierCycleStart = cycleDaysSortedByDate[i - 1]
|
||||
if (!earlierCycleStart) return null
|
||||
return getCycleForStartDay(earlierCycleStart)
|
||||
}
|
||||
|
||||
function getCyclesBefore(targetCycleStartDay) {
|
||||
const startFromHere = cycleStartsSortedByDate.findIndex(start => {
|
||||
return start.date < targetCycleStartDay.date
|
||||
})
|
||||
if (startFromHere < 0) return null
|
||||
return cycleStartsSortedByDate
|
||||
.slice(startFromHere)
|
||||
.map(getCycleForStartDay)
|
||||
}
|
||||
|
||||
function getCycleForStartDay(startDay) {
|
||||
const cycleStartIndex = cycleDaysSortedByDate.indexOf(startDay)
|
||||
const i = cycleStartsSortedByDate.indexOf(startDay)
|
||||
const nextMensesStart = cycleStartsSortedByDate[i - 1]
|
||||
if (nextMensesStart) {
|
||||
return cycleDaysSortedByDate.slice(
|
||||
cycleDaysSortedByDate.indexOf(nextMensesStart) + 1,
|
||||
cycleStartIndex + 1
|
||||
cycleStartIndex + 1,
|
||||
)
|
||||
} else {
|
||||
return cycleDaysSortedByDate.slice(0, cycleStartIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function getAllMensesStarts(initialBleedingDays = bleedingDaysSortedByDate) {
|
||||
return recurse(initialBleedingDays.filter(d => !d.bleeding.exclude))
|
||||
|
||||
function recurse(bleedingDays, collectedDates) {
|
||||
collectedDates = collectedDates || []
|
||||
const lastStart = findLatestMensesStart(bleedingDays)
|
||||
if (!lastStart) {
|
||||
return collectedDates
|
||||
} else {
|
||||
collectedDates.push(lastStart.date)
|
||||
const index = bleedingDays.indexOf(lastStart)
|
||||
const remainingDays = bleedingDays.slice(index + 1)
|
||||
return recurse(remainingDays, collectedDates)
|
||||
}
|
||||
}
|
||||
function getCycleForDay(dayOrDate) {
|
||||
const dateString = typeof dayOrDate === 'string' ? dayOrDate : dayOrDate.date
|
||||
const cycleStart = getLastMensesStartForDay(dateString)
|
||||
if (!cycleStart) return null
|
||||
return getCycleForStartDay(cycleStart)
|
||||
}
|
||||
|
||||
function isMensesStart(cycleDay) {
|
||||
@@ -174,32 +125,33 @@ export default function config(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCycleLength(cycleStartDates) {
|
||||
const cycleLengths = []
|
||||
for (let i = 0; i < cycleStartDates.length - 1; i++) {
|
||||
const nextCycleStart = LocalDate.parse(cycleStartDates[i])
|
||||
const cycleStart = LocalDate.parse(cycleStartDates[i + 1])
|
||||
const cycleLength = cycleStart.until(nextCycleStart, DAYS)
|
||||
if (cycleLength <= maxCycleLength) { cycleLengths.push(cycleLength) }
|
||||
}
|
||||
return cycleLengths
|
||||
function getAllCycleLengths() {
|
||||
return cycleStartsSortedByDate
|
||||
.map(day => LocalDate.parse(day.date))
|
||||
.reduce((lengths, cycleStart, i, startsAsLocalDates) => {
|
||||
if (i === startsAsLocalDates.length - 1) return lengths
|
||||
const prevCycleStart = startsAsLocalDates[i + 1]
|
||||
const cycleLength = prevCycleStart.until(cycleStart, DAYS)
|
||||
if (cycleLength <= maxCycleLength) { lengths.push(cycleLength) }
|
||||
return lengths
|
||||
}, [])
|
||||
}
|
||||
|
||||
function getPredictedMenses() {
|
||||
const allMensesStarts = getAllMensesStarts()
|
||||
const allMensesStarts = cycleStartsSortedByDate
|
||||
const atLeastOneCycle = allMensesStarts.length > 1
|
||||
if (!atLeastOneCycle ||
|
||||
allMensesStarts.length < minCyclesForPrediction
|
||||
) {
|
||||
return []
|
||||
}
|
||||
const cycleLengths = getCycleLength(allMensesStarts)
|
||||
const cycleLengths = getAllCycleLengths()
|
||||
const cycleInfo = getCycleLengthStats(cycleLengths)
|
||||
const periodDistance = Math.round(cycleInfo.mean)
|
||||
let periodStartVariation
|
||||
if (cycleInfo.stdDeviation === null) {
|
||||
periodStartVariation = 2
|
||||
} else if (cycleInfo.stdDeviation < 1.5) { // threshold is choosen a little arbitrarily
|
||||
} else if (cycleInfo.stdDeviation < 1.5) { // threshold is chosen a little arbitrarily
|
||||
periodStartVariation = 1
|
||||
} else {
|
||||
periodStartVariation = 2
|
||||
@@ -207,7 +159,7 @@ export default function config(opts) {
|
||||
if (periodDistance - 5 < periodStartVariation) { // otherwise predictions overlap
|
||||
return []
|
||||
}
|
||||
let lastStart = LocalDate.parse(allMensesStarts[0])
|
||||
let lastStart = LocalDate.parse(allMensesStarts[0].date)
|
||||
const predictedMenses = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
lastStart = lastStart.plusDays(periodDistance)
|
||||
@@ -227,8 +179,7 @@ export default function config(opts) {
|
||||
getCycleForDay,
|
||||
getPreviousCycle,
|
||||
getCyclesBefore,
|
||||
getAllMensesStarts,
|
||||
getCycleLength,
|
||||
getAllCycleLengths,
|
||||
getPredictedMenses,
|
||||
isMensesStart,
|
||||
getMensesDaysAfter
|
||||
|
||||
Reference in New Issue
Block a user