-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdisease_status_extractor.rb
More file actions
128 lines (110 loc) · 5.57 KB
/
Copy pathdisease_status_extractor.rb
File metadata and controls
128 lines (110 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
require 'standoff'
=begin
This is a really naive information extractor to detect assertions about disease statuses.
All it does is tag mentions of a list of disease-status-related keywords (disease, cancer, status), tag mentions of disease-status-related value words (e.g. stable, improving), and tag mentions of rationale concepts (e.g. imaging, scans, symptoms). For each keyword, it checks the next tag to the right, and if that tag is a status value, it returns the value. It also returns and normalizes all rationale mentions found within the chunk along with that status assertion.
So, it will assert "stable" for the phrase "your cancer is stable."
It will also do the right thing in slightly more complicated cases, for example, still correctly asserting "stable" and also correctly NOT assert "progressing" for the phrase "your disease is um stable it is not progressing."
=end
=begin
Example output:
[
{
"disease": null,
"status": {
"mention text": "not changing",
"normalized": "stable"
},
"rationale": [
{
"mention text": "CT scans",
"normalized": "Imaging"
}
]
},
{
"disease": null,
"status": {
"mention text": "progressing",
"normalized": "progressing"
},
"rationale": [
{
"mention text": "CT scans",
"normalized": "Imaging"
},
{
"mention text": "physical exam",
"normalized": "Physical Exam"
}
]
}
]
=end
class DiseaseStatusExtractor
def analyze_text (text)
annotated = Standoff::AnnotatedString.new( :signal => text, :tags => [])
# this would be just key.match document, but we want MatchData for possible multiple keyword matches, not just one
annotated.signal.to_enum(:scan, /status|disease|cancer/i).map{ Regexp.last_match }.each do |match|
annotated.tags << Standoff::Tag.new(:content => match[0],
:name => "disease_status_key",
:start => match.begin(0),
:end => match.end(0) )
end
# TODO: this is dopey. duplication of search expressions between here and the disease_status patterns in Chunker should be abstracted and merged.
annotated.signal.to_enum(:scan, /((not? )|(complete ))?(stable|progressing|responding|response( to treatment)?|resection|inevaluable|changed?|getting worse|worsening|getting better|improving)/i).map{ Regexp.last_match }.each do |match|
mention_text = match[0]
mapped_for_normalization = case mention_text # be very careful with this. it evaluates greedily, and as such the order of expressions here matters a lot.
when /^not? /i
"stable"
when /getting worse|worsening|progress/i
"progressing"
when /complete resection/i, /complete response/i
$&
when /getting better|improving|response to treatment/i
"responding"
when /inevaluable/i, /stable/i, /progressing/i
$&
else
nil
end
normalized = mapped_for_normalization ? mapped_for_normalization.split(/ |\_/).map(&:capitalize).join(" ") : nil#cap each word
annotated.tags << Standoff::Tag.new(:content => match[0],
:name => "status_value",
:attributes => {:normalized => normalized},
:start => match.begin(0),
:end => match.end(0) )
end
annotated.signal.to_enum(:scan, /((ca?t scan|mri|x-ray|x ray|imaging)( results)?)/i).map{ Regexp.last_match }.each do |match|
annotated.tags << Standoff::Tag.new(:content => match[0],
:name => "status_rationale",
:attributes => {:normalized => "Imaging"},
:start => match.begin(0),
:end => match.end(0) )
end
annotated.signal.to_enum(:scan, /(pathology|symptoms|(physical )?exam|markers)/i).map{ Regexp.last_match }.each do |match|
mention_text = m[0]
mention_text = "physical exam" if mention_text == "exam"
normalized = mention_text.split(/ |\_/).map(&:capitalize).join(" ") #cap each word
annotated.tags << Standoff::Tag.new(:content => mention_text,
:name => "status_rationale",
:attributes => {:normalized => normalized},
:start => match.begin(0),
:end => match.end(0) )
end
disease_status_assertions = []
annotated.tags.select{|tag| tag.name == "disease_status_key"}.each do |key_tag|
next_tag = annotated.next_tag key_tag
if next_tag && (next_tag.name == "status_value")
disease_status_assertions << {
:disease => nil,
:rationale => annotated.tags.select{|tag| tag.name == "status_rationale"}.map do|tag|
{:mention_text => tag.content, :normalized => tag.attributes[:normalized]}
end.uniq,
:status => { :mention_text => next_tag.content,
:normalized => next_tag.attributes[:normalized]}
}
end
end
return disease_status_assertions
end
end