diff --git a/components/stats.js b/components/stats.js
index 6ae0e27..ad4b0e6 100644
--- a/components/stats.js
+++ b/components/stats.js
@@ -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 {
{labels.emptyStats}
}
{atLeastOneCycle && numberOfCycles === 1 &&
- {labels.oneCycleStats(cycleLengths[0])}
+
+ {labels.oneCycleStats(cycleLengths[0])}
+
}
{atLeastOneCycle && numberOfCycles > 1 &&
- {labels.getBasisOfStats(numberOfCycles)}
+
+ {labels.getBasisOfStats(numberOfCycles)}
+
{labels.averageLabel}
{cycleInfo.mean + ' ' + labels.daysLabel}
diff --git a/lib/cycle-length.js b/lib/cycle-length.js
index 1d4ae02..956328d 100644
--- a/lib/cycle-length.js
+++ b/lib/cycle-length.js
@@ -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
}
\ No newline at end of file
diff --git a/lib/cycle.js b/lib/cycle.js
index affdee8..5d41855 100644
--- a/lib/cycle.js
+++ b/lib/cycle.js
@@ -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
}
}
\ No newline at end of file
diff --git a/test/cycle.spec.js b/test/cycle.spec.js
index 09df48f..34ed144 100644
--- a/test/cycle.spec.js
+++ b/test/cycle.spec.js
@@ -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)
+ })
})
})
\ No newline at end of file