File Coverage

blib/lib/Data/Ovulation.pm
Criterion Covered Total %
statement 91 97 93.8
branch 32 40 80.0
condition 23 36 63.8
subroutine 17 17 100.0
pod 6 6 100.0
total 169 196 86.2


line stmt bran cond sub pod time code
1             package Data::Ovulation;
2              
3 4     4   105795 use strict;
  4         12  
  4         255  
4 4     4   24 use warnings;
  4         8  
  4         125  
5              
6 4     4   100 use 5.008;
  4         23  
  4         187  
7              
8 4     4   23 use Carp;
  4         8  
  4         355  
9 4     4   2393 use Data::Ovulation::Result;
  4         11  
  4         28  
10 4     4   191 use base qw/ Exporter /;
  4         13  
  4         309  
11              
12 4     4   23 use vars qw/ @EXPORT /;
  4         8  
  4         258  
13             @EXPORT = qw/ DELTA_FERTILE_DAYS DELTA_OVULATION_DAYS DELTA_NEXT_CYCLE /;
14              
15 4     4   32 use constant DELTA_FERTILE_DAYS => 5;
  4         9  
  4         299  
16 4     4   33 use constant DELTA_OVULATION_DAYS => 3;
  4         10  
  4         192  
17 4     4   25 use constant DELTA_NEXT_CYCLE => 14;
  4         7  
  4         4583  
18              
19             =head1 NAME
20              
21             Data::Ovulation - Female ovulation prediction based on basal body temperature values
22              
23             =head1 VERSION
24              
25             This document describes Data::Ovulation version 0.01
26              
27             =cut
28              
29             our $VERSION = '0.01';
30              
31             =head1 SYNOPSIS
32              
33             use Data::Ovulation;
34              
35             my $ovul = Data::Ovulation->new;
36             $ovul->add_temperature( { day => 1, temp => '36.5' } );
37             $ovul->add_temperature( { day => 2, temp => '36.1' } );
38              
39             my $ovul = Data::Ovulation->new;
40             $ovul->temperatures( [ qw/
41             36.5 36.1 36.1 36.2 36.2 36.2 36.3 36.2 36.2 36.1 36.3 36.4
42             36.2 36.4 36.4 36.4 36.4 36.5 36.7 36.7 36.6 36.6 36.7 36.8
43             / ] );
44              
45             my $result = $ovul->calculate;
46             my $could_be_pregnant = $result->impregnation;
47             my @ovulation_days = @{ $result->ovulation_days };
48             my @fertile_days = @{ $result->fertile_days };
49              
50             See L for all result object methods.
51              
52             =head1 DESCRIPTION
53              
54             This module tries to predict (based on scientific facts) if and when an ovulation has occurred
55             within the female menstrual cycle based on basal body temperature values. Taking the temperature
56             values after the ovulation into account it is possible to predict if an impregnation has occured.
57             This data is often used as the basis for basal temperature curves.
58              
59             =head1 SUBROUTINES/METHODS
60              
61             =head2 C
62              
63             Creates a new L object. You may pass in an arrayref of temperatures during object
64             construction:
65              
66             my $ovul = Data::Ovulation->new( {
67             temperatures => [ qw/ 36.2 36.1 ... / ]
68             } );
69              
70             =cut
71              
72             sub new {
73 2     2 1 64 my ( $class, $param ) = @_;
74 2         6 my $self = {};
75 2         13 $self->{ '_temperatures' } = [];
76 2         11 bless $self, $class;
77             }
78              
79             =head2 C
80              
81             Set all temperatures at once. Expects an arrayref of temperatures for every day of the menstrual cycle in
82             consecutive order starting with day 1. If called without parameters returns an arrayref of set temperatures.
83              
84             $ovul->temperatures( [ qw/ 36.5 36.1 / ] );
85             my @temperatures = @{ $ovul->temperatures };
86              
87             =cut
88              
89             sub temperatures {
90 28     28 1 13864 my ( $self, $param ) = @_;
91 28 100       63 if ( $param ) {
92 4 50       21 if ( ref( $param ) eq "ARRAY" ) {
93 4         74 $self->{ '_temperatures' } = $param;
94             }
95             else {
96 0         0 croak "Not an arrayref";
97             }
98             }
99             else {
100 24   50     174 return $self->{ '_temperatures' } || [];
101             }
102             }
103              
104             =head2 C
105              
106             Sets/Gets the temperature for a day. Day numbering starts at 1 - not 0!
107             Day 1 is supposed to be the first day of a new menstrual cycle. Returns
108             the set value on success.
109              
110             $ovul->add_temperature( { day => 12, temp => '36.2' } );
111              
112             =cut
113              
114             sub temperature {
115 48     48 1 216 my ( $self, $params ) = @_;
116              
117 48 50 33     235 croak "day out of range or not specified" if $params->{ 'day' } < 0 || ! int $params->{ 'day' };
118              
119 48 100       104 if ( defined $params->{ 'temp' } ) {
120 47         115 $self->{ '_temperatures' }->[ $params->{ 'day' } - 1 ] = $params->{ 'temp' };
121             }
122              
123 48         149 return $self->{ '_temperatures' }->[ $params->{ 'day' } - 1 ];
124             }
125              
126             =head2 C
127              
128             Calculates the ovulation day and various other aspects of the female menstrual cycle based on basal
129             body temperature values set in the object and returns a L object with the results.
130             Returns 0 if the calculation failed. There must be at least 10 temperature values in the object
131             for the calculation to be somewhat reasonable. A warning will be issued if there are less than 10
132             values and the method will immediately return with a value of 0.
133              
134             $ovul->calculate();
135              
136             =cut
137              
138             sub calculate {
139 3     3 1 21 my ( $self ) = @_;
140              
141 3 50       13 if ( $self->no_of_values < 10 ) {
142 0         0 carp "Not enough values - need at least 10 temperature values to calculate ovulation";
143 0         0 return 0;
144             }
145              
146 3         10 my $list = $self->temperatures;
147            
148             #----------------------------------------------------------
149             # Calculate min/max temperature values
150             #----------------------------------------------------------
151 3         6 my $max = 0;
152 3         6 my $min = 99.9;
153 3         6 for my $day( 0 .. @{ $list } ) {
  3         11  
154 60 100       132 if( defined $list->[ $day ] ) {
155 57 100       163 $min = $list->[ $day ] if $list->[ $day ] < $min;
156 57 100       157 $max = $list->[ $day ] if $list->[ $day ] > $max;
157             }
158             }
159              
160             #----------------------------------------------------------
161             # Calculate ovulation day
162             #----------------------------------------------------------
163 3         7 my $ovulation_day;
164             my $max6;
165 3         6 for my $day ( 6 .. @{ $list } - 1 ) {
  3         10  
166              
167             # get highest temperature value of previous six entries
168 14         35 $max6 = $self->_max6( $day );
169              
170 14 50       37 if ( int( $max6 ) > 0 ) {
171 14 100 50     324 if (
    100 50        
    100 100        
      50        
      100        
      50        
      50        
      100        
      50        
      66        
      50        
      66        
172              
173             #----------------------------------------------
174             # Rule 1
175             # Three temperature values are greater than
176             # $max6 and the third value is at least 0.2°
177             # higher than $max6
178             #----------------------------------------------
179             ( sprintf( "%2.1f", $list->[ $day ] || 0 ) > $max6 )
180             && ( sprintf( "%2.1f", $list->[ $day + 1 ] || 0 ) > $max6 )
181             && (
182             sprintf( "%2.1f", $list->[ $day + 2 ] || 0 ) >= sprintf "%2.1f", ( $max6 + 0.2 )
183             )
184             )
185             {
186 1         3 $ovulation_day = $day + 1;
187             }
188             elsif (
189              
190             #----------------------------------------------
191             # Rule 2
192             # Four values are greater than $max6
193             #----------------------------------------------
194             ( sprintf( "%2.1f", $list->[ $day ] || 0 ) > $max6 )
195             && ( sprintf( "%2.1f", $list->[ $day + 1 ] || 0 ) > $max6 )
196             && ( sprintf( "%2.1f", $list->[ $day + 2 ] || 0 ) > $max6 )
197             && ( sprintf( "%2.1f", $list->[ $day + 3 ] || 0 ) > $max6 )
198             )
199             {
200 1         3 $ovulation_day = $day + 1;
201             }
202             elsif ( sprintf( "%2.1f", $list->[ $day ] ) > $max6 ) {
203              
204             #----------------------------------------------
205             # Rule 3
206             # One temperature value is "choked up", i.e.
207             # only one value is less than $max6
208             #----------------------------------------------
209 2         4 my $higher_values = 1;
210 2         8 for ( $day + 1 .. $day + 2 ) {
211 4 50       22 if ( sprintf( "%2.1f", $list->[ $_ ] > $max6 ) ) { $higher_values++ }
  4         11  
212             }
213 2 50       7 if ( $higher_values >= 2 ) {
214 2 100 50     23 if (
215             sprintf( "%2.1f", $list->[ $day + 3 ] || 0 ) >=
216             sprintf( "%2.1f", $max6 + 0.2 ) )
217             {
218 1         4 $ovulation_day = $day + 1;
219             }
220             }
221             }
222             }
223              
224 14 100       40 last if $ovulation_day;
225             }
226              
227 3 50       8 if ( $ovulation_day ) {
228              
229 3         6 my $impregnation = 0;
230              
231 3 100       5 if ( scalar @{ $list } > $ovulation_day + DELTA_NEXT_CYCLE ) {
  3         11  
232              
233             #-------------------------------------------------------------
234             # Calculate if impregnation is likely to have occured.
235             # This is the case if the temperature after the ovulation day
236             # is still above $max6 when the next menstrual cycle begins.
237             #-------------------------------------------------------------
238 1         1 $impregnation = 1;
239 1         2 for my $day ( $ovulation_day + DELTA_NEXT_CYCLE .. @{ $list } ) {
  1         3  
240 6 50       42 if ( sprintf( "%2.1f", $list->[ $day - 1 ] ) < sprintf( "%2.1f", $max6 ) ) {
241 0         0 $impregnation = 0;
242 0         0 last;
243             }
244             }
245             }
246              
247 3         56 my $result = Data::Ovulation::Result->new(
248             {
249             min => $min,
250             max => $max,
251             day_rise => $ovulation_day,
252             ovulation_days => [ ( $ovulation_day - DELTA_OVULATION_DAYS + 1 .. $ovulation_day ) ],
253             fertile_days => [ ( $ovulation_day - DELTA_FERTILE_DAYS + 1 .. $ovulation_day ) ],
254             cover_temperature => $max6,
255             impregnation => $impregnation,
256             }
257             );
258 3         88 return $result;
259             }
260              
261 0         0 return 0;
262             }
263              
264             =head2 C
265              
266             Returns the number of temperature values set.
267              
268             my $no_of_values = $ovul->no_of_values();
269              
270             =cut
271              
272             sub no_of_values {
273 4     4 1 13 return scalar grep { defined $_ } @{ shift->temperatures };
  312         983  
  4         14  
274             }
275              
276             =head2 C
277              
278             Remove set temperatures.
279              
280             $ovul->clear();
281              
282             =cut
283              
284 2     2 1 9 sub clear { return shift->{ '_temperatures' } = [] }
285              
286             # Return highest temperature value of previous six values
287              
288             sub _max6 {
289 14     14   23 my ( $self, $day ) = @_;
290 14         22 my $max = 0.0;
291 14         29 for my $value ( @{ $self->temperatures }[ $day - 6 .. $day - 1 ] ) {
  14         33  
292 84 100       592 if ( ( sprintf "%2.1f", $value ) > ( sprintf "%2.1f", $max ) ) {
293 33         128 $max = sprintf "%2.1f", $value;
294             }
295             }
296 14         41 return $max;
297             }
298              
299             =head1 EXPORTS
300              
301             The following constants are exported by default:
302              
303             DELTA_FERTILE_DAYS # Number of days the fertility lasts.
304             # The fertile period is supposed to start around
305             # 5 days before the temperature rises.
306            
307             DELTA_OVULATION_DAYS # The ovulation is supposed to happen on one of
308             # 3 days prior to the temperature rise.
309            
310             DELTA_NEXT_CYCLE # Number of days until the next menstrual cycle
311             # starts counted from the day the temperature rises.
312              
313             =head1 KNOWN BUGS
314              
315             None yet.
316              
317             =head1 SUPPORT
318              
319             C<< >>.
320              
321             =head1 AUTHOR
322              
323             Tobias Kremer, C<< >>
324              
325             =head1 SEE ALSO
326              
327             =over
328              
329             =item L - Result class methods
330              
331             =item L - Wikipedia entry on Basal body temperature
332              
333             =item L - An example of this module in use (german only)
334              
335             =back
336              
337             =head1 COPYRIGHT & LICENSE
338              
339             Copyright 2007 Tobias Kremer, all rights reserved.
340              
341             This program is free software; you can redistribute it and/or modify it
342             under the same terms as Perl itself.
343              
344             =cut
345              
346             1;