openehr 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.rdoc +93 -12
- data/lib/openehr/am/archetype/constraint_model/primitive.rb +204 -6
- data/lib/openehr/am/archetype/constraint_model.rb +228 -4
- data/lib/openehr/am/archetype/ontology.rb +2 -2
- data/lib/openehr/am/archetype.rb +101 -2
- data/lib/openehr/am/openehr_profile/data_types/basic.rb +28 -0
- data/lib/openehr/am/openehr_profile/data_types/quantity.rb +88 -0
- data/lib/openehr/am/openehr_profile/data_types/text.rb +23 -0
- data/lib/openehr/am/template.rb +25 -3
- data/lib/openehr/aql/engine/binding.rb +13 -0
- data/lib/openehr/aql/engine/contains_resolver.rb +161 -0
- data/lib/openehr/aql/engine/dataset.rb +122 -0
- data/lib/openehr/aql/engine/path_evaluator.rb +137 -0
- data/lib/openehr/aql/engine/predicate_evaluator.rb +65 -0
- data/lib/openehr/aql/engine.rb +119 -0
- data/lib/openehr/aql/errors.rb +27 -0
- data/lib/openehr/aql/lexer.rb +295 -0
- data/lib/openehr/aql/model/containment.rb +45 -0
- data/lib/openehr/aql/model/from_clause.rb +35 -0
- data/lib/openehr/aql/model/function_call.rb +56 -0
- data/lib/openehr/aql/model/identified_path.rb +20 -0
- data/lib/openehr/aql/model/literal.rb +15 -0
- data/lib/openehr/aql/model/object_path.rb +34 -0
- data/lib/openehr/aql/model/order_by_and_limit.rb +50 -0
- data/lib/openehr/aql/model/predicate.rb +84 -0
- data/lib/openehr/aql/model/query.rb +24 -0
- data/lib/openehr/aql/model/select_clause.rb +29 -0
- data/lib/openehr/aql/model/where_clause.rb +105 -0
- data/lib/openehr/aql/model.rb +14 -0
- data/lib/openehr/aql/parser.rb +484 -0
- data/lib/openehr/aql/result_set.rb +54 -0
- data/lib/openehr/aql.rb +28 -0
- data/lib/openehr/assumed_library_types.rb +68 -17
- data/lib/openehr/parser/adl_grammar.tt +29 -13
- data/lib/openehr/parser/adl_parser.rb +18 -4
- data/lib/openehr/parser/archetype_validator.rb +126 -0
- data/lib/openehr/parser/exception.rb +13 -0
- data/lib/openehr/parser/opt_parser.rb +162 -9
- data/lib/openehr/parser.rb +2 -0
- data/lib/openehr/path.rb +195 -0
- data/lib/openehr/rm/common/archetyped.rb +116 -6
- data/lib/openehr/rm/common/change_control.rb +3 -8
- data/lib/openehr/rm/common/directory.rb +4 -0
- data/lib/openehr/rm/common/generic.rb +24 -10
- data/lib/openehr/rm/composition/content/entry.rb +24 -5
- data/lib/openehr/rm/composition/content/navigation.rb +2 -1
- data/lib/openehr/rm/composition.rb +19 -2
- data/lib/openehr/rm/data_structures/history.rb +3 -0
- data/lib/openehr/rm/data_structures/item_structure/representation.rb +13 -9
- data/lib/openehr/rm/data_structures/item_structure.rb +28 -15
- data/lib/openehr/rm/data_types/encapsulated.rb +23 -3
- data/lib/openehr/rm/data_types/quantity/date_time.rb +9 -1
- data/lib/openehr/rm/data_types/quantity.rb +66 -14
- data/lib/openehr/rm/data_types/text.rb +8 -0
- data/lib/openehr/rm/data_types/time_specification.rb +5 -4
- data/lib/openehr/rm/demographic.rb +4 -3
- data/lib/openehr/rm/ehr.rb +20 -1
- data/lib/openehr/rm/factory.rb +234 -6
- data/lib/openehr/rm/integration.rb +1 -0
- data/lib/openehr/rm/support/identification.rb +37 -8
- data/lib/openehr/rm/support/measurement.rb +32 -0
- data/lib/openehr/rm/type_name.rb +90 -0
- data/lib/openehr/rm.rb +3 -0
- data/lib/openehr/serializer/adl_serializer.rb +335 -0
- data/lib/openehr/serializer/base.rb +20 -0
- data/lib/openehr/serializer/opt_serializer.rb +49 -0
- data/lib/openehr/serializer/rm_json_serializer.rb +62 -0
- data/lib/openehr/serializer/xml_serializer.rb +260 -0
- data/lib/openehr/serializer.rb +5 -291
- data/lib/openehr/terminology_service.rb +44 -0
- data/lib/openehr/version.rb +1 -1
- data/lib/openehr.rb +5 -2
- metadata +35 -7
- data/lib/openehr/parser/validator.rb +0 -18
- data/lib/openehr/parser/xml_parser.rb +0 -13
- data/lib/openehr/rm/data_types/charset_extract.rb +0 -24
- data/lib/openehr/writer.rb +0 -12
data/lib/openehr/parser.rb
CHANGED
data/lib/openehr/path.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# OpenEHR::Path parses the openEHR archetype path syntax used to
|
|
2
|
+
# navigate LOCATABLE/PATHABLE trees, e.g.
|
|
3
|
+
# /content[at0001]/data[at0002]/events[at0006]/data[at0003]/items[at0004, 'Systolic']/value
|
|
4
|
+
#
|
|
5
|
+
# Grammar subset supported (see the plan for the full rationale):
|
|
6
|
+
# path := '/' | ('/' segment)+
|
|
7
|
+
# segment := attribute predicate?
|
|
8
|
+
# attribute := [a-z][a-zA-Z0-9_]*
|
|
9
|
+
# predicate := '[' node_id (',' ws* name)? ']'
|
|
10
|
+
# node_id := at-code (at\d+(\.\d+)*, incl. specialised at0001.1 and
|
|
11
|
+
# the short at0.2 form ADL 1.4 uses for nodes newly
|
|
12
|
+
# introduced by a specialisation)
|
|
13
|
+
# | archetype-id
|
|
14
|
+
# name := "'" ... "'" (a Locatable#name.value literal)
|
|
15
|
+
#
|
|
16
|
+
# Deliberately unsupported (raises InvalidPathError): relative paths,
|
|
17
|
+
# '//' wildcards, numeric index predicates, and general expression
|
|
18
|
+
# predicates. Those are layered on top by consumers (e.g. AQL); this
|
|
19
|
+
# class only parses the path shape used by RM path navigation.
|
|
20
|
+
#
|
|
21
|
+
# This file has no dependency on any RM class, so it can be required
|
|
22
|
+
# standalone.
|
|
23
|
+
module OpenEHR
|
|
24
|
+
class Path
|
|
25
|
+
class InvalidPathError < ArgumentError; end
|
|
26
|
+
|
|
27
|
+
AT_CODE = /\Aat\d+(\.\d+)*\z/
|
|
28
|
+
ARCHETYPE_ID = /\A[a-zA-Z]\w+-[a-zA-Z]\w+-[a-zA-Z]\w+\.[a-zA-Z]\w+(-[a-zA-Z]\w+)?\.v\d+\z/
|
|
29
|
+
ATTRIBUTE = /\A[a-z][a-zA-Z0-9_]*\z/
|
|
30
|
+
|
|
31
|
+
class Segment
|
|
32
|
+
attr_reader :attribute, :archetype_node_id, :name
|
|
33
|
+
|
|
34
|
+
def initialize(attribute, archetype_node_id: nil, name: nil)
|
|
35
|
+
unless attribute.is_a?(String) && attribute =~ ATTRIBUTE
|
|
36
|
+
raise InvalidPathError, "invalid path attribute: #{attribute.inspect}"
|
|
37
|
+
end
|
|
38
|
+
if archetype_node_id && !(archetype_node_id =~ AT_CODE || archetype_node_id =~ ARCHETYPE_ID)
|
|
39
|
+
raise InvalidPathError, "invalid node id: #{archetype_node_id.inspect}"
|
|
40
|
+
end
|
|
41
|
+
raise InvalidPathError, 'name predicate requires a node_id predicate' if name && archetype_node_id.nil?
|
|
42
|
+
|
|
43
|
+
@attribute = attribute
|
|
44
|
+
@archetype_node_id = archetype_node_id
|
|
45
|
+
@name = name
|
|
46
|
+
freeze
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def predicate?
|
|
50
|
+
!@archetype_node_id.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
return @attribute unless predicate?
|
|
55
|
+
|
|
56
|
+
s = "#{@attribute}[#{@archetype_node_id}"
|
|
57
|
+
s += ", '#{@name}'" if @name
|
|
58
|
+
s + ']'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ==(other)
|
|
62
|
+
other.is_a?(Segment) &&
|
|
63
|
+
attribute == other.attribute &&
|
|
64
|
+
archetype_node_id == other.archetype_node_id &&
|
|
65
|
+
name == other.name
|
|
66
|
+
end
|
|
67
|
+
alias eql? ==
|
|
68
|
+
|
|
69
|
+
def hash
|
|
70
|
+
[attribute, archetype_node_id, name].hash
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.parse(str)
|
|
75
|
+
raise InvalidPathError, 'path must be a String' unless str.is_a?(String)
|
|
76
|
+
raise InvalidPathError, "path must start with '/': #{str.inspect}" unless str.start_with?('/')
|
|
77
|
+
return new([]) if str == '/'
|
|
78
|
+
|
|
79
|
+
body = str[1..-1]
|
|
80
|
+
raise InvalidPathError, "path must not end with '/': #{str.inspect}" if body.end_with?('/')
|
|
81
|
+
|
|
82
|
+
new(split_segments(body).map { |token| parse_segment(token) })
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.valid?(str)
|
|
86
|
+
parse(str)
|
|
87
|
+
true
|
|
88
|
+
rescue InvalidPathError
|
|
89
|
+
false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Splits on top-level '/' only (not '/' occurring inside a '[...]'
|
|
93
|
+
# predicate), and rejects empty segments (i.e. a '//' wildcard).
|
|
94
|
+
def self.split_segments(body)
|
|
95
|
+
tokens = []
|
|
96
|
+
depth = 0
|
|
97
|
+
current = +''
|
|
98
|
+
body.each_char do |c|
|
|
99
|
+
case c
|
|
100
|
+
when '['
|
|
101
|
+
depth += 1
|
|
102
|
+
current << c
|
|
103
|
+
when ']'
|
|
104
|
+
depth -= 1
|
|
105
|
+
current << c
|
|
106
|
+
when '/'
|
|
107
|
+
if depth.zero?
|
|
108
|
+
raise InvalidPathError, "empty path segment ('//' is not supported): #{body.inspect}" if current.empty?
|
|
109
|
+
|
|
110
|
+
tokens << current
|
|
111
|
+
current = +''
|
|
112
|
+
else
|
|
113
|
+
current << c
|
|
114
|
+
end
|
|
115
|
+
else
|
|
116
|
+
current << c
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
raise InvalidPathError, "unbalanced '[' in path: #{body.inspect}" unless depth.zero?
|
|
120
|
+
raise InvalidPathError, "empty path segment ('//' is not supported): #{body.inspect}" if current.empty?
|
|
121
|
+
|
|
122
|
+
tokens << current
|
|
123
|
+
tokens
|
|
124
|
+
end
|
|
125
|
+
private_class_method :split_segments
|
|
126
|
+
|
|
127
|
+
def self.parse_segment(token)
|
|
128
|
+
if token =~ /\A([^\[\]]+)\[(.*)\]\z/
|
|
129
|
+
attribute = Regexp.last_match(1)
|
|
130
|
+
predicate = Regexp.last_match(2)
|
|
131
|
+
if predicate =~ /\A([^,]+),\s*'([^']*)'\z/
|
|
132
|
+
Segment.new(attribute, archetype_node_id: Regexp.last_match(1).strip, name: Regexp.last_match(2))
|
|
133
|
+
else
|
|
134
|
+
Segment.new(attribute, archetype_node_id: predicate.strip)
|
|
135
|
+
end
|
|
136
|
+
elsif token =~ /\A[^\[\]]+\z/
|
|
137
|
+
Segment.new(token)
|
|
138
|
+
else
|
|
139
|
+
raise InvalidPathError, "malformed path segment: #{token.inspect}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
private_class_method :parse_segment
|
|
143
|
+
|
|
144
|
+
attr_reader :segments
|
|
145
|
+
|
|
146
|
+
def initialize(segments)
|
|
147
|
+
@segments = segments.freeze
|
|
148
|
+
freeze
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def root?
|
|
152
|
+
@segments.empty?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def to_s
|
|
156
|
+
return '/' if root?
|
|
157
|
+
|
|
158
|
+
'/' + @segments.map(&:to_s).join('/')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def ==(other)
|
|
162
|
+
other.is_a?(Path) && segments == other.segments
|
|
163
|
+
end
|
|
164
|
+
alias eql? ==
|
|
165
|
+
|
|
166
|
+
def hash
|
|
167
|
+
segments.hash
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parent
|
|
171
|
+
return self if root?
|
|
172
|
+
|
|
173
|
+
self.class.new(segments[0..-2])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def +(other)
|
|
177
|
+
case other
|
|
178
|
+
when Segment
|
|
179
|
+
self.class.new(segments + [other])
|
|
180
|
+
when Path
|
|
181
|
+
self.class.new(segments + other.segments)
|
|
182
|
+
when String
|
|
183
|
+
self.class.new(segments + [Segment.new(other)])
|
|
184
|
+
else
|
|
185
|
+
raise ArgumentError, "cannot append #{other.class} to a Path"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def descend
|
|
190
|
+
return [nil, self] if root?
|
|
191
|
+
|
|
192
|
+
[segments.first, self.class.new(segments[1..-1])]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -13,31 +13,138 @@ module OpenEHR
|
|
|
13
13
|
MULTIPART_ID_DELIMITER = "::"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Raised by Pathable#item_at_path when a path predicate matches
|
|
17
|
+
# more than one child (e.g. a node_id shared by siblings with
|
|
18
|
+
# different names).
|
|
19
|
+
class PathNotUniqueError < StandardError; end
|
|
20
|
+
|
|
16
21
|
class Pathable
|
|
17
22
|
attr_accessor :parent
|
|
18
23
|
|
|
24
|
+
# Declares which of this class's attributes are exposed to
|
|
25
|
+
# path navigation, e.g. `path_attribute :data, :state`.
|
|
26
|
+
# Declarations accumulate down the inheritance chain.
|
|
27
|
+
def self.path_attribute(*attr_names)
|
|
28
|
+
@path_attributes ||= []
|
|
29
|
+
@path_attributes.concat(attr_names.map(&:to_sym))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.path_attributes
|
|
33
|
+
own = @path_attributes || []
|
|
34
|
+
self == Pathable ? own : superclass.path_attributes + own
|
|
35
|
+
end
|
|
36
|
+
|
|
19
37
|
def initialize(args = { })
|
|
20
38
|
self.parent = args[:parent]
|
|
21
39
|
end
|
|
22
40
|
|
|
41
|
+
# The one-level-deep path-navigable children of this node, as
|
|
42
|
+
# {attribute_name_string => value}. Attributes whose value is
|
|
43
|
+
# nil are omitted; a value may itself be an Array (e.g. a
|
|
44
|
+
# CLUSTER's items).
|
|
45
|
+
def path_children
|
|
46
|
+
self.class.path_attributes.each_with_object({}) do |attr, hash|
|
|
47
|
+
value = send(attr)
|
|
48
|
+
hash[attr.to_s] = value unless value.nil?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
23
52
|
def item_at_path(path)
|
|
24
|
-
|
|
53
|
+
matches = items_at_path(path)
|
|
54
|
+
case matches.size
|
|
55
|
+
when 0 then nil
|
|
56
|
+
when 1 then matches.first
|
|
57
|
+
else raise PathNotUniqueError, "path #{path} matches #{matches.size} items"
|
|
58
|
+
end
|
|
25
59
|
end
|
|
26
60
|
|
|
27
61
|
def items_at_path(path)
|
|
28
|
-
|
|
62
|
+
path = self.class.normalize_path(path)
|
|
63
|
+
return [self] if path.root?
|
|
64
|
+
|
|
65
|
+
segment, rest = path.descend
|
|
66
|
+
children = path_children[segment.attribute]
|
|
67
|
+
return [] if children.nil?
|
|
68
|
+
|
|
69
|
+
matched = self.class.select_by_predicate(Array(children), segment)
|
|
70
|
+
return matched if rest.root?
|
|
71
|
+
|
|
72
|
+
matched.flat_map do |child|
|
|
73
|
+
child.is_a?(Pathable) ? child.items_at_path(rest) : []
|
|
74
|
+
end
|
|
29
75
|
end
|
|
30
76
|
|
|
31
77
|
def path_exists?(path)
|
|
32
|
-
|
|
78
|
+
!items_at_path(path).empty?
|
|
33
79
|
end
|
|
34
80
|
|
|
35
81
|
def path_of_item(item)
|
|
36
|
-
|
|
82
|
+
find_path_to(item, OpenEHR::Path.new([]))
|
|
37
83
|
end
|
|
38
84
|
|
|
39
85
|
def path_unique?(path)
|
|
40
|
-
|
|
86
|
+
items_at_path(path).size == 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.normalize_path(path)
|
|
90
|
+
path.is_a?(OpenEHR::Path) ? path : OpenEHR::Path.parse(path)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Filters children matched by a path segment's predicate: a
|
|
94
|
+
# node_id predicate keeps only children whose
|
|
95
|
+
# archetype_node_id matches (non-Locatable children never
|
|
96
|
+
# match a node_id predicate); a name predicate further
|
|
97
|
+
# narrows to the child whose name.value matches.
|
|
98
|
+
def self.select_by_predicate(children, segment)
|
|
99
|
+
return children unless segment.predicate?
|
|
100
|
+
|
|
101
|
+
matched = children.select do |child|
|
|
102
|
+
child.respond_to?(:archetype_node_id) &&
|
|
103
|
+
child.archetype_node_id == segment.archetype_node_id
|
|
104
|
+
end
|
|
105
|
+
return matched if segment.name.nil?
|
|
106
|
+
|
|
107
|
+
matched.select do |child|
|
|
108
|
+
child.respond_to?(:name) && child.name.respond_to?(:value) &&
|
|
109
|
+
child.name.value == segment.name
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def find_path_to(target, prefix)
|
|
116
|
+
return prefix.to_s if equal?(target)
|
|
117
|
+
|
|
118
|
+
self.class.path_attributes.each do |attr|
|
|
119
|
+
value = send(attr)
|
|
120
|
+
next if value.nil?
|
|
121
|
+
|
|
122
|
+
siblings = Array(value)
|
|
123
|
+
siblings.each do |child|
|
|
124
|
+
child_prefix = prefix + segment_for(attr, child, siblings)
|
|
125
|
+
if child.equal?(target)
|
|
126
|
+
return child_prefix.to_s
|
|
127
|
+
elsif child.is_a?(Pathable)
|
|
128
|
+
found = child.send(:find_path_to, target, child_prefix)
|
|
129
|
+
return found if found
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def segment_for(attribute, child, siblings)
|
|
137
|
+
node_id = child.respond_to?(:archetype_node_id) ? child.archetype_node_id : nil
|
|
138
|
+
return OpenEHR::Path::Segment.new(attribute.to_s) if node_id.nil?
|
|
139
|
+
|
|
140
|
+
sharing_node_id = siblings.count do |sibling|
|
|
141
|
+
sibling.respond_to?(:archetype_node_id) && sibling.archetype_node_id == node_id
|
|
142
|
+
end
|
|
143
|
+
if sharing_node_id > 1 && child.respond_to?(:name) && child.name.respond_to?(:value)
|
|
144
|
+
OpenEHR::Path::Segment.new(attribute.to_s, archetype_node_id: node_id, name: child.name.value)
|
|
145
|
+
else
|
|
146
|
+
OpenEHR::Path::Segment.new(attribute.to_s, archetype_node_id: node_id)
|
|
147
|
+
end
|
|
41
148
|
end
|
|
42
149
|
end
|
|
43
150
|
|
|
@@ -79,7 +186,7 @@ module OpenEHR
|
|
|
79
186
|
|
|
80
187
|
def concept
|
|
81
188
|
if self.is_archetype_root?
|
|
82
|
-
return DvText.new(:value =>
|
|
189
|
+
return OpenEHR::RM::DataTypes::Text::DvText.new(:value =>
|
|
83
190
|
@archetype_details.archetype_id.concept_name)
|
|
84
191
|
else
|
|
85
192
|
raise ArgumentError, 'this is not root'
|
|
@@ -159,6 +266,8 @@ module OpenEHR
|
|
|
159
266
|
class FeederAuditDetails
|
|
160
267
|
attr_reader :system_id
|
|
161
268
|
attr_accessor :provider, :location, :time, :subject, :version_id
|
|
269
|
+
# RM 1.1.0 (SPECRM-74): optional custom meta-data, ITEM_STRUCTURE.
|
|
270
|
+
attr_accessor :other_details
|
|
162
271
|
|
|
163
272
|
def initialize(args = { })
|
|
164
273
|
self.system_id = args[:system_id]
|
|
@@ -167,6 +276,7 @@ module OpenEHR
|
|
|
167
276
|
self.time = args[:time]
|
|
168
277
|
self.subject = args[:subject]
|
|
169
278
|
self.version_id = args[:version_id]
|
|
279
|
+
self.other_details = args[:other_details]
|
|
170
280
|
end
|
|
171
281
|
|
|
172
282
|
def system_id=(system_id)
|
|
@@ -99,7 +99,7 @@ module OpenEHR
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def owner_id
|
|
102
|
-
return HierObjectID.new(:value => @uid.value)
|
|
102
|
+
return OpenEHR::RM::Support::Identification::HierObjectID.new(:value => @uid.value)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def is_branch?
|
|
@@ -187,7 +187,7 @@ module OpenEHR
|
|
|
187
187
|
end
|
|
188
188
|
|
|
189
189
|
def all_versions=(all_versions)
|
|
190
|
-
if all_versions.nil? || all_versions.
|
|
190
|
+
if all_versions.nil? || all_versions.empty?
|
|
191
191
|
raise ArgumentError, 'version count invalid'
|
|
192
192
|
end
|
|
193
193
|
@all_versions = all_versions
|
|
@@ -251,12 +251,7 @@ module OpenEHR
|
|
|
251
251
|
end
|
|
252
252
|
|
|
253
253
|
def latest_trunk_version
|
|
254
|
-
trunk_versions =
|
|
255
|
-
@all_versions.each do |ver|
|
|
256
|
-
if ver.uid.version_tree_id.trunk_version == '1'
|
|
257
|
-
trunk_versions << ver
|
|
258
|
-
end
|
|
259
|
-
end
|
|
254
|
+
trunk_versions = @all_versions.reject { |ver| ver.uid.is_branch? }
|
|
260
255
|
sorted_trunk_version = trunk_versions.sort do |a,b|
|
|
261
256
|
a.commit_audit.time_committed <=> b.commit_audit.time_committed
|
|
262
257
|
end
|
|
@@ -11,11 +11,15 @@ module OpenEHR
|
|
|
11
11
|
class Folder < OpenEHR::RM::Common::Archetyped::Locatable
|
|
12
12
|
attr_accessor :items
|
|
13
13
|
attr_reader :folders
|
|
14
|
+
# details: ITEM_STRUCTURE [0..1], "Archetypable meta-data for FOLDER".
|
|
15
|
+
attr_accessor :details
|
|
16
|
+
path_attribute :folders, :items, :details
|
|
14
17
|
|
|
15
18
|
def initialize(args = { })
|
|
16
19
|
super(args)
|
|
17
20
|
self.folders = args[:folders]
|
|
18
21
|
self.items = args[:items]
|
|
22
|
+
self.details = args[:details]
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
def folders=(folders)
|
|
@@ -99,38 +99,52 @@ module OpenEHR
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
class PartyIdentified < PartyProxy
|
|
102
|
-
attr_reader :name, :
|
|
102
|
+
attr_reader :name, :identifiers
|
|
103
103
|
|
|
104
104
|
def initialize(args = { })
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
identifiers = args[:identifiers] || args[:identifier]
|
|
106
|
+
if args[:external_ref].nil? && args[:name].nil? && identifiers.nil?
|
|
107
107
|
raise ArgumentError, 'cannot identified'
|
|
108
108
|
end
|
|
109
109
|
self.name = args[:name]
|
|
110
|
-
self.
|
|
110
|
+
self.identifiers = identifiers
|
|
111
111
|
super(args)
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def name=(name)
|
|
115
|
-
if name.nil? && @external_ref.nil? && @
|
|
115
|
+
if name.nil? && @external_ref.nil? && @identifiers.nil?
|
|
116
116
|
raise ArgumentError, 'cannot identified'
|
|
117
117
|
end
|
|
118
118
|
raise ArgumentError, 'invaild name' unless name.nil? || !name.empty?
|
|
119
119
|
@name = name
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def
|
|
123
|
-
if @name.nil? && @external_ref.nil? &&
|
|
122
|
+
def identifiers=(identifiers)
|
|
123
|
+
if @name.nil? && @external_ref.nil? && identifiers.nil?
|
|
124
124
|
raise ArgumentError, 'cannot identified'
|
|
125
125
|
end
|
|
126
|
-
if !
|
|
126
|
+
if !identifiers.nil? && identifiers.empty?
|
|
127
127
|
raise ArgumentError, 'invaild identifier'
|
|
128
128
|
end
|
|
129
|
-
@
|
|
129
|
+
@identifiers = identifiers
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Deprecated: PartyIdentified's identifiers attribute is a
|
|
133
|
+
# List<DV_IDENTIFIER> per spec, so the plural #identifiers is
|
|
134
|
+
# the correct name; these singular aliases exist only for
|
|
135
|
+
# backward compatibility with earlier releases.
|
|
136
|
+
def identifier
|
|
137
|
+
warn '[DEPRECATED] PartyIdentified#identifier is deprecated; use #identifiers instead'
|
|
138
|
+
@identifiers
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def identifier=(identifier)
|
|
142
|
+
warn '[DEPRECATED] PartyIdentified#identifier= is deprecated; use #identifiers= instead'
|
|
143
|
+
self.identifiers = identifier
|
|
130
144
|
end
|
|
131
145
|
|
|
132
146
|
def external_ref=(external_ref)
|
|
133
|
-
if @name.nil? && @
|
|
147
|
+
if @name.nil? && @identifiers.nil? && external_ref.nil?
|
|
134
148
|
raise ArgumentError, 'invalid external_ref'
|
|
135
149
|
end
|
|
136
150
|
@external_ref = external_ref
|
|
@@ -48,12 +48,13 @@ module OpenEHR
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def subject_is_self?
|
|
51
|
-
return @subject.instance_of? PartySelf
|
|
51
|
+
return @subject.instance_of? OpenEHR::RM::Common::Generic::PartySelf
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
class AdminEntry < Entry
|
|
56
56
|
attr_reader :data
|
|
57
|
+
path_attribute :data
|
|
57
58
|
|
|
58
59
|
def initialize(args = { })
|
|
59
60
|
super(args)
|
|
@@ -67,6 +68,7 @@ module OpenEHR
|
|
|
67
68
|
end
|
|
68
69
|
class CareEntry < Entry
|
|
69
70
|
attr_accessor :protocol, :guideline_id
|
|
71
|
+
path_attribute :protocol
|
|
70
72
|
|
|
71
73
|
def initialize(args = { })
|
|
72
74
|
super(args)
|
|
@@ -78,6 +80,7 @@ module OpenEHR
|
|
|
78
80
|
class Observation < CareEntry
|
|
79
81
|
attr_reader :data
|
|
80
82
|
attr_accessor :state
|
|
83
|
+
path_attribute :data, :state
|
|
81
84
|
|
|
82
85
|
def initialize(args = { })
|
|
83
86
|
super(args)
|
|
@@ -93,6 +96,7 @@ module OpenEHR
|
|
|
93
96
|
|
|
94
97
|
class Evaluation < CareEntry
|
|
95
98
|
attr_reader :data
|
|
99
|
+
path_attribute :data
|
|
96
100
|
|
|
97
101
|
def initialize(args = { })
|
|
98
102
|
super(args)
|
|
@@ -108,6 +112,7 @@ module OpenEHR
|
|
|
108
112
|
class Instruction < CareEntry
|
|
109
113
|
attr_reader :narrative, :activities
|
|
110
114
|
attr_accessor :expiry_time, :wf_definition
|
|
115
|
+
path_attribute :activities
|
|
111
116
|
|
|
112
117
|
def initialize(args = { })
|
|
113
118
|
super(args)
|
|
@@ -134,6 +139,7 @@ module OpenEHR
|
|
|
134
139
|
|
|
135
140
|
class Activity < OpenEHR::RM::Common::Archetyped::Locatable
|
|
136
141
|
attr_reader :description, :timing, :action_archetype_id
|
|
142
|
+
path_attribute :description
|
|
137
143
|
|
|
138
144
|
def initialize(args = { })
|
|
139
145
|
super(args)
|
|
@@ -149,10 +155,10 @@ module OpenEHR
|
|
|
149
155
|
@description = description
|
|
150
156
|
end
|
|
151
157
|
|
|
158
|
+
# RM 1.1.0: optional - may be omitted when timing is instead
|
|
159
|
+
# represented structurally in description, or is unavailable
|
|
160
|
+
# (e.g. imported legacy data).
|
|
152
161
|
def timing=(timing)
|
|
153
|
-
if timing.nil?
|
|
154
|
-
raise ArgumentError, 'timing is mandatory'
|
|
155
|
-
end
|
|
156
162
|
@timing = timing
|
|
157
163
|
end
|
|
158
164
|
|
|
@@ -167,7 +173,8 @@ module OpenEHR
|
|
|
167
173
|
class Action < CareEntry
|
|
168
174
|
attr_reader :time, :description, :ism_transition
|
|
169
175
|
attr_accessor :instruction_details
|
|
170
|
-
|
|
176
|
+
path_attribute :description, :ism_transition, :instruction_details
|
|
177
|
+
|
|
171
178
|
def initialize(args = { })
|
|
172
179
|
super(args)
|
|
173
180
|
self.description = args[:description]
|
|
@@ -201,6 +208,7 @@ module OpenEHR
|
|
|
201
208
|
class InstructionDetails < OpenEHR::RM::Common::Archetyped::Pathable
|
|
202
209
|
attr_reader :instruction_id, :activity_id
|
|
203
210
|
attr_accessor :wf_details
|
|
211
|
+
path_attribute :wf_details
|
|
204
212
|
|
|
205
213
|
def initialize(args = { })
|
|
206
214
|
super(args)
|
|
@@ -227,18 +235,26 @@ module OpenEHR
|
|
|
227
235
|
class IsmTransition < OpenEHR::RM::Common::Archetyped::Pathable
|
|
228
236
|
attr_reader :current_state, :transition
|
|
229
237
|
attr_accessor :careflow_step
|
|
238
|
+
# RM 1.1.0: optional list of one or more reasons for this
|
|
239
|
+
# careflow step having been taken (e.g. useful in medication
|
|
240
|
+
# management, where several reasons may apply).
|
|
241
|
+
attr_accessor :reason
|
|
230
242
|
|
|
231
243
|
def initialize(args = { })
|
|
232
244
|
super(args)
|
|
233
245
|
self.current_state = args[:current_state]
|
|
234
246
|
self.transition = args[:transition]
|
|
235
247
|
self.careflow_step = args[:careflow_step]
|
|
248
|
+
self.reason = args[:reason]
|
|
236
249
|
end
|
|
237
250
|
|
|
238
251
|
def current_state=(current_state)
|
|
239
252
|
if current_state.nil?
|
|
240
253
|
raise ArgumentError, 'current_state is mandatory'
|
|
241
254
|
end
|
|
255
|
+
unless OpenEHR::TerminologyService.has_code_for_group?('ism transition current state', current_state.defining_code.code_string)
|
|
256
|
+
raise ArgumentError, 'current_state code is invalid'
|
|
257
|
+
end
|
|
242
258
|
@current_state = current_state
|
|
243
259
|
end
|
|
244
260
|
|
|
@@ -246,6 +262,9 @@ module OpenEHR
|
|
|
246
262
|
if transition.nil?
|
|
247
263
|
raise ArgumentError, 'transition is mandatory'
|
|
248
264
|
end
|
|
265
|
+
unless OpenEHR::TerminologyService.has_code_for_group?('ism transition careflow transition', transition.defining_code.code_string)
|
|
266
|
+
raise ArgumentError, 'transition code is invalid'
|
|
267
|
+
end
|
|
249
268
|
@transition = transition
|
|
250
269
|
end
|
|
251
270
|
end
|
|
@@ -9,7 +9,9 @@ module OpenEHR
|
|
|
9
9
|
module Composition
|
|
10
10
|
class Composition < OpenEHR::RM::Common::Archetyped::Locatable
|
|
11
11
|
attr_reader :language, :category, :territory, :composer
|
|
12
|
-
attr_accessor :
|
|
12
|
+
attr_accessor :context
|
|
13
|
+
attr_reader :content
|
|
14
|
+
path_attribute :content, :context
|
|
13
15
|
|
|
14
16
|
def initialize(args = { })
|
|
15
17
|
super(args)
|
|
@@ -25,6 +27,9 @@ module OpenEHR
|
|
|
25
27
|
if language.nil?
|
|
26
28
|
raise ArgumentError, 'language is mandatory'
|
|
27
29
|
end
|
|
30
|
+
unless OpenEHR::TerminologyService.valid_code?('ISO_639-1', language.code_string)
|
|
31
|
+
raise ArgumentError, 'language code is invalid'
|
|
32
|
+
end
|
|
28
33
|
@language = language
|
|
29
34
|
end
|
|
30
35
|
|
|
@@ -32,6 +37,9 @@ module OpenEHR
|
|
|
32
37
|
if category.nil?
|
|
33
38
|
raise ArgumentError, 'category is mandatory'
|
|
34
39
|
end
|
|
40
|
+
unless OpenEHR::TerminologyService.has_code_for_group?('composition category', category.defining_code.code_string)
|
|
41
|
+
raise ArgumentError, 'category code is invalid'
|
|
42
|
+
end
|
|
35
43
|
@category = category
|
|
36
44
|
end
|
|
37
45
|
|
|
@@ -49,6 +57,11 @@ module OpenEHR
|
|
|
49
57
|
@composer = composer
|
|
50
58
|
end
|
|
51
59
|
|
|
60
|
+
def content=(content)
|
|
61
|
+
content.each { |item| item.parent = self if item.respond_to?(:parent=) } if content.respond_to?(:each)
|
|
62
|
+
@content = content
|
|
63
|
+
end
|
|
64
|
+
|
|
52
65
|
def is_persistent?
|
|
53
66
|
return category.value == 'persistent'
|
|
54
67
|
end
|
|
@@ -56,7 +69,7 @@ module OpenEHR
|
|
|
56
69
|
|
|
57
70
|
class EventContext < OpenEHR::RM::Common::Archetyped::Pathable
|
|
58
71
|
attr_reader :start_time, :setting, :participations, :location
|
|
59
|
-
attr_accessor :end_time, :other_context
|
|
72
|
+
attr_accessor :end_time, :other_context, :health_care_facility
|
|
60
73
|
|
|
61
74
|
def initialize(args = { })
|
|
62
75
|
super(args)
|
|
@@ -66,6 +79,7 @@ module OpenEHR
|
|
|
66
79
|
self.participations = args[:participations]
|
|
67
80
|
self.location = args[:location]
|
|
68
81
|
self.other_context = args[:other_context]
|
|
82
|
+
self.health_care_facility = args[:health_care_facility]
|
|
69
83
|
end
|
|
70
84
|
|
|
71
85
|
def start_time=(start_time)
|
|
@@ -79,6 +93,9 @@ module OpenEHR
|
|
|
79
93
|
if setting.nil?
|
|
80
94
|
raise ArgumentError, 'setting is mandatory'
|
|
81
95
|
end
|
|
96
|
+
unless OpenEHR::TerminologyService.has_code_for_group?('event context setting', setting.defining_code.code_string)
|
|
97
|
+
raise ArgumentError, 'setting code is invalid'
|
|
98
|
+
end
|
|
82
99
|
@setting = setting
|
|
83
100
|
end
|
|
84
101
|
|