moves getCycleLength to corresponding files, adds function to get next menses (not finished) with first tests

This commit is contained in:
tina
2018-08-22 18:21:57 +02:00
parent b80c91cb46
commit ea21fc92a2
5 changed files with 171 additions and 15 deletions
+2 -12
View File
@@ -4,10 +4,10 @@ import {
View, View,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import { LocalDate, ChronoUnit } from 'js-joda'
import styles from '../styles/index' import styles from '../styles/index'
import cycleModule from '../lib/cycle' import cycleModule from '../lib/cycle'
import getCycleInfo from '../lib/cycle-length' import {getCycleLengthStats as getCycleInfo, getCycleLength} from '../lib/cycle-length'
import {stats as labels} from './labels' import {stats as labels} from './labels'
export default class Stats extends Component { export default class Stats extends Component {
@@ -56,14 +56,4 @@ export default class Stats extends Component {
</ScrollView> </ScrollView>
) )
} }
}
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])
cycleLengths.push(cycleStart.until(nextCycleStart, ChronoUnit.DAYS))
}
return cycleLengths
} }
+13 -1
View File
@@ -1,6 +1,8 @@
import assert from 'assert' import assert from 'assert'
import { LocalDate, ChronoUnit } from 'js-joda'
import cycleModule from '../lib/cycle'
export default function getCycleLengthStats(cycleLengths) { export function getCycleLengthStats(cycleLengths) {
throwIfArgsAreNotInRequiredFormat(cycleLengths) throwIfArgsAreNotInRequiredFormat(cycleLengths)
const cycleLengthStats = {} const cycleLengthStats = {}
const sortedCycleLengths = cycleLengths.sort((a, b) => { const sortedCycleLengths = cycleLengths.sort((a, b) => {
@@ -46,4 +48,14 @@ function throwIfArgsAreNotInRequiredFormat(cycleLengths) {
assert.equal(typeof cycleLength, 'number', 'Elements in the array should be of type number.') assert.equal(typeof cycleLength, 'number', 'Elements in the array should be of type number.')
assert.ok(!isNaN(cycleLength), 'Elements of array should not be NaN.') assert.ok(!isNaN(cycleLength), 'Elements of array should not be NaN.')
}) })
}
export 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])
cycleLengths.push(cycleStart.until(nextCycleStart, ChronoUnit.DAYS))
}
return cycleLengths
} }
+32 -1
View File
@@ -1,4 +1,5 @@
import * as joda from 'js-joda' import * as joda from 'js-joda'
import {getCycleLengthStats, getCycleLength} from './cycle-length'
const LocalDate = joda.LocalDate const LocalDate = joda.LocalDate
const DAYS = joda.ChronoUnit.DAYS const DAYS = joda.ChronoUnit.DAYS
@@ -143,11 +144,41 @@ export default function config(opts) {
} }
} }
function getPredictedMenses(maxCycleLength, minCyclesForPrediction) {
maxCycleLength = maxCycleLength || 99
minCyclesForPrediction = minCyclesForPrediction || 3
const allMensesStarts = getAllMensesStarts()
const atLeastOneCycle = allMensesStarts.length > 1
if (!atLeastOneCycle ||
allMensesStarts.length < minCyclesForPrediction ||
getCycleDayNumber(LocalDate.now().toString()) > maxCycleLength
) {
return {}
}
const cycleLengths = getCycleLength(allMensesStarts)
const cycleInfo = getCycleLengthStats(cycleLengths)
const periodDistance = Math.round(cycleInfo.mean)
const periodStartVariation = (cycleInfo.stdDeviation < 1.5) ? 1 : 2 // threshold is choosen a little arbitrarily
var lastStart = allMensesStarts[0]
const predictedMenses = []
for (let i = 0; i < 3; i++) {
lastStart = LocalDate.parse(lastStart).plusDays(periodDistance).toString()
const nextPredictedRange = {
'startDate': LocalDate.parse(lastStart).minusDays(periodStartVariation).toString(),
'endDate': LocalDate.parse(lastStart).plusDays(periodStartVariation).toString()
}
predictedMenses.push(nextPredictedRange)
}
return predictedMenses
}
return { return {
getCycleDayNumber, getCycleDayNumber,
getCycleForDay, getCycleForDay,
getPreviousCycle, getPreviousCycle,
getCyclesBefore, getCyclesBefore,
getAllMensesStarts getAllMensesStarts,
getPredictedMenses
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import chai from 'chai' import chai from 'chai'
import { AssertionError } from 'assert' import { AssertionError } from 'assert'
import cycleInfo from '../lib/cycle-length' import {getCycleLengthStats as cycleInfo} from '../lib/cycle-length'
const expect = chai.expect const expect = chai.expect
+123
View File
@@ -343,4 +343,127 @@ describe('getCycleForDay', () => {
}, },
]) ])
}) })
})
describe.only('getPredictedMenses', () => {
describe('cannot predict next menses', () => {
it('if no bleeding is documented', () => {
const cycleDaysSortedByDate = [ {} ]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses(99, 1)
expect(result).to.eql({})
})
it('if no cycle is completed', () => {
const cycleDaysSortedByDate = [
{
date: '2018-06-02',
bleeding: { value: 2 }
}
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses(99, 1)
expect(result).to.eql({})
})
it('if number of cycles is below minCyclesForPrediction', () => {
const cycleDaysSortedByDate = [
{
date: '2018-06-02',
bleeding: { value: 2 }
},
{
date: '2018-06-01',
bleeding: { value: 2 }
},
{
date: '2018-05-01',
bleeding: { value: 2 }
},
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses()
expect(result).to.eql({})
})
it('if last bleeding was more than maxCycleLength days ago', () => {
const cycleDaysSortedByDate = [
{
date: '2017-07-02',
bleeding: { value: 2 }
},
{
date: '2017-06-01',
bleeding: { value: 2 }
},
{
date: '2017-05-01',
bleeding: { value: 2 }
},
{
date: '2017-04-01',
bleeding: { value: 2 }
}
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses()
expect(result).to.eql({})
})
})
describe('works', () => {
it('if number of cycles is above minCyclesForPrediction with little standard deviation', () => {
const cycleDaysSortedByDate = [
{
date: '2018-08-02',
bleeding: { value: 2 }
},
{
date: '2018-07-02',
bleeding: { value: 2 }
},
{
date: '2018-06-01',
bleeding: { value: 2 }
},
{
date: '2018-05-01',
bleeding: { value: 2 }
},
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses()
const expectedResult = [
{
'startDate': '2018-09-01',
'endDate': '2018-09-03'
},
{
'startDate': '2018-10-02',
'endDate': '2018-10-04'
},
{
'startDate': '2018-11-02',
'endDate': '2018-11-04'
}
]
expect(result).to.eql(expectedResult)
})
})
}) })