File Coverage

blib/lib/Lingua/ConText.pm
Criterion Covered Total %
statement 20 184 10.8
branch 2 62 3.2
condition 0 3 0.0
subroutine 5 10 50.0
pod 1 6 16.6
total 28 265 10.5


line stmt bran cond sub pod time code
1             package Lingua::ConText;
2              
3 1     1   22118 use 5.008008;
  1         3  
  1         32  
4 1     1   5 use strict;
  1         2  
  1         31  
5 1     1   5 use warnings;
  1         6  
  1         123  
6              
7             require Exporter;
8              
9             our (@ISA,@EXPORT_OK,$VERSION,$phrase);
10             BEGIN {
11 1     1   14 @ISA = qw(Exporter);
12 1         3 $VERSION = '0.01';
13 1         2783 @EXPORT_OK = qw(
14             applyContext
15             );
16             }
17             ##################################################################################################
18              
19              
20             our $MAX_WINDOW = 15;
21             our $target_word_regex = '';
22              
23             ##################################################################################################
24              
25              
26             our ($phrase, $regex_list);
27              
28             sub init {
29 1     1 0 2 while ( my($position,$type_hash) = each %{$phrase} ) {
  5         19  
30 4         4 while ( my ($type,$list) = each %{$type_hash} ) {
  20         69  
31 16         16 foreach my $p ( @{$list} ) {
  16         33  
32 354 100       758 $regex_list->{$position}->{$type} .= '|' if $regex_list->{$position}->{$type};
33 354         654 $regex_list->{$position}->{$type} .= $p;
34             }
35             }
36             }
37             }
38              
39             ##################################################################################################
40              
41             sub applyContext {
42 0     0 1   my ( $concept, $sentence ) = @_;
43 0 0 0       return unless $concept && $sentence;
44 0           my $tagged = preprocess_sentence( $concept, $sentence );
45 0 0         return [ "'$concept' not found in this sentence.", $sentence, ] if ! $tagged;
46             # print "$tagged\n\n";
47              
48 0           my @words = split /[,;\s]+/, $tagged;
49              
50 0           my $ne = applyNegEx( \@words );
51 0           my $tmp = applyTemporality( \@words );
52 0           my $subj = applyExperiencer( \@words );
53 0           return [ $concept, $sentence, $ne, $tmp, $subj ];
54             }
55              
56             ##################################################################################################
57              
58             sub preprocess_sentence {
59 0     0 0   my $concept = lc shift;
60 0           my $sentence = lc shift;
61 0           $sentence =~ s/\s+/ /g;
62              
63 0 0         if ( $sentence =~ /\b$concept\b/ ) {
64 0           $sentence =~ s/\b$concept\b/$target_word_regex/g;
65             } else {
66 0           return;
67             }
68              
69 0           while ( my($position,$type_hash) = each %{$regex_list} ) {
  0            
70 0           while ( my ($type,$regex) = each %{$type_hash} ) {
  0            
71 0           my $tag = uc( qq{<$type} . '_' . qq{$position>} );
72 0           $sentence =~ s/\b($regex)\b/$tag/g;
73             }
74             }
75              
76 0           my $regex_time = "((1[4-9]|[1-9]?[2-9][0-9])[ |-][day|days] of)|(([2-9]|[1-9][0-9])[ |-][week|weeks] of)|(([1-9]?[0-9])[ |-][month|months|year|years] of)"; #pattern to recognize expressions of >14 days
77 0           my $regex_time_for = "[for|over] the [last|past] (((1[4-9]|[1-9]?[2-9][0-9])[ |-][day|days] of)|(([2-9]|[1-9][0-9])[ |-][week|weeks] of)|(([1-9]?[0-9])[ |-][month|months|year|years] of))"; # other pattern to recognize expressions of >14 days
78 0           my $regex_time_since = "since [last|the last]? ((([2-9]|[1-9][0-9]) weeks ago)|(([1-9]?[0-9])? [month|months|year|years] ago)|([january|february|march|april|may|june|july|august|september|october|november|december|spring|summer|fall|winter]))";
79              
80 0           $sentence =~ s/\b($regex_time)\b//g;
81 0           $sentence =~ s/\b($regex_time_for)\b//g;
82 0           $sentence =~ s/\b($regex_time_since)\b//g;
83              
84 0           return $sentence;
85             }
86              
87             ##################################################################################################
88              
89             sub applyNegEx {
90 0     0 0   my $words = shift;
91              
92 0           my $window = [];
93 0           my $word_count = scalar @{$words};
  0            
94              
95 0           my $this_context; # = 'affirmed'; # affirmed, negated, possible
96              
97 0           my $m = 0;
98 0           while ( $m < $word_count ) {
99              
100 0 0         if ( @{$words}[$m] =~ /PSEUDO/ ) {
  0 0          
  0 0          
101 0           $m++;
102              
103 0           } elsif ( ${$words}[$m] eq '' ) {
104 0 0         my $max_window = ( $word_count < $m + $MAX_WINDOW) ? ($word_count - $m) : $MAX_WINDOW;
105 0           foreach ( my $o=1; $o < $max_window; $o++) {
106 0 0         if ( ${$words}[$m+$o] =~ /(|||)/ ) {
  0            
107 0           last;
108             } else {
109 0           push @{$window}, ${$words}[$m+$o];
  0            
  0            
110             }
111             }
112              
113 0 0         if ( ${$words}[$m] eq '' ) {
  0 0          
  0            
114 0           $this_context = 'negated';
115             } elsif ( @{$words}[$m] eq '' ) {
116 0           $this_context = 'possible';
117             }
118              
119 0           for ( my $w=0; $w < scalar( @{$window} ); $w++) {
  0            
120 0 0         if ( @{$window}[$w] =~ /$target_word_regex/ ) {
  0            
121 0           return $this_context;
122             }
123             }
124 0           $window = [];
125 0           $m++;
126             } elsif ( @{$words}[$m] =~ /(|)/ ) {
127              
128 0 0         my $max_window = ($m < $MAX_WINDOW) ? $m : $MAX_WINDOW;
129 0           for ( my $o=1; $o < $max_window; $o++) {
130 0 0         if( @{$words}[$m-$o] =~ /(||||)/ ) {
  0            
131 0           last;
132             } else {
133 0           push @{$window}, @{$words}[$m-$o];
  0            
  0            
134             }
135              
136 0 0         if ( @{$words}[$m] eq '' ){
  0 0          
  0            
137 0           $this_context = 'negated';
138             } elsif ( @{$words}[$m] eq '' ) {
139 0           $this_context = 'possible';
140 0           for( my $w=0; $w< scalar( @{$window}); $w++ ) {
  0            
141 0 0         if( @{$window}[$w] =! /$target_word_regex/ ) {
  0            
142 0           return $this_context;
143             }
144             }
145 0           $window = [];
146 0           $m++;
147             }
148             }
149             } else {
150 0           $m++;
151             }
152             }
153 0           return $this_context;
154             }
155              
156              
157              
158             # recent, historical, hypothetical
159             sub applyTemporality {
160 0     0 0   my $words = shift;
161 0           my $window = [];
162 0           my $words_length = scalar @{$words};
  0            
163              
164             #Going from one temporality term to another, and creating the appropriate window
165 0           my $mm = 0;
166 0           while ( $mm < $words_length ) {
167             # /IF word is a pseudo-negation, skips to the next word
168 0 0         if ( @{$words}[$mm] eq '' ) {
  0 0          
  0 0          
    0          
169 0           $mm++;
170 0           } elsif ( @{$words}[$mm] eq '' ) {
171             #/IF word is a pre- hypothetical trigger term
172             #/expands window until end of sentence, termination term, or other negation/possible trigger term
173 0           for ( my $o=1; ($mm+$o) < $words_length; $o++ ) {
174 0 0         if ( @{$words}[$mm+$o] =~ /(||)/ ) {
  0            
175 0           last;
176             } else {
177 0           push @{$window}, @{$words}[$mm+$0];
  0            
  0            
178             }
179             }
180             #/check if there are concepts in the window
181 0           for ( my $w=0; $w < scalar(@{$window}); $w++) {
  0            
182 0 0         if ( @{$window}[ $w ] =~ /$target_word_regex/ ) {
  0            
183 0           return 'hypothetical';
184             }
185             }
186 0           $window = [];
187 0           $mm++;
188              
189 0           } elsif ( @{$words}[$mm] =~ /(||)/ ) {
190             #/expands window until end of sentence, termination term, or other negation/possible trigger term
191 0           for ( my $o=1; ($mm+$o) < $words_length; $o++ ) {
192 0 0         if ( @{$words}[$mm+$o] =~ /(|||)/ ) {
  0            
193 0           last;
194             } else {
195 0           push @{$window}, @{$words}[$mm+$o];
  0            
  0            
196             }
197             }
198             #/check if there are concepts in the window
199 0           for ( my $w=0; $w < scalar(@{$window}); $w++) {
  0            
200 0 0         if ( @{$window}[$w] =~ /$target_word_regex/ ) {
  0            
201 0           return 'historical';
202             }
203             }
204 0           $window = [];
205 0           $mm++;
206             } elsif ( @{$words}[$mm] eq '' ) {
207             #/expands window until end of sentence, termination term, or other negation/possible trigger term
208 0           for ( my $o=1; ($mm - $o ) >= 0; $o++ ) {
209 0 0         if ( @{$words}[ $mm - $o ] =~ /(||)/ ) {
  0            
210 0           last;
211             } else {
212 0           push @{$window}, @{$words}[$mm - $o ];
  0            
  0            
213             }
214             }
215             #/check if there are concepts in the window
216 0           for ( my $w=0; $w < scalar(@{$window}); $w++ ) {
  0            
217 0 0         if ( @{$window}[ $w] =~ /$target_word_regex/ ) {
  0            
218 0           return 'historical';
219             }
220             }
221 0           $window = [];
222 0           $mm++;
223             } else {
224 0           $mm++;
225             }
226             }
227 0           return 'recent';
228             }
229              
230             sub applyExperiencer {
231 0     0 0   my $words = shift;
232 0           my $window;
233 0           my $mm = 0;
234 0           my $word_length = scalar( @{$words} );
  0            
235 0           while ( $mm < $word_length ) {
236             #/IF word is a pseudo-negation, skips to the next word
237 0 0         if( @{$words}[$mm] eq '' ) {
  0 0          
  0            
238 0           $mm++;
239             } elsif ( @{$words}[$mm] eq '' ) {
240             #expands window until end of sentence, termination term, or other negation/possible trigger term
241 0           for ( my $o=1; ($mm+$o) < $word_length; $o++ ) {
242 0 0         if( @{$words}[$mm+$o] =~ /(|||)/ ) {
  0            
243 0           last;
244             } else {
245 0           push @{$window}, @{$words}[ $mm+$o ];
  0            
  0            
246             }
247 0           for ( my $w=0; $w < scalar( @{$window} ); $w++ ) {
  0            
248 0 0         if( @{$window}[$w] =~ /$target_word_regex/ ) {
  0            
249 0           return 'other';
250             }
251             }
252             }
253 0           $window = [];
254 0           $mm++;
255             } else {
256 0           $mm++;
257             }
258             }
259 0           return "patient";
260             }
261              
262             ##################################################################################################
263              
264             $phrase = {
265             pseudo => {
266             'hypo' => [
267             'if negative',
268             ],
269             'neg' => [
270             'gram negative',
271             'no change',
272             'no definite change',
273             'no increase',
274             'no interval change',
275             'no significant change',
276             'no significant interval change',
277             'no suspicious change',
278             'not cause',
279             'not certain if',
280             'not certain whether',
281             'not drain',
282             'not extend',
283             'not necessarily',
284             'not on',
285             'not only',
286             'without difficulty',
287             ],
288             'hist' => [
289             'history and',
290             'history and examination',
291             'history and physical',
292             'history for',
293             'history of chief complaint',
294             'history of present illness',
295             'history taking',
296             '"history physical"',
297             'poor history',
298             'social history',
299             'sudden onset of',
300             ],
301             },
302             post => {
303             'poss' => [
304             'be ruled out',
305             'being ruled out',
306             'can be ruled out',
307             'could be ruled out',
308             'did not rule out',
309             'is to be ruled out',
310             'may be ruled out',
311             'might be ruled out',
312             'must be ruled out',
313             'not been ruled out',
314             'not ruled out',
315             'ought to be ruled out',
316             'should be ruled out',
317             'will be ruled out',
318             ],
319             'neg' => [
320             'are ruled out',
321             'free',
322             'has been negative',
323             'has been ruled out',
324             'have been ruled out',
325             'is ruled out',
326             'no longer present',
327             'non diagnostic',
328             'now resolved',
329             'prophylaxis',
330             'unlikely',
331             'was negative',
332             'was ruled out',
333             ],
334             },
335             pre => {
336             'exp' => [
337             'aunt',
338             'aunt\'s',
339             'brother',
340             'brother\'s',
341             'dad',
342             'dad\'s',
343             'family',
344             'fam hx',
345             'father',
346             'father\'s',
347             'grandfather',
348             'grandfather\'s',
349             'grandmother',
350             'grandmother\'s',
351             'mom',
352             'mom\'s',
353             'mother',
354             'mother\'s',
355             'sister',
356             'sister\'s',
357             'uncle',
358             'uncle\'s',
359             ],
360             'hypo' => [
361             'as needed',
362             'come back for',
363             'come back to',
364             'if',
365             'return',
366             'should he',
367             'should she',
368             'should the patient',
369             'should there',
370             ],
371             'poss' => [
372             'be ruled out for',
373             'can be ruled out for',
374             'could be ruled out for',
375             'is to be ruled out for',
376             'may be ruled out for',
377             'might be ruled out for',
378             'must be ruled out for',
379             'ought to be ruled out for',
380             'r/o',
381             'ro',
382             'rule her out',
383             'rule her out for',
384             'rule him out',
385             'rule him out for',
386             'rule out',
387             'rule out for',
388             'rule the patient out',
389             'rule the patinet out for',
390             'should be ruled out for',
391             'what must be ruled out is',
392             'will be ruled out for',
393             ],
394             'neg' => [
395             'adequate to rule her out',
396             'adequate to rule him out',
397             'adequate to rule out',
398             'adequate to rule the patient out',
399             'any other',
400             'as well as any',
401             'can rule her out',
402             'can rule her out against',
403             'can rule her out for',
404             'can rule him out',
405             'can rule him out against',
406             'can rule him out for',
407             'can rule out',
408             'can rule out against',
409             'can rule out for',
410             'can rule the patient out',
411             'can rule the patinet out against',
412             'can rule the patinet out for',
413             'cannot',
414             'checked for',
415             'clear of',
416             'declined',
417             'declines',
418             'denied',
419             'denies',
420             'denying',
421             'did rule her out',
422             'did rule her out against',
423             'did rule her out for',
424             'did rule him out',
425             'did rule him out against',
426             'did rule him out for',
427             'did rule out',
428             'did rule out against',
429             'did rule out for',
430             'did rule the patient out',
431             'did rule the patient out against',
432             'did rule the patient out for',
433             'doesn\'t look like',
434             'evaluate for',
435             'fails to reveal',
436             'free of',
437             'inconsistent with',
438             'is not',
439             'isn\'t',
440             'lack of',
441             'lacked',
442             'negative for',
443             'never developed',
444             'never had',
445             'no',
446             'no abnormal',
447             'no cause of',
448             'no complaints of',
449             'no evidence',
450             'no evidence to suggest',
451             'no findings of',
452             'no findings to indicate',
453             'no history of',
454             'no mammographic evidence of',
455             'no new',
456             'no new evidence',
457             'no other evidence',
458             'no radiographic evidence of',
459             'no sign of',
460             'no significant',
461             'no signs of',
462             'no suggestion of',
463             'no suspicious',
464             'not',
465             'not appear',
466             'not appreciate',
467             'not associated with',
468             'not complain of',
469             'not demonstrate',
470             'not exhibit',
471             'not feel',
472             'not had',
473             'not have',
474             'not have evidence of',
475             'not know of',
476             'not known to have',
477             'not reveal',
478             'not see',
479             'not to be',
480             'patient was not',
481             'rather than',
482             'resolved',
483             'ruled her out',
484             'ruled her out against',
485             'ruled her out for',
486             'ruled him out',
487             'ruled him out against',
488             'ruled him out for',
489             'ruled out',
490             'ruled out against',
491             'ruled out for',
492             'ruled the patient out',
493             'ruled the patient out against',
494             'ruled the patient out for',
495             'rules her out',
496             'rules her out for',
497             'rules him out',
498             'rules him out for',
499             'rules out',
500             'rules out for',
501             'rules the patient out',
502             'rules the patient out for',
503             'sufficient to rule her out',
504             'sufficient to rule her out against',
505             'sufficient to rule her out for',
506             'sufficient to rule him out',
507             'sufficient to rule him out against',
508             'sufficient to rule him out for',
509             'sufficient to rule out',
510             'sufficient to rule out against',
511             'sufficient to rule out for',
512             'sufficient to rule the patient out',
513             'sufficient to rule the patient out against',
514             'sufficient to rule the patient out for',
515             'test for',
516             'to exclude',
517             'unremarkable for',
518             'was not',
519             'wasn\'t',
520             'with no',
521             'without',
522             'without any evidence of',
523             'without evidence',
524             'without indication of',
525             'without sign of',
526             ],
527             'hist' => [
528             'history',
529             'past history',
530             'past medical history',
531             'previous',
532             ],
533             },
534             end => {
535             'exp' => [
536             'which',
537             ],
538             'histexp' => [
539             'complains',
540             'currently',
541             'noted',
542             'presenting',
543             'presents',
544             'reported',
545             'reports',
546             'states',
547             'today',
548             'was found',
549             ],
550             'hypo' => [
551             'because',
552             'since',
553             ],
554             'hypoexp' => [
555             'her',
556             'his',
557             'patient',
558             'patient\'s',
559             'who',
560             ],
561             'neg' => [
562             'although',
563             'apart from',
564             'as a cause for',
565             'as a cause of',
566             'as a etiology for',
567             'as a etiology of',
568             'as a reason for',
569             'as a reason of',
570             'as a secondary cause for',
571             'as a secondary cause of',
572             'as a secondary etiology for',
573             'as a secondary etiology of',
574             'as a secondary origin for',
575             'as a secondary origin of',
576             'as a secondary reason for',
577             'as a secondary reason of',
578             'as a secondary source for',
579             'as a secondary source of',
580             'as a source for',
581             'as a source of',
582             'as an cause for',
583             'as an cause of',
584             'as an etiology for',
585             'as an etiology of',
586             'as an origin for',
587             'as an origin of',
588             'as an reason for',
589             'as an reason of',
590             'as an secondary cause for',
591             'as an secondary cause of',
592             'as an secondary etiology for',
593             'as an secondary etiology of',
594             'as an secondary origin for',
595             'as an secondary origin of',
596             'as an secondary reason for',
597             'as an secondary reason of',
598             'as an secondary source for',
599             'as an secondary source of',
600             'as an source for',
601             'as an source of',
602             'as has',
603             'as the cause for',
604             'as the cause of',
605             'as the etiology for',
606             'as the etiology of',
607             'as the origin for',
608             'as the origin of',
609             'as the reason for',
610             'as the reason of',
611             'as the secondary cause for',
612             'as the secondary cause of',
613             'as the secondary etiology for',
614             'as the secondary etiology of',
615             'as the secondary origin for',
616             'as the secondary origin of',
617             'as the secondary reason for',
618             'as the secondary reason of',
619             'as the secondary source for',
620             'as the secondary source of',
621             'as the source for',
622             'as the source of',
623             'aside from',
624             'but',
625             'cause for',
626             'cause of',
627             'causes for',
628             'causes of',
629             'etiology for',
630             'etiology of',
631             'except',
632             'however',
633             'nevertheless',
634             'origin for',
635             'origin of',
636             'origins for',
637             'origins of',
638             'other possibilities of',
639             'reason for',
640             'reason of',
641             'reasons for',
642             'reasons of',
643             'secondary',
644             'secondary to',
645             'source for',
646             'source of',
647             'sources for',
648             'sources of',
649             'still',
650             'though',
651             'trigger event for',
652             'yet',
653             ],
654             'hist' => [
655             'ED',
656             'emergency department',
657             ],
658             },
659             };
660              
661             init();
662              
663             1;
664              
665              
666              
667             __END__