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 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>
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -49,13 +47,3 @@ function throwIfArgsAreNotInRequiredFormat(cycleLengths) {
|
|||||||
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
@@ -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
@@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user