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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +19 -6
  3. data/lib/openehr/am/archetype/constraint_model.rb +1 -1
  4. data/lib/openehr/aql/engine/contains_resolver.rb +98 -7
  5. data/lib/openehr/aql/engine/dataset.rb +3 -1
  6. data/lib/openehr/aql/engine/predicate_evaluator.rb +70 -3
  7. data/lib/openehr/aql/engine.rb +45 -12
  8. data/lib/openehr/assumed_library_types.rb +20 -17
  9. data/lib/openehr/parser/adl_parser.rb +0 -4
  10. data/lib/openehr/parser/opt_parser.rb +12 -429
  11. data/lib/openehr/parser/xml_archetype_parser.rb +255 -0
  12. data/lib/openehr/parser/xml_constraint_parsing.rb +264 -0
  13. data/lib/openehr/parser/xml_domain_type_parsing.rb +128 -0
  14. data/lib/openehr/parser/xml_primitive_parsing.rb +145 -0
  15. data/lib/openehr/parser.rb +1 -0
  16. data/lib/openehr/rm/common/generic.rb +6 -9
  17. data/lib/openehr/rm/common/resource.rb +0 -1
  18. data/lib/openehr/rm/composition/content/entry.rb +4 -1
  19. data/lib/openehr/rm/data_structures/item_structure/representation.rb +12 -2
  20. data/lib/openehr/rm/data_structures/item_structure.rb +0 -1
  21. data/lib/openehr/rm/data_types/basic.rb +11 -21
  22. data/lib/openehr/rm/data_types/encapsulated.rb +18 -2
  23. data/lib/openehr/rm/data_types/quantity/date_time.rb +203 -28
  24. data/lib/openehr/rm/data_types/quantity.rb +66 -10
  25. data/lib/openehr/rm/data_types/time_specification.rb +101 -27
  26. data/lib/openehr/rm/data_types/uri.rb +0 -8
  27. data/lib/openehr/rm/ehr.rb +6 -6
  28. data/lib/openehr/rm/factory.rb +22 -0
  29. data/lib/openehr/rm/security.rb +3 -1
  30. data/lib/openehr/rm/type_name.rb +0 -1
  31. data/lib/openehr/serializer/adl_serializer.rb +239 -84
  32. data/lib/openehr/serializer/base.rb +2 -0
  33. data/lib/openehr/serializer/rm_json_serializer.rb +0 -1
  34. data/lib/openehr/serializer/xml_serializer.rb +351 -103
  35. data/lib/openehr/version.rb +1 -1
  36. 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, :other_reference_ranges, :normal_status
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
- result = self.dup
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
- negated = other.dup
246
- negated.magnitude = -other.magnitude
247
- self + negated
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
- negated = self.dup
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
- alias calender_alignment calendar_alignment
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
- attr_reader :value
39
- def initialize(value)
40
- super(value)
41
- end
44
+ FORMALISM = 'HL7:GTS'
45
+
42
46
  def value=(value)
43
- raise ArgumentError, "value is not valied" unless value.formalism.is_equal?('HL7:GTS')
44
- @value = value
45
- end
46
- private
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
- attr_reader :value, :calender_alignment, :event_alingment, :period
53
- def initialize(value)
54
- value_valid(value)
55
- super(value)
56
- end
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.formalism.is_equal('HL7:PIVL') or value.formalism.is_equal('HL7:EIVL')
59
- raise ArgumentError, "value is not valid"
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
- if value.formalism('HL7:PIVL')
62
- /^\[(\d+)\;?(\d+)?\]\/\((\d+\w+)\)(@(\w+?))?(IST)?$/ =~ value
63
- interval1, interval2, difference, allignment = $1, $2, $3, $5
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
- if value
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 institution_specified?
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)
@@ -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)
@@ -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))
@@ -5,7 +5,9 @@ module OpenEHR
5
5
  module RM
6
6
  module Security
7
7
  class AccessControlSettings
8
-
8
+ # Deliberate stub: openEHR RM 1.0.1 Security package class (see
9
+ # UML reference above); no behavior is specified for it beyond
10
+ # existence.
9
11
  end
10
12
  end
11
13
  end
@@ -1,4 +1,3 @@
1
- require 'set'
2
1
  require 'active_support/core_ext/string/inflections'
3
2
 
4
3
  module OpenEHR