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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f34e667ae737ff2718bb8d2e676d75711b0753fd2fb39f64cba6e6eb3bef59a
|
|
4
|
+
data.tar.gz: bfede641c16301632b2a3647727a10036dff325b2e8bed288bbca1aa820e075c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1eabc54006e29b679f16d9d085f097ddb808037a97190611331161b435d1dc77fd7f53ae08f686eda74c3186094a0a15f45351e5fc4d1e1079c13029a10be4e9
|
|
7
|
+
data.tar.gz: 035d2e2a083d64cb84984c20c7362c4cb8a40912406855a9472c0d1fc240ec43e97547bd17fa83f29f3026b7d55483106b53691f45ceefd813ef858f36026fef
|
data/README.rdoc
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{<img src="https://
|
|
1
|
+
{<img src="https://github.com/skoba/openehr-ruby/actions/workflows/ci.yml/badge.svg" alt="CI" />}[https://github.com/skoba/openehr-ruby/actions/workflows/ci.yml]
|
|
2
2
|
=Synopsis
|
|
3
3
|
|
|
4
4
|
A Ruby implementation of the openEHR specifications
|
|
@@ -7,17 +7,17 @@ A Ruby implementation of the openEHR specifications
|
|
|
7
7
|
|
|
8
8
|
= Requirements
|
|
9
9
|
|
|
10
|
-
* Supports CRuby
|
|
11
|
-
* Developed with CRuby 3.
|
|
12
|
-
* Ruby
|
|
10
|
+
* Supports CRuby 3.1 or later.
|
|
11
|
+
* Developed with CRuby 3.4 on Linux; CI covers 3.1-3.4.
|
|
12
|
+
* Ruby 3.0 or earlier are no longer supported.
|
|
13
13
|
|
|
14
14
|
=Description
|
|
15
15
|
|
|
16
16
|
This is a stable release of Ruby openEHR implementation project.
|
|
17
|
-
This release
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
This release targets openEHR specification release 1.0.2 as its
|
|
18
|
+
baseline, with a growing set of RM 1.1.0 / BASE 1.2.0 deltas layered on
|
|
19
|
+
top (see "RM 1.0.2 vs RM 1.1.0 semantics" below for exactly which
|
|
20
|
+
classes/attributes are 1.1.0-only). Formerly, we named this package
|
|
21
21
|
as open_ehr, but changed to openehr from release 1.1.0. 1.0.x versions
|
|
22
22
|
are obsoleted.
|
|
23
23
|
|
|
@@ -25,12 +25,18 @@ The intention is to have a sample EHR to deploy quickly with Ruby on Rails for d
|
|
|
25
25
|
|
|
26
26
|
This package includes:
|
|
27
27
|
* ADL 1.4 parser
|
|
28
|
-
* AM/RM packages based on the openEHR specification 1.0.2
|
|
28
|
+
* AM/RM packages based on the openEHR specification 1.0.2, plus RM 1.1.0 deltas
|
|
29
|
+
* ADL/XML/JSON serializers and an ADL archetype validator
|
|
30
|
+
* AQL (Archetype Query Language): a parser plus an in-memory execution
|
|
31
|
+
engine that evaluates queries directly against RM object graphs you
|
|
32
|
+
already have in memory (see "Querying with AQL" below)
|
|
29
33
|
|
|
30
34
|
This package excludes:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
35
|
+
* An XML archetype parser (in progress)
|
|
36
|
+
* Persistent data stores and search indexes - AQL here only ever
|
|
37
|
+
queries in-memory RM object graphs; the moment your data lives in a
|
|
38
|
+
database, that integration (e.g. openehr-rails) owns fetching it out
|
|
39
|
+
and this gem's AQL engine stays completely unaware of how
|
|
34
40
|
* Terminology service(moved to openehr-terminology package)
|
|
35
41
|
* Rails plugin(moved to openehr-rails package
|
|
36
42
|
|
|
@@ -43,6 +49,81 @@ and so on. We have therefore postponed implementations of these classes.
|
|
|
43
49
|
Terminology and Demographic servers will be implemented in other
|
|
44
50
|
projects.
|
|
45
51
|
|
|
52
|
+
=RM 1.0.2 vs RM 1.1.0 semantics
|
|
53
|
+
|
|
54
|
+
This gem's baseline is openEHR RM 1.0.2, with the following RM 1.1.0 /
|
|
55
|
+
BASE 1.2.0 deltas incorporated so far. Everything not listed here keeps
|
|
56
|
+
its 1.0.2 meaning; anything listed only exists, or only behaves this
|
|
57
|
+
way, from RM 1.1.0 onward.
|
|
58
|
+
|
|
59
|
+
Class / attribute RM version Notes
|
|
60
|
+
------------------------------------------ ---------- ---------------------------------------------
|
|
61
|
+
DV_SCALE (new class) 1.1.0 Like DV_ORDINAL, but Real-valued (SPECRM-19)
|
|
62
|
+
DV_QUANTITY.units_system 1.1.0 Optional; identifies a non-UCUM units system
|
|
63
|
+
DV_QUANTITY.units_display_name 1.1.0 Optional displayable form of units (SPECRM-65)
|
|
64
|
+
CODE_PHRASE.preferred_term 1.1.0 Optional preferred term for code_string
|
|
65
|
+
DV_PARAGRAPH deprecated Deprecated since RM 1.0.4; still legal, warns
|
|
66
|
+
DV_DURATION negative values ("-P10D") 1.1.0 Consistently supported (SPECRM-96)
|
|
67
|
+
EHR.folders 1.1.0 List<OBJECT_REF>; directory = folders.item(1)
|
|
68
|
+
FOLDER.details 1.1.0 Optional ITEM_STRUCTURE meta-data
|
|
69
|
+
FEEDER_AUDIT_DETAILS.other_details 1.1.0 Optional ITEM_STRUCTURE meta-data (SPECRM-74)
|
|
70
|
+
ACTIVITY.timing 1.1.0 Now optional (was mandatory pre-1.1.0)
|
|
71
|
+
ISM_TRANSITION.reason 1.1.0 Optional List<DV_TEXT>
|
|
72
|
+
COMPOSITION.category = "episodic" 1.1.0 A terminology value, not a code change (SPECRM-89);
|
|
73
|
+
already supported via the pluggable
|
|
74
|
+
OpenEHR::TerminologyService seam
|
|
75
|
+
|
|
76
|
+
Not yet incorporated from RM 1.1.0 / BASE 1.2.0: ITEM_TAG (still in a
|
|
77
|
+
development branch, not yet in a released RM version) and the finer
|
|
78
|
+
points of DV_DURATION arithmetic beyond negation/unary minus.
|
|
79
|
+
|
|
80
|
+
=Querying with AQL
|
|
81
|
+
|
|
82
|
+
query = OpenEHR::AQL.parse(aql_string) # => a parsed, immutable Query
|
|
83
|
+
result = query.execute(dataset, params: { ehr_id: '...' }) # => a ResultSet (Enumerable, plus #to_json)
|
|
84
|
+
result = OpenEHR::AQL.execute(aql_string, dataset, params: {}) # one-shot
|
|
85
|
+
|
|
86
|
+
The engine evaluates queries against <tt>OpenEHR::AQL::Dataset</tt>, its
|
|
87
|
+
one and only input boundary - and the whole of its independence from
|
|
88
|
+
any particular web framework or O/R mapper. Everything under
|
|
89
|
+
<tt>lib/openehr/aql/</tt> talks to "wherever your data lives"
|
|
90
|
+
exclusively through <tt>Dataset</tt>, which itself depends on nothing
|
|
91
|
+
but Ruby's own <tt>Enumerable</tt>/<tt>#each</tt> protocol and this
|
|
92
|
+
gem's own RM classes - no ActiveRecord, no Rails, no Sequel, anywhere
|
|
93
|
+
in this gem, ever.
|
|
94
|
+
|
|
95
|
+
==Supplying data to AQL
|
|
96
|
+
|
|
97
|
+
<tt>Dataset</tt> accepts any Enumerable of records shaped like
|
|
98
|
+
<tt>{ehr_id:, compositions:, ehr_status: (optional)}</tt> (a Hash with
|
|
99
|
+
symbol or string keys, or a duck-typed object responding to the same
|
|
100
|
+
methods), or a full <tt>OpenEHR::RM::EHR::EHR</tt> if you already have
|
|
101
|
+
one. Every element yielded by a record's <tt>compositions</tt> must be
|
|
102
|
+
one of this gem's own RM objects (<tt>is_a?(OpenEHR::RM::Common::Archetyped::Pathable)</tt>) -
|
|
103
|
+
anything else raises <tt>OpenEHR::AQL::DatasetError</tt> immediately,
|
|
104
|
+
naming the offending class and index, rather than half-working via
|
|
105
|
+
loose duck typing.
|
|
106
|
+
|
|
107
|
+
Construction never iterates its source, so a lazily-produced
|
|
108
|
+
Enumerable stays lazy all the way through <tt>execute</tt>. A
|
|
109
|
+
hypothetical Rails + ActiveRecord integration (e.g. openehr-rails)
|
|
110
|
+
might look like this - note this is entirely outside this gem, shown
|
|
111
|
+
only to make the boundary concrete:
|
|
112
|
+
|
|
113
|
+
records = Ehr.find_each.lazy.map { |row|
|
|
114
|
+
{ ehr_id: row.uid,
|
|
115
|
+
compositions: row.composition_records.lazy.map { |cr|
|
|
116
|
+
OpenEHR::RM::CompositionFactory.create_from_json(cr.canonical_json) } } }
|
|
117
|
+
OpenEHR::AQL.execute(aql, OpenEHR::AQL::Dataset.new(ehrs: records))
|
|
118
|
+
|
|
119
|
+
Swapping to Sinatra + Sequel, or any other framework/ORM, only changes
|
|
120
|
+
the two <tt>.map</tt> bodies above - nothing in this gem's AQL code
|
|
121
|
+
needs to know or care. For quick, non-database scripts:
|
|
122
|
+
|
|
123
|
+
c1 = OpenEHR::RM::CompositionFactory.create_from_json(composition_json) # or build one by hand
|
|
124
|
+
OpenEHR::AQL.execute(aql, OpenEHR::AQL::Dataset.of_compositions([c1, c2]))
|
|
125
|
+
# or even just: OpenEHR::AQL.execute(aql, [c1, c2]) # via Dataset.wrap
|
|
126
|
+
|
|
46
127
|
=Main codebase authors
|
|
47
128
|
* Shinji Kobayashi and Akimichi Tatsukawa of http://openehr.jp
|
|
48
129
|
* ADL parser test cases are derived from Java and Eiffel reference implementation project of the openEHR.
|
|
@@ -61,6 +61,10 @@ module OpenEHR
|
|
|
61
61
|
return @false_valid
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def valid_value?(value)
|
|
65
|
+
(value == true && true_valid?) || (value == false && false_valid?)
|
|
66
|
+
end
|
|
67
|
+
|
|
64
68
|
private
|
|
65
69
|
def binary_consistency(true_valid, false_valid)
|
|
66
70
|
if (true_valid == false) && (false_valid == false)
|
|
@@ -77,6 +81,7 @@ module OpenEHR
|
|
|
77
81
|
|
|
78
82
|
class CString < CPrimitive
|
|
79
83
|
attr_reader :pattern, :list
|
|
84
|
+
attr_accessor :list_open
|
|
80
85
|
|
|
81
86
|
def initialize(args = { })
|
|
82
87
|
args[:type] = 'String'
|
|
@@ -84,6 +89,7 @@ module OpenEHR
|
|
|
84
89
|
consistency(args[:pattern], args[:list])
|
|
85
90
|
@pattern = args[:pattern]
|
|
86
91
|
@list = args[:list]
|
|
92
|
+
@list_open = args[:list_open]
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
def pattern=(pattern)
|
|
@@ -96,9 +102,20 @@ module OpenEHR
|
|
|
96
102
|
@list = list
|
|
97
103
|
end
|
|
98
104
|
|
|
105
|
+
def valid_value?(value)
|
|
106
|
+
return false unless value.is_a?(String)
|
|
107
|
+
return true if pattern.nil? && list.nil?
|
|
108
|
+
return list.include?(value) if list
|
|
109
|
+
|
|
110
|
+
Regexp.new(pattern).match?(value)
|
|
111
|
+
end
|
|
112
|
+
|
|
99
113
|
private
|
|
114
|
+
# C_STRING may be fully unconstrained (neither pattern nor
|
|
115
|
+
# list, i.e. any_allowed); only having both set
|
|
116
|
+
# simultaneously is a genuine contradiction.
|
|
100
117
|
def consistency(pattern, list)
|
|
101
|
-
if pattern.nil?
|
|
118
|
+
if !pattern.nil? && !list.nil?
|
|
102
119
|
raise ArgumentError, 'consistency invaild'
|
|
103
120
|
end
|
|
104
121
|
end
|
|
@@ -125,12 +142,28 @@ module OpenEHR
|
|
|
125
142
|
@range = range
|
|
126
143
|
end
|
|
127
144
|
|
|
145
|
+
def valid_value?(value)
|
|
146
|
+
return false unless value.is_a?(Integer)
|
|
147
|
+
|
|
148
|
+
constrained_value?(value)
|
|
149
|
+
end
|
|
150
|
+
|
|
128
151
|
private
|
|
152
|
+
# C_INTEGER may be fully unconstrained (neither list nor range,
|
|
153
|
+
# i.e. any_allowed); only having both set simultaneously is a
|
|
154
|
+
# genuine contradiction.
|
|
129
155
|
def consistency(list, range)
|
|
130
|
-
if list.nil?
|
|
156
|
+
if !list.nil? && !range.nil?
|
|
131
157
|
raise ArgumentError, 'consistency invalid'
|
|
132
158
|
end
|
|
133
159
|
end
|
|
160
|
+
|
|
161
|
+
def constrained_value?(value)
|
|
162
|
+
return true if list.nil? && range.nil?
|
|
163
|
+
return list.include?(value) if list
|
|
164
|
+
|
|
165
|
+
range.has?(value)
|
|
166
|
+
end
|
|
134
167
|
end
|
|
135
168
|
|
|
136
169
|
class CReal < CInteger
|
|
@@ -138,11 +171,17 @@ module OpenEHR
|
|
|
138
171
|
args[:type] = 'Real'
|
|
139
172
|
super
|
|
140
173
|
end
|
|
174
|
+
|
|
175
|
+
def valid_value?(value)
|
|
176
|
+
return false unless value.is_a?(Numeric)
|
|
177
|
+
|
|
178
|
+
constrained_value?(value)
|
|
179
|
+
end
|
|
141
180
|
end
|
|
142
181
|
|
|
143
182
|
module CDateModule
|
|
144
183
|
attr_accessor :timezone_validity, :day_validity, :range, :list
|
|
145
|
-
attr_reader :month_validity, :
|
|
184
|
+
attr_reader :month_validity, :pattern
|
|
146
185
|
|
|
147
186
|
def month_validity=(month_validity)
|
|
148
187
|
if (month_validity == ValidityKind::OPTIONAL &&
|
|
@@ -163,6 +202,13 @@ module OpenEHR
|
|
|
163
202
|
return !@range.nil?
|
|
164
203
|
end
|
|
165
204
|
|
|
205
|
+
def valid_value?(value)
|
|
206
|
+
date = as_date(value)
|
|
207
|
+
return false if date.nil?
|
|
208
|
+
|
|
209
|
+
date_fields_valid?(date) && constrained_date?(date)
|
|
210
|
+
end
|
|
211
|
+
|
|
166
212
|
protected
|
|
167
213
|
def valid_pattern?(pattern)
|
|
168
214
|
if /^([Yy?X]{4})(-([Mm?X]{2})(-([Dd?X]{2}))?)?$/ =~ pattern
|
|
@@ -172,9 +218,43 @@ module OpenEHR
|
|
|
172
218
|
end
|
|
173
219
|
end
|
|
174
220
|
|
|
221
|
+
def date_fields_valid?(date)
|
|
222
|
+
validity_satisfied?(month_validity, !date.month_unknown?) &&
|
|
223
|
+
validity_satisfied?(day_validity, !date.day_unknown?)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def validity_satisfied?(validity, field_present)
|
|
227
|
+
case validity
|
|
228
|
+
when ValidityKind::DISALLOWED then !field_present
|
|
229
|
+
when ValidityKind::MANDATORY then field_present
|
|
230
|
+
else
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
175
235
|
private
|
|
176
236
|
def consistency_validity(month_validity, day_validity)
|
|
177
237
|
end
|
|
238
|
+
|
|
239
|
+
# A String value is parsed as a DV_DATE (matching the
|
|
240
|
+
# concrete type this codebase's range/list bounds actually
|
|
241
|
+
# use, so Comparable/Interval#has? compare like with like);
|
|
242
|
+
# a malformed string surfaces as ArgumentError, caught below.
|
|
243
|
+
def as_date(value)
|
|
244
|
+
return value if value.respond_to?(:month_unknown?)
|
|
245
|
+
return nil unless value.is_a?(String)
|
|
246
|
+
|
|
247
|
+
OpenEHR::RM::DataTypes::Quantity::DateTime::DvDate.new(:value => value)
|
|
248
|
+
rescue ArgumentError
|
|
249
|
+
nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def constrained_date?(date)
|
|
253
|
+
return true if range.nil? && list.nil?
|
|
254
|
+
return list.any? { |item| as_date(item) == date } if list
|
|
255
|
+
|
|
256
|
+
range.has?(date)
|
|
257
|
+
end
|
|
178
258
|
end
|
|
179
259
|
|
|
180
260
|
class CDate < CPrimitive
|
|
@@ -212,7 +292,7 @@ module OpenEHR
|
|
|
212
292
|
end
|
|
213
293
|
|
|
214
294
|
module CTimeModule
|
|
215
|
-
attr_accessor :range, :list
|
|
295
|
+
attr_accessor :range, :list, :timezone_validity
|
|
216
296
|
attr_reader :minute_validity, :second_validity,
|
|
217
297
|
:millisecond_validity, :pattern
|
|
218
298
|
def pattern=(pattern)
|
|
@@ -247,6 +327,13 @@ module OpenEHR
|
|
|
247
327
|
return !@range.nil?
|
|
248
328
|
end
|
|
249
329
|
|
|
330
|
+
def valid_value?(value)
|
|
331
|
+
time = as_time(value)
|
|
332
|
+
return false if time.nil?
|
|
333
|
+
|
|
334
|
+
time_fields_valid?(time) && constrained_time?(time)
|
|
335
|
+
end
|
|
336
|
+
|
|
250
337
|
protected
|
|
251
338
|
def valid_pattern?(pattern)
|
|
252
339
|
if /^([Hh?X]{2})(:([Mm?X]{2})(:([Ss?X]{2}))?)?$/ =~ pattern
|
|
@@ -255,6 +342,43 @@ module OpenEHR
|
|
|
255
342
|
false
|
|
256
343
|
end
|
|
257
344
|
end
|
|
345
|
+
|
|
346
|
+
def time_fields_valid?(time)
|
|
347
|
+
validity_satisfied?(minute_validity, !time.minute_unknown?) &&
|
|
348
|
+
validity_satisfied?(second_validity, !time.second_unknown?) &&
|
|
349
|
+
validity_satisfied?(millisecond_validity, time.has_fractional_second?)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def validity_satisfied?(validity, field_present)
|
|
353
|
+
case validity
|
|
354
|
+
when ValidityKind::DISALLOWED then !field_present
|
|
355
|
+
when ValidityKind::MANDATORY then field_present
|
|
356
|
+
else
|
|
357
|
+
true
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private
|
|
362
|
+
|
|
363
|
+
# A String value is parsed as a DV_TIME (matching the
|
|
364
|
+
# concrete type this codebase's range/list bounds actually
|
|
365
|
+
# use, so Comparable/Interval#has? compare like with like);
|
|
366
|
+
# a malformed string surfaces as ArgumentError, caught below.
|
|
367
|
+
def as_time(value)
|
|
368
|
+
return value if value.respond_to?(:minute_unknown?)
|
|
369
|
+
return nil unless value.is_a?(String)
|
|
370
|
+
|
|
371
|
+
OpenEHR::RM::DataTypes::Quantity::DateTime::DvTime.new(:value => value)
|
|
372
|
+
rescue ArgumentError
|
|
373
|
+
nil
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def constrained_time?(time)
|
|
377
|
+
return true if range.nil? && list.nil?
|
|
378
|
+
return list.any? { |item| as_time(item) == time } if list
|
|
379
|
+
|
|
380
|
+
range.has?(time)
|
|
381
|
+
end
|
|
258
382
|
end
|
|
259
383
|
|
|
260
384
|
class CTime < CPrimitive
|
|
@@ -286,9 +410,9 @@ module OpenEHR
|
|
|
286
410
|
self.millisecond_validity = args[:millisecond_validity]
|
|
287
411
|
self.second_validity = args[:second_validity]
|
|
288
412
|
self.minute_validity = args[:minute_validity]
|
|
289
|
-
self.hour_validity = args[:
|
|
413
|
+
self.hour_validity = args[:hour_validity]
|
|
290
414
|
self.day_validity = args[:day_validity]
|
|
291
|
-
self.month_validity = args[:
|
|
415
|
+
self.month_validity = args[:month_validity]
|
|
292
416
|
end
|
|
293
417
|
|
|
294
418
|
def hour_validity=(hour_validity)
|
|
@@ -313,6 +437,15 @@ module OpenEHR
|
|
|
313
437
|
@day_validity = day_validity
|
|
314
438
|
end
|
|
315
439
|
|
|
440
|
+
def valid_value?(value)
|
|
441
|
+
datetime = as_datetime(value)
|
|
442
|
+
return false if datetime.nil?
|
|
443
|
+
|
|
444
|
+
date_fields_valid?(datetime) && time_fields_valid?(datetime) &&
|
|
445
|
+
validity_satisfied?(hour_validity, !datetime.hour_unknown?) &&
|
|
446
|
+
constrained_datetime?(datetime)
|
|
447
|
+
end
|
|
448
|
+
|
|
316
449
|
protected
|
|
317
450
|
def valid_pattern?(pattern)
|
|
318
451
|
if /^([Yy?X]{4})(-([Mm?X]{2})(-([Dd?X]{2}))?)?[T ]?([Hh?X]{2})(:([Mm?X]{2})(:([Ss?X]{2}))?)?$/ =~ pattern
|
|
@@ -321,6 +454,28 @@ module OpenEHR
|
|
|
321
454
|
false
|
|
322
455
|
end
|
|
323
456
|
end
|
|
457
|
+
|
|
458
|
+
private
|
|
459
|
+
|
|
460
|
+
# A String value is parsed as a DV_DATE_TIME (matching the
|
|
461
|
+
# concrete type this codebase's range/list bounds actually
|
|
462
|
+
# use, so Comparable/Interval#has? compare like with like);
|
|
463
|
+
# a malformed string surfaces as ArgumentError, caught below.
|
|
464
|
+
def as_datetime(value)
|
|
465
|
+
return value if value.respond_to?(:hour_unknown?)
|
|
466
|
+
return nil unless value.is_a?(String)
|
|
467
|
+
|
|
468
|
+
OpenEHR::RM::DataTypes::Quantity::DateTime::DvDateTime.new(:value => value)
|
|
469
|
+
rescue ArgumentError
|
|
470
|
+
nil
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def constrained_datetime?(datetime)
|
|
474
|
+
return true if range.nil? && list.nil?
|
|
475
|
+
return list.any? { |item| as_datetime(item) == datetime } if list
|
|
476
|
+
|
|
477
|
+
range.has?(datetime)
|
|
478
|
+
end
|
|
324
479
|
end
|
|
325
480
|
|
|
326
481
|
class CDuration < CPrimitive
|
|
@@ -335,6 +490,49 @@ module OpenEHR
|
|
|
335
490
|
self.pattern = args[:pattern]
|
|
336
491
|
self.list = args[:list]
|
|
337
492
|
self.range = args[:range]
|
|
493
|
+
self.years_allowed = args[:years_allowed]
|
|
494
|
+
self.months_allowed = args[:months_allowed]
|
|
495
|
+
self.weeks_allowed = args[:weeks_allowed]
|
|
496
|
+
self.days_allowed = args[:days_allowed]
|
|
497
|
+
self.hours_allowed = args[:hours_allowed]
|
|
498
|
+
self.minutes_allowed = args[:minutes_allowed]
|
|
499
|
+
self.seconds_allowed = args[:seconds_allowed]
|
|
500
|
+
self.fractional_seconds_allowed = args[:fractional_seconds_allowed]
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def valid_value?(value)
|
|
504
|
+
duration = as_duration(value)
|
|
505
|
+
return false if duration.nil?
|
|
506
|
+
return false unless fields_allowed?(duration)
|
|
507
|
+
return true if list.nil? && range.nil?
|
|
508
|
+
return list.any? { |item| as_duration(item).to_seconds == duration.to_seconds } if list
|
|
509
|
+
|
|
510
|
+
range.has?(duration)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
private
|
|
514
|
+
|
|
515
|
+
# A String value is parsed as a DV_DURATION (matching the
|
|
516
|
+
# concrete type this codebase's range/list bounds actually
|
|
517
|
+
# use, so Comparable/Interval#has? compare like with like);
|
|
518
|
+
# a malformed string surfaces as ArgumentError, caught below.
|
|
519
|
+
def as_duration(value)
|
|
520
|
+
return value if value.respond_to?(:to_seconds)
|
|
521
|
+
return nil unless value.is_a?(String)
|
|
522
|
+
|
|
523
|
+
OpenEHR::RM::DataTypes::Quantity::DateTime::DvDuration.new(:value => value)
|
|
524
|
+
rescue ArgumentError
|
|
525
|
+
nil
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def fields_allowed?(duration)
|
|
529
|
+
[[years_allowed, duration.years], [months_allowed, duration.months],
|
|
530
|
+
[weeks_allowed, duration.weeks], [days_allowed, duration.days],
|
|
531
|
+
[hours_allowed, duration.hours], [minutes_allowed, duration.minutes],
|
|
532
|
+
[seconds_allowed, duration.seconds],
|
|
533
|
+
[fractional_seconds_allowed, duration.fractional_second]].all? do |allowed, field|
|
|
534
|
+
allowed != false || field.nil?
|
|
535
|
+
end
|
|
338
536
|
end
|
|
339
537
|
end
|
|
340
538
|
end # of Primitive
|