openehr 2.0.2 → 2.3.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 +19 -6
- data/lib/openehr/am/archetype/constraint_model.rb +1 -1
- data/lib/openehr/aql/engine/contains_resolver.rb +98 -7
- data/lib/openehr/aql/engine/dataset.rb +3 -1
- data/lib/openehr/aql/engine/predicate_evaluator.rb +70 -3
- data/lib/openehr/aql/engine.rb +45 -12
- data/lib/openehr/assumed_library_types.rb +20 -17
- data/lib/openehr/parser/adl_parser.rb +0 -4
- data/lib/openehr/parser/opt_parser.rb +12 -429
- data/lib/openehr/parser/xml_archetype_parser.rb +255 -0
- data/lib/openehr/parser/xml_constraint_parsing.rb +264 -0
- data/lib/openehr/parser/xml_domain_type_parsing.rb +128 -0
- data/lib/openehr/parser/xml_primitive_parsing.rb +145 -0
- data/lib/openehr/parser.rb +1 -0
- data/lib/openehr/rm/common/generic.rb +6 -9
- data/lib/openehr/rm/common/resource.rb +0 -1
- data/lib/openehr/rm/composition/content/entry.rb +4 -1
- data/lib/openehr/rm/data_structures/item_structure/representation.rb +12 -2
- data/lib/openehr/rm/data_structures/item_structure.rb +0 -1
- data/lib/openehr/rm/data_types/basic.rb +11 -21
- data/lib/openehr/rm/data_types/encapsulated.rb +18 -2
- data/lib/openehr/rm/data_types/quantity/date_time.rb +203 -28
- data/lib/openehr/rm/data_types/quantity.rb +66 -10
- data/lib/openehr/rm/data_types/time_specification.rb +101 -27
- data/lib/openehr/rm/data_types/uri.rb +0 -8
- data/lib/openehr/rm/ehr.rb +6 -6
- data/lib/openehr/rm/factory.rb +22 -0
- data/lib/openehr/rm/security.rb +3 -1
- data/lib/openehr/rm/type_name.rb +0 -1
- data/lib/openehr/serializer/adl_serializer.rb +239 -84
- data/lib/openehr/serializer/base.rb +2 -0
- data/lib/openehr/serializer/rm_json_serializer.rb +0 -1
- data/lib/openehr/serializer/xml_serializer.rb +351 -103
- data/lib/openehr/version.rb +1 -1
- metadata +9 -144
|
@@ -11,7 +11,8 @@ module OpenEHR
|
|
|
11
11
|
|
|
12
12
|
class DvOrdered < OpenEHR::RM::DataTypes::Basic::DataValue
|
|
13
13
|
include Comparable
|
|
14
|
-
attr_accessor :normal_range, :
|
|
14
|
+
attr_accessor :normal_range, :normal_status
|
|
15
|
+
attr_reader :other_reference_ranges
|
|
15
16
|
|
|
16
17
|
def initialize(args = {})
|
|
17
18
|
super(args)
|
|
@@ -236,23 +237,27 @@ module OpenEHR
|
|
|
236
237
|
unless self.is_strictly_comparable_to? other
|
|
237
238
|
raise ArgumentError, 'type mismatch'
|
|
238
239
|
end
|
|
239
|
-
|
|
240
|
-
result.magnitude = @magnitude + other.magnitude
|
|
241
|
-
return result
|
|
240
|
+
build_arithmetic_result(magnitude + other.magnitude)
|
|
242
241
|
end
|
|
243
242
|
|
|
244
243
|
def -(other)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
unless self.is_strictly_comparable_to? other
|
|
245
|
+
raise ArgumentError, 'type mismatch'
|
|
246
|
+
end
|
|
247
|
+
build_arithmetic_result(magnitude - other.magnitude)
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
def -@
|
|
251
|
-
|
|
252
|
-
negated.magnitude = -magnitude
|
|
253
|
-
negated
|
|
251
|
+
build_arithmetic_result(-magnitude)
|
|
254
252
|
end
|
|
255
253
|
|
|
254
|
+
def multiply(factor)
|
|
255
|
+
raise ArgumentError, 'factor must be Numeric' unless factor.is_a?(Numeric)
|
|
256
|
+
|
|
257
|
+
build_arithmetic_result(magnitude * factor)
|
|
258
|
+
end
|
|
259
|
+
alias * multiply
|
|
260
|
+
|
|
256
261
|
def set_accuracy(accuracy, accuracy_percent)
|
|
257
262
|
if accuracy_percent
|
|
258
263
|
raise ArgumentError, 'accuracy invalid' unless self.class.valid_percentage(accuracy)
|
|
@@ -269,6 +274,20 @@ module OpenEHR
|
|
|
269
274
|
def self.valid_percentage(number)
|
|
270
275
|
number >= 0.0 && number <= 100.0
|
|
271
276
|
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
# Overridable hook used by +/-/-@/multiply: reconstructs the
|
|
281
|
+
# result from a new magnitude via dup + magnitude=. Works for
|
|
282
|
+
# any subclass whose magnitude is ivar-backed (DvQuantity,
|
|
283
|
+
# DvCount). DvProportion's magnitude is derived from
|
|
284
|
+
# numerator/denominator instead, so it overrides +/-@/multiply
|
|
285
|
+
# directly rather than relying on this hook.
|
|
286
|
+
def build_arithmetic_result(new_magnitude)
|
|
287
|
+
result = dup
|
|
288
|
+
result.magnitude = new_magnitude
|
|
289
|
+
result
|
|
290
|
+
end
|
|
272
291
|
end
|
|
273
292
|
|
|
274
293
|
class DvQuantity < DvAmount
|
|
@@ -445,6 +464,43 @@ module OpenEHR
|
|
|
445
464
|
return false
|
|
446
465
|
end
|
|
447
466
|
end
|
|
467
|
+
|
|
468
|
+
# DvProportion's magnitude is derived from numerator/denominator
|
|
469
|
+
# rather than ivar-backed, so it cannot use DvAmount's default
|
|
470
|
+
# build_arithmetic_result (magnitude=-based reconstruction) -
|
|
471
|
+
# +/-@/multiply are overridden here at the numerator/denominator
|
|
472
|
+
# level instead. Only same-denominator addition/subtraction is
|
|
473
|
+
# well-defined without inventing a fraction-normalization
|
|
474
|
+
# policy beyond what the RM specifies.
|
|
475
|
+
def +(other)
|
|
476
|
+
unless is_strictly_comparable_to?(other) && denominator == other.denominator
|
|
477
|
+
raise ArgumentError, 'type mismatch: proportions must share type and denominator'
|
|
478
|
+
end
|
|
479
|
+
build_arithmetic_result(numerator + other.numerator)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def -(other)
|
|
483
|
+
self + (-other)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def -@
|
|
487
|
+
build_arithmetic_result(-numerator)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def multiply(factor)
|
|
491
|
+
raise ArgumentError, 'factor must be Numeric' unless factor.is_a?(Numeric)
|
|
492
|
+
|
|
493
|
+
build_arithmetic_result(numerator * factor)
|
|
494
|
+
end
|
|
495
|
+
alias * multiply
|
|
496
|
+
|
|
497
|
+
private
|
|
498
|
+
|
|
499
|
+
def build_arithmetic_result(new_numerator)
|
|
500
|
+
result = dup
|
|
501
|
+
result.numerator = new_numerator
|
|
502
|
+
result
|
|
503
|
+
end
|
|
448
504
|
end # end of DvProportion
|
|
449
505
|
end # of Quantity
|
|
450
506
|
end # of Data_Types
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# This module is related to the ticket #47
|
|
2
2
|
require_relative 'basic'
|
|
3
|
+
require_relative 'quantity/date_time'
|
|
3
4
|
|
|
4
5
|
module OpenEHR
|
|
5
6
|
module RM
|
|
@@ -8,19 +9,24 @@ module OpenEHR
|
|
|
8
9
|
class DvTimeSpecification < OpenEHR::RM::DataTypes::Basic::DataValue
|
|
9
10
|
attr_reader :value
|
|
10
11
|
|
|
11
|
-
def initialize(value)
|
|
12
|
-
self.value=(value)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
12
|
def value=(value)
|
|
16
13
|
raise ArgumentError, 'value must be not nil' if value.nil?
|
|
14
|
+
unless value.respond_to?(:formalism) && value.respond_to?(:value)
|
|
15
|
+
raise ArgumentError, 'value must be a DV_PARSABLE'
|
|
16
|
+
end
|
|
17
17
|
@value = value
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
def calendar_alignment
|
|
21
21
|
raise NotImplementedError, "calendar_alignment must be implemented"
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
# Legacy misspelling, deliberately kept (pinned by spec). Defined
|
|
25
|
+
# as a delegating method rather than `alias` so a subclass
|
|
26
|
+
# override of calendar_alignment is still reached through it.
|
|
27
|
+
def calender_alignment
|
|
28
|
+
calendar_alignment
|
|
29
|
+
end
|
|
24
30
|
|
|
25
31
|
def event_alignment
|
|
26
32
|
raise NotImplementedError, "event_alignment must be implemented"
|
|
@@ -35,39 +41,107 @@ module OpenEHR
|
|
|
35
41
|
# because I could not obtain HL7 specification related them.
|
|
36
42
|
|
|
37
43
|
class DvGeneralTimeSpecification < DvTimeSpecification
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
super(value)
|
|
41
|
-
end
|
|
44
|
+
FORMALISM = 'HL7:GTS'
|
|
45
|
+
|
|
42
46
|
def value=(value)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def value_valid(value)
|
|
47
|
+
unless value.respond_to?(:formalism) && value.formalism == FORMALISM
|
|
48
|
+
raise ArgumentError, "formalism must be #{FORMALISM}"
|
|
49
|
+
end
|
|
50
|
+
super
|
|
48
51
|
end
|
|
52
|
+
# calendar_alignment / event_alignment / institution_specified
|
|
53
|
+
# remain NotImplementedError (inherited from DvTimeSpecification):
|
|
54
|
+
# deriving them requires parsing the full HL7 GTS set-algebra
|
|
55
|
+
# (union/intersection/exclusion of interval sets), which is
|
|
56
|
+
# explicitly out of scope here.
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
class DvPeriodicTimeSpecification < DvTimeSpecification
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
PIVL_FORMALISM = 'HL7:PIVL'
|
|
61
|
+
EIVL_FORMALISM = 'HL7:EIVL'
|
|
62
|
+
|
|
63
|
+
# Single-literal PIVL: [phase_low(;phase_high)?]/(period)@ALIGN? IST?
|
|
64
|
+
# e.g. "[200004181100;200004181110]/(7d)@DW IST"
|
|
65
|
+
PIVL_PATTERN = %r{\A\[(?<phase_low>\d+)(?:;(?<phase_high>\d+))?\]/
|
|
66
|
+
\((?<period>\d+(?:\.\d+)?)(?<unit>[a-z]+)\)
|
|
67
|
+
(?:@(?<alignment>[A-Z]{2}))?(?<ist>\ ?IST)?\z}x
|
|
68
|
+
|
|
69
|
+
# Single-literal EIVL: EVENT([+-]offset)? e.g. "ACM+10min"
|
|
70
|
+
EIVL_PATTERN = /\A(?<event>[A-Z]+)(?:(?<sign>[+-])(?<offset>\d+(?:\.\d+)?)(?<unit>[a-z]+))?\z/
|
|
71
|
+
|
|
72
|
+
# HL7 CalendarCycle codes (DW=day of week, HD=hour of day, ...)
|
|
73
|
+
CALENDAR_CYCLE_CODES = %w[CY MY CM CW WY DM CD DY DW HD CH NH CN SN CS].freeze
|
|
74
|
+
# HL7 TimingEvent codes (HS=bedtime, ACM=before breakfast, ...)
|
|
75
|
+
TIMING_EVENT_CODES = %w[HS WAKE C CM CD CV AC ACM ACD ACV PC PCM PCD PCV].freeze
|
|
76
|
+
# HL7 physical-quantity time units -> ISO8601 duration templates
|
|
77
|
+
PERIOD_UNIT_TO_ISO8601 = { 's' => 'PT%sS', 'min' => 'PT%sM', 'h' => 'PT%sH',
|
|
78
|
+
'd' => 'P%sD', 'wk' => 'P%sW', 'mo' => 'P%sM',
|
|
79
|
+
'a' => 'P%sY' }.freeze
|
|
80
|
+
|
|
81
|
+
attr_reader :period, :calendar_alignment, :event_alignment
|
|
82
|
+
|
|
57
83
|
def value=(value)
|
|
58
|
-
unless value.
|
|
59
|
-
|
|
84
|
+
unless value.respond_to?(:formalism) &&
|
|
85
|
+
[PIVL_FORMALISM, EIVL_FORMALISM].include?(value.formalism)
|
|
86
|
+
raise ArgumentError, "formalism must be #{PIVL_FORMALISM} or #{EIVL_FORMALISM}"
|
|
87
|
+
end
|
|
88
|
+
if value.formalism == PIVL_FORMALISM
|
|
89
|
+
parse_pivl(value.value)
|
|
90
|
+
else
|
|
91
|
+
parse_eivl(value.value)
|
|
60
92
|
end
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def institution_specified
|
|
97
|
+
@institution_specified
|
|
98
|
+
end
|
|
99
|
+
alias institution_specified? institution_specified
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def parse_pivl(raw)
|
|
104
|
+
match = PIVL_PATTERN.match(raw)
|
|
105
|
+
raise ArgumentError, 'value does not match HL7:PIVL syntax' unless match
|
|
106
|
+
|
|
107
|
+
unit = match[:unit]
|
|
108
|
+
unless PERIOD_UNIT_TO_ISO8601.key?(unit)
|
|
109
|
+
raise ArgumentError, "unknown HL7 PIVL period unit #{unit.inspect}"
|
|
64
110
|
end
|
|
65
|
-
|
|
111
|
+
|
|
112
|
+
alignment = match[:alignment]
|
|
113
|
+
if alignment && !CALENDAR_CYCLE_CODES.include?(alignment)
|
|
114
|
+
raise ArgumentError, "unknown HL7 CalendarCycle code #{alignment.inspect}"
|
|
66
115
|
end
|
|
116
|
+
|
|
117
|
+
@period = DataTypes::Quantity::DateTime::DvDuration.new(:value => period_from(match[:period], unit))
|
|
118
|
+
@calendar_alignment = alignment
|
|
119
|
+
@event_alignment = nil
|
|
120
|
+
@institution_specified = !match[:ist].nil?
|
|
67
121
|
end
|
|
68
122
|
|
|
69
|
-
def
|
|
123
|
+
def parse_eivl(raw)
|
|
124
|
+
match = EIVL_PATTERN.match(raw)
|
|
125
|
+
raise ArgumentError, 'value does not match HL7:EIVL syntax' unless match
|
|
126
|
+
|
|
127
|
+
event = match[:event]
|
|
128
|
+
unless TIMING_EVENT_CODES.include?(event)
|
|
129
|
+
raise ArgumentError, "unknown HL7 TimingEvent code #{event.inspect}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
unit = match[:unit]
|
|
133
|
+
if unit && !PERIOD_UNIT_TO_ISO8601.key?(unit)
|
|
134
|
+
raise ArgumentError, "unknown HL7 EIVL offset unit #{unit.inspect}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@event_alignment = event
|
|
138
|
+
@calendar_alignment = nil
|
|
139
|
+
@period = nil
|
|
140
|
+
@institution_specified = false
|
|
141
|
+
end
|
|
70
142
|
|
|
143
|
+
def period_from(quantity, unit)
|
|
144
|
+
format(PERIOD_UNIT_TO_ISO8601[unit], quantity)
|
|
71
145
|
end
|
|
72
146
|
end
|
|
73
147
|
end
|
|
@@ -31,10 +31,6 @@ module OpenEHR
|
|
|
31
31
|
module DataTypes
|
|
32
32
|
module URI
|
|
33
33
|
class DvUri < OpenEHR::RM::DataTypes::Basic::DataValue
|
|
34
|
-
def initialize(args = {})
|
|
35
|
-
self.value = args[:value]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
34
|
def value
|
|
39
35
|
@value
|
|
40
36
|
end
|
|
@@ -69,10 +65,6 @@ module OpenEHR
|
|
|
69
65
|
end
|
|
70
66
|
|
|
71
67
|
class DvEhrUri < DvUri
|
|
72
|
-
def initialize(value)
|
|
73
|
-
super
|
|
74
|
-
end
|
|
75
|
-
|
|
76
68
|
def value=(value)
|
|
77
69
|
raise ArgumentError, "scheme must be ehr" if !(value =~ /^ehr/i)
|
|
78
70
|
parse(value)
|
data/lib/openehr/rm/ehr.rb
CHANGED
|
@@ -45,6 +45,8 @@ module OpenEHR
|
|
|
45
45
|
@time_created = time_created
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# contributions is optional (0..1) since RM 1.1.0 (was mandatory
|
|
49
|
+
# pre-1.1.0).
|
|
48
50
|
def contributions=(contributions)
|
|
49
51
|
unless contributions.nil?
|
|
50
52
|
contributions.each do |contrib|
|
|
@@ -52,10 +54,8 @@ module OpenEHR
|
|
|
52
54
|
raise ArgumentError, 'contribution type should be CONTRIBUTION'
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
|
-
@contributions = contributions
|
|
56
|
-
else
|
|
57
|
-
raise ArgumentError, 'contributions are mandatory'
|
|
58
57
|
end
|
|
58
|
+
@contributions = contributions
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def ehr_access=(ehr_access)
|
|
@@ -72,6 +72,8 @@ module OpenEHR
|
|
|
72
72
|
@ehr_status = ehr_status
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# compositions is optional (0..1) since RM 1.1.0 (was mandatory
|
|
76
|
+
# pre-1.1.0).
|
|
75
77
|
def compositions=(compositions)
|
|
76
78
|
unless compositions.nil?
|
|
77
79
|
compositions.each do |compo|
|
|
@@ -79,10 +81,8 @@ module OpenEHR
|
|
|
79
81
|
raise ArgumentError, 'composition type should be VERSIONED_COMPOSITION'
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
|
-
@compositions = compositions
|
|
83
|
-
else
|
|
84
|
-
raise ArgumentError, 'compositions are mandatory'
|
|
85
84
|
end
|
|
85
|
+
@compositions = compositions
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def directory=(directory)
|
data/lib/openehr/rm/factory.rb
CHANGED
|
@@ -277,6 +277,24 @@ module OpenEHR
|
|
|
277
277
|
end
|
|
278
278
|
end
|
|
279
279
|
|
|
280
|
+
class DvTimeSpecificationFactory
|
|
281
|
+
def self.create(param)
|
|
282
|
+
DataTypes::TimeSpecification::DvTimeSpecification.new(param)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class DvGeneralTimeSpecificationFactory
|
|
287
|
+
def self.create(param)
|
|
288
|
+
DataTypes::TimeSpecification::DvGeneralTimeSpecification.new(param)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
class DvPeriodicTimeSpecificationFactory
|
|
293
|
+
def self.create(param)
|
|
294
|
+
DataTypes::TimeSpecification::DvPeriodicTimeSpecification.new(param)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
280
298
|
class ObservationFactory
|
|
281
299
|
def self.create(param)
|
|
282
300
|
Composition::Content::Entry::Observation.new(param)
|
|
@@ -579,6 +597,10 @@ module OpenEHR
|
|
|
579
597
|
|
|
580
598
|
class CompositionFactory < Factory
|
|
581
599
|
class << self
|
|
600
|
+
def create(param)
|
|
601
|
+
OpenEHR::RM::Composition::Composition.new(param)
|
|
602
|
+
end
|
|
603
|
+
|
|
582
604
|
def create_from_json(json)
|
|
583
605
|
hash = JSON.parse(json, max_nesting: false, symbolize_names: true)
|
|
584
606
|
OpenEHR::RM::Composition::Composition.new(params(hash))
|
data/lib/openehr/rm/security.rb
CHANGED
data/lib/openehr/rm/type_name.rb
CHANGED