adds more tests for period predictions, moves getcycleslength into cycle module
This commit is contained in:
+8
-4
@@ -7,7 +7,7 @@ import {
|
||||
|
||||
import styles from '../styles/index'
|
||||
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'
|
||||
|
||||
export default class Stats extends Component {
|
||||
@@ -18,7 +18,7 @@ export default class Stats extends Component {
|
||||
let numberOfCycles
|
||||
let cycleInfo
|
||||
if (atLeastOneCycle) {
|
||||
cycleLengths = getCycleLength(allMensesStarts)
|
||||
cycleLengths = cycleModule().getCycleLength(allMensesStarts)
|
||||
numberOfCycles = cycleLengths.length
|
||||
if (numberOfCycles > 1) {
|
||||
cycleInfo = getCycleInfo(cycleLengths)
|
||||
@@ -31,10 +31,14 @@ export default class Stats extends Component {
|
||||
<Text style={styles.statsIntro}>{labels.emptyStats}</Text>
|
||||
}
|
||||
{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>
|
||||
<Text style={styles.statsIntro}>{labels.getBasisOfStats(numberOfCycles)}</Text>
|
||||
<Text style={styles.statsIntro}>
|
||||
{labels.getBasisOfStats(numberOfCycles)}
|
||||
</Text>
|
||||
<View style={styles.statsRow}>
|
||||
<Text style={styles.statsLabelLeft}>{labels.averageLabel}</Text>
|
||||
<Text style={styles.statsLabelRight}>{cycleInfo.mean + ' ' + labels.daysLabel}</Text>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import assert from 'assert'
|
||||
import { LocalDate, ChronoUnit } from 'js-joda'
|
||||
import cycleModule from '../lib/cycle'
|
||||
|
||||
export function getCycleLengthStats(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.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
@@ -1,5 +1,5 @@
|
||||
import * as joda from 'js-joda'
|
||||
import {getCycleLengthStats, getCycleLength} from './cycle-length'
|
||||
import {getCycleLengthStats} from './cycle-length'
|
||||
const LocalDate = joda.LocalDate
|
||||
const DAYS = joda.ChronoUnit.DAYS
|
||||
|
||||
@@ -7,6 +7,8 @@ export default function config(opts) {
|
||||
let bleedingDaysSortedByDate
|
||||
let cycleDaysSortedByDate
|
||||
let maxBreakInBleeding
|
||||
let maxCycleLength
|
||||
let minCyclesForPrediction
|
||||
|
||||
if (!opts) {
|
||||
// we only want to require (and run) the db module
|
||||
@@ -14,10 +16,14 @@ export default function config(opts) {
|
||||
bleedingDaysSortedByDate = require('../db').bleedingDaysSortedByDate
|
||||
cycleDaysSortedByDate = require('../db').cycleDaysSortedByDate
|
||||
maxBreakInBleeding = 1
|
||||
maxCycleLength = 99
|
||||
minCyclesForPrediction = 3
|
||||
} else {
|
||||
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || []
|
||||
cycleDaysSortedByDate = opts.cycleDaysSortedByDate || []
|
||||
maxBreakInBleeding = opts.maxBreakInBleeding || 1
|
||||
maxCycleLength = opts.maxCycleLength || 99
|
||||
minCyclesForPrediction = opts.minCyclesForPrediction || 3
|
||||
}
|
||||
|
||||
function getLastMensesStart(targetDateString) {
|
||||
@@ -144,22 +150,37 @@ export default function config(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPredictedMenses(maxCycleLength, minCyclesForPrediction) {
|
||||
maxCycleLength = maxCycleLength || 99
|
||||
minCyclesForPrediction = minCyclesForPrediction || 3
|
||||
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 getPredictedMenses() {
|
||||
const allMensesStarts = getAllMensesStarts()
|
||||
|
||||
const atLeastOneCycle = allMensesStarts.length > 1
|
||||
if (!atLeastOneCycle ||
|
||||
allMensesStarts.length < minCyclesForPrediction ||
|
||||
getCycleDayNumber(LocalDate.now().toString()) > maxCycleLength
|
||||
allMensesStarts.length < minCyclesForPrediction
|
||||
) {
|
||||
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
|
||||
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]
|
||||
const predictedMenses = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
@@ -179,6 +200,7 @@ export default function config(opts) {
|
||||
getPreviousCycle,
|
||||
getCyclesBefore,
|
||||
getAllMensesStarts,
|
||||
getCycleLength,
|
||||
getPredictedMenses
|
||||
}
|
||||
}
|
||||
+115
-22
@@ -345,19 +345,21 @@ describe('getCycleForDay', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe.only('getPredictedMenses', () => {
|
||||
describe('getPredictedMenses', () => {
|
||||
describe('cannot predict next menses', () => {
|
||||
it('if no bleeding is documented', () => {
|
||||
const cycleDaysSortedByDate = [ {} ]
|
||||
|
||||
const { getPredictedMenses } = cycleModule({
|
||||
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({})
|
||||
})
|
||||
it('if no cycle is completed', () => {
|
||||
it('if one bleeding is documented (no completed cycle)', () => {
|
||||
const cycleDaysSortedByDate = [
|
||||
{
|
||||
date: '2018-06-02',
|
||||
@@ -367,9 +369,11 @@ describe.only('getPredictedMenses', () => {
|
||||
|
||||
const { getPredictedMenses } = cycleModule({
|
||||
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({})
|
||||
})
|
||||
it('if number of cycles is below minCyclesForPrediction', () => {
|
||||
@@ -395,36 +399,43 @@ describe.only('getPredictedMenses', () => {
|
||||
const result = getPredictedMenses()
|
||||
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 = [
|
||||
{
|
||||
date: '2017-07-02',
|
||||
date: '2018-07-15',
|
||||
bleeding: { value: 2 }
|
||||
},
|
||||
{
|
||||
date: '2017-06-01',
|
||||
bleeding: { value: 2 }
|
||||
},
|
||||
{
|
||||
date: '2017-05-01',
|
||||
bleeding: { value: 2 }
|
||||
},
|
||||
{
|
||||
date: '2017-04-01',
|
||||
date: '2018-07-01',
|
||||
bleeding: { value: 2 }
|
||||
}
|
||||
]
|
||||
|
||||
const { getPredictedMenses } = cycleModule({
|
||||
cycleDaysSortedByDate,
|
||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
||||
minCyclesForPrediction: 1
|
||||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
describe('works', () => {
|
||||
it('if number of cycles is above minCyclesForPrediction with little standard deviation', () => {
|
||||
it('if number of cycles is above minCyclesForPrediction', () => {
|
||||
const cycleDaysSortedByDate = [
|
||||
{
|
||||
date: '2018-08-02',
|
||||
@@ -465,5 +476,87 @@ describe.only('getPredictedMenses', () => {
|
||||
]
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user