adds more tests for period predictions, moves getcycleslength into cycle module

This commit is contained in:
tina
2018-08-23 11:36:57 +02:00
parent ea21fc92a2
commit 497a3a3ff5
4 changed files with 152 additions and 45 deletions
+8 -4
View File
@@ -7,7 +7,7 @@ import {
import styles from '../styles/index' import styles from '../styles/index'
import cycleModule from '../lib/cycle' import cycleModule from '../lib/cycle'
import {getCycleLengthStats as getCycleInfo, getCycleLength} from '../lib/cycle-length' import {getCycleLengthStats as getCycleInfo} 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 {
@@ -18,7 +18,7 @@ export default class Stats extends Component {
let numberOfCycles let numberOfCycles
let cycleInfo let cycleInfo
if (atLeastOneCycle) { if (atLeastOneCycle) {
cycleLengths = getCycleLength(allMensesStarts) cycleLengths = cycleModule().getCycleLength(allMensesStarts)
numberOfCycles = cycleLengths.length numberOfCycles = cycleLengths.length
if (numberOfCycles > 1) { if (numberOfCycles > 1) {
cycleInfo = getCycleInfo(cycleLengths) cycleInfo = getCycleInfo(cycleLengths)
@@ -31,10 +31,14 @@ export default class Stats extends Component {
<Text style={styles.statsIntro}>{labels.emptyStats}</Text> <Text style={styles.statsIntro}>{labels.emptyStats}</Text>
} }
{atLeastOneCycle && numberOfCycles === 1 && {atLeastOneCycle && numberOfCycles === 1 &&
<Text style={styles.statsIntro}>{labels.oneCycleStats(cycleLengths[0])}</Text> <Text style={styles.statsIntro}>
{labels.oneCycleStats(cycleLengths[0])}
</Text>
} }
{atLeastOneCycle && numberOfCycles > 1 && <View> {atLeastOneCycle && numberOfCycles > 1 && <View>
<Text style={styles.statsIntro}>{labels.getBasisOfStats(numberOfCycles)}</Text> <Text style={styles.statsIntro}>
{labels.getBasisOfStats(numberOfCycles)}
</Text>
<View style={styles.statsRow}> <View style={styles.statsRow}>
<Text style={styles.statsLabelLeft}>{labels.averageLabel}</Text> <Text style={styles.statsLabelLeft}>{labels.averageLabel}</Text>
<Text style={styles.statsLabelRight}>{cycleInfo.mean + ' ' + labels.daysLabel}</Text> <Text style={styles.statsLabelRight}>{cycleInfo.mean + ' ' + labels.daysLabel}</Text>
-12
View File
@@ -1,6 +1,4 @@
import assert from 'assert' import assert from 'assert'
import { LocalDate, ChronoUnit } from 'js-joda'
import cycleModule from '../lib/cycle'
export function getCycleLengthStats(cycleLengths) { export function getCycleLengthStats(cycleLengths) {
throwIfArgsAreNotInRequiredFormat(cycleLengths) throwIfArgsAreNotInRequiredFormat(cycleLengths)
@@ -48,14 +46,4 @@ 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
} }
+29 -7
View File
@@ -1,5 +1,5 @@
import * as joda from 'js-joda' import * as joda from 'js-joda'
import {getCycleLengthStats, getCycleLength} from './cycle-length' import {getCycleLengthStats} from './cycle-length'
const LocalDate = joda.LocalDate const LocalDate = joda.LocalDate
const DAYS = joda.ChronoUnit.DAYS const DAYS = joda.ChronoUnit.DAYS
@@ -7,6 +7,8 @@ export default function config(opts) {
let bleedingDaysSortedByDate let bleedingDaysSortedByDate
let cycleDaysSortedByDate let cycleDaysSortedByDate
let maxBreakInBleeding let maxBreakInBleeding
let maxCycleLength
let minCyclesForPrediction
if (!opts) { if (!opts) {
// we only want to require (and run) the db module // we only want to require (and run) the db module
@@ -14,10 +16,14 @@ export default function config(opts) {
bleedingDaysSortedByDate = require('../db').bleedingDaysSortedByDate bleedingDaysSortedByDate = require('../db').bleedingDaysSortedByDate
cycleDaysSortedByDate = require('../db').cycleDaysSortedByDate cycleDaysSortedByDate = require('../db').cycleDaysSortedByDate
maxBreakInBleeding = 1 maxBreakInBleeding = 1
maxCycleLength = 99
minCyclesForPrediction = 3
} else { } else {
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || [] bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || []
cycleDaysSortedByDate = opts.cycleDaysSortedByDate || [] cycleDaysSortedByDate = opts.cycleDaysSortedByDate || []
maxBreakInBleeding = opts.maxBreakInBleeding || 1 maxBreakInBleeding = opts.maxBreakInBleeding || 1
maxCycleLength = opts.maxCycleLength || 99
minCyclesForPrediction = opts.minCyclesForPrediction || 3
} }
function getLastMensesStart(targetDateString) { function getLastMensesStart(targetDateString) {
@@ -144,22 +150,37 @@ export default function config(opts) {
} }
} }
function getPredictedMenses(maxCycleLength, minCyclesForPrediction) { function getCycleLength(cycleStartDates) {
maxCycleLength = maxCycleLength || 99 const cycleLengths = []
minCyclesForPrediction = minCyclesForPrediction || 3 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 getPredictedMenses() {
const allMensesStarts = getAllMensesStarts() const allMensesStarts = getAllMensesStarts()
const atLeastOneCycle = allMensesStarts.length > 1 const atLeastOneCycle = allMensesStarts.length > 1
if (!atLeastOneCycle || if (!atLeastOneCycle ||
allMensesStarts.length < minCyclesForPrediction || allMensesStarts.length < minCyclesForPrediction
getCycleDayNumber(LocalDate.now().toString()) > maxCycleLength
) { ) {
return {} return {}
} }
const cycleLengths = getCycleLength(allMensesStarts) const cycleLengths = getCycleLength(allMensesStarts)
const cycleInfo = getCycleLengthStats(cycleLengths) const cycleInfo = getCycleLengthStats(cycleLengths)
const periodDistance = Math.round(cycleInfo.mean) const periodDistance = Math.round(cycleInfo.mean)
const periodStartVariation = (cycleInfo.stdDeviation < 1.5) ? 1 : 2 // threshold is choosen a little arbitrarily let periodStartVariation
if (cycleInfo.stdDeviation === null) {
periodStartVariation = 2
} else if (cycleInfo.stdDeviation < 1.5) { // threshold is choosen a little arbitrarily
periodStartVariation = 1
} else {
periodStartVariation = 2
}
var lastStart = allMensesStarts[0] var lastStart = allMensesStarts[0]
const predictedMenses = [] const predictedMenses = []
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
@@ -179,6 +200,7 @@ export default function config(opts) {
getPreviousCycle, getPreviousCycle,
getCyclesBefore, getCyclesBefore,
getAllMensesStarts, getAllMensesStarts,
getCycleLength,
getPredictedMenses getPredictedMenses
} }
} }
+115 -22
View File
@@ -345,19 +345,21 @@ describe('getCycleForDay', () => {
}) })
}) })
describe.only('getPredictedMenses', () => { describe('getPredictedMenses', () => {
describe('cannot predict next menses', () => { describe('cannot predict next menses', () => {
it('if no bleeding is documented', () => { it('if no bleeding is documented', () => {
const cycleDaysSortedByDate = [ {} ] const cycleDaysSortedByDate = [ {} ]
const { getPredictedMenses } = cycleModule({ const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate, cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding) bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
maxCycleLength: 99,
minCyclesForPrediction: 1
}) })
const result = getPredictedMenses(99, 1) const result = getPredictedMenses()
expect(result).to.eql({}) expect(result).to.eql({})
}) })
it('if no cycle is completed', () => { it('if one bleeding is documented (no completed cycle)', () => {
const cycleDaysSortedByDate = [ const cycleDaysSortedByDate = [
{ {
date: '2018-06-02', date: '2018-06-02',
@@ -367,9 +369,11 @@ describe.only('getPredictedMenses', () => {
const { getPredictedMenses } = cycleModule({ const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate, cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding) bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
maxCycleLength: 99,
minCyclesForPrediction: 1
}) })
const result = getPredictedMenses(99, 1) const result = getPredictedMenses()
expect(result).to.eql({}) expect(result).to.eql({})
}) })
it('if number of cycles is below minCyclesForPrediction', () => { it('if number of cycles is below minCyclesForPrediction', () => {
@@ -395,36 +399,43 @@ describe.only('getPredictedMenses', () => {
const result = getPredictedMenses() const result = getPredictedMenses()
expect(result).to.eql({}) expect(result).to.eql({})
}) })
it('if last bleeding was more than maxCycleLength days ago', () => { })
describe('works', () => {
it('for one completed cycle with minCyclesForPrediction = 1', () => {
const cycleDaysSortedByDate = [ const cycleDaysSortedByDate = [
{ {
date: '2017-07-02', date: '2018-07-15',
bleeding: { value: 2 } bleeding: { value: 2 }
}, },
{ {
date: '2017-06-01', date: '2018-07-01',
bleeding: { value: 2 }
},
{
date: '2017-05-01',
bleeding: { value: 2 }
},
{
date: '2017-04-01',
bleeding: { value: 2 } bleeding: { value: 2 }
} }
] ]
const { getPredictedMenses } = cycleModule({ const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate, cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding) bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
minCyclesForPrediction: 1
}) })
const result = getPredictedMenses() const result = getPredictedMenses()
expect(result).to.eql({}) const expectedResult = [
{
'startDate': '2018-07-27',
'endDate': '2018-07-31'
},
{
'startDate': '2018-08-10',
'endDate': '2018-08-14'
},
{
'startDate': '2018-08-24',
'endDate': '2018-08-28'
}
]
expect(result).to.eql(expectedResult)
}) })
}) it('if number of cycles is above minCyclesForPrediction', () => {
describe('works', () => {
it('if number of cycles is above minCyclesForPrediction with little standard deviation', () => {
const cycleDaysSortedByDate = [ const cycleDaysSortedByDate = [
{ {
date: '2018-08-02', date: '2018-08-02',
@@ -465,5 +476,87 @@ describe.only('getPredictedMenses', () => {
] ]
expect(result).to.eql(expectedResult) expect(result).to.eql(expectedResult)
}) })
it('3 cycles with little standard deviation', () => {
const cycleDaysSortedByDate = [
{
date: '2018-08-01',
bleeding: { value: 2 }
},
{
date: '2018-07-18',
bleeding: { value: 2 }
},
{
date: '2018-07-05',
bleeding: { value: 2 }
},
{
date: '2018-06-20',
bleeding: { value: 2 }
},
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses()
const expectedResult = [
{
'startDate': '2018-08-14',
'endDate': '2018-08-16'
},
{
'startDate': '2018-08-28',
'endDate': '2018-08-30'
},
{
'startDate': '2018-09-11',
'endDate': '2018-09-13'
}
]
expect(result).to.eql(expectedResult)
})
it('3 cycles with bigger standard deviation', () => {
const cycleDaysSortedByDate = [
{
date: '2018-08-01',
bleeding: { value: 2 }
},
{
date: '2018-07-14',
bleeding: { value: 2 }
},
{
date: '2018-07-04',
bleeding: { value: 2 }
},
{
date: '2018-06-20',
bleeding: { value: 2 }
},
]
const { getPredictedMenses } = cycleModule({
cycleDaysSortedByDate,
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
})
const result = getPredictedMenses()
const expectedResult = [
{
'startDate': '2018-08-13',
'endDate': '2018-08-17'
},
{
'startDate': '2018-08-27',
'endDate': '2018-08-31'
},
{
'startDate': '2018-09-10',
'endDate': '2018-09-14'
}
]
expect(result).to.eql(expectedResult)
})
}) })
}) })