oehrpy 0.1.0__py3-none-any.whl
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.
- oehrpy-0.1.0.dist-info/METADATA +362 -0
- oehrpy-0.1.0.dist-info/RECORD +18 -0
- oehrpy-0.1.0.dist-info/WHEEL +4 -0
- oehrpy-0.1.0.dist-info/licenses/LICENSE +21 -0
- openehr_sdk/__init__.py +49 -0
- openehr_sdk/aql/__init__.py +40 -0
- openehr_sdk/aql/builder.py +589 -0
- openehr_sdk/client/__init__.py +32 -0
- openehr_sdk/client/ehrbase.py +675 -0
- openehr_sdk/rm/__init__.py +17 -0
- openehr_sdk/rm/rm_types.py +1864 -0
- openehr_sdk/serialization/__init__.py +37 -0
- openehr_sdk/serialization/canonical.py +203 -0
- openehr_sdk/serialization/flat.py +372 -0
- openehr_sdk/templates/__init__.py +40 -0
- openehr_sdk/templates/builder_generator.py +421 -0
- openehr_sdk/templates/builders.py +432 -0
- openehr_sdk/templates/opt_parser.py +352 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AQL Query Builder implementation.
|
|
3
|
+
|
|
4
|
+
This module provides a fluent API for constructing AQL queries
|
|
5
|
+
with proper escaping and parameterization.
|
|
6
|
+
|
|
7
|
+
AQL Syntax Reference:
|
|
8
|
+
SELECT <select_clause>
|
|
9
|
+
FROM <from_clause>
|
|
10
|
+
[WHERE <where_clause>]
|
|
11
|
+
[ORDER BY <order_by_clause>]
|
|
12
|
+
[LIMIT <limit> [OFFSET <offset>]]
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> query = (
|
|
16
|
+
... AQLBuilder()
|
|
17
|
+
... .select("c/uid/value")
|
|
18
|
+
... .from_ehr()
|
|
19
|
+
... .contains_composition("c")
|
|
20
|
+
... .build()
|
|
21
|
+
... )
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from enum import Enum
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SortOrder(str, Enum):
|
|
32
|
+
"""Sort order for ORDER BY clause."""
|
|
33
|
+
|
|
34
|
+
ASC = "ASC"
|
|
35
|
+
DESC = "DESC"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class SelectClause:
|
|
40
|
+
"""Represents a SELECT clause item."""
|
|
41
|
+
|
|
42
|
+
path: str
|
|
43
|
+
alias: str | None = None
|
|
44
|
+
aggregate: str | None = None # COUNT, MAX, MIN, etc.
|
|
45
|
+
|
|
46
|
+
def to_string(self) -> str:
|
|
47
|
+
"""Convert to AQL string."""
|
|
48
|
+
expr = f"{self.aggregate}({self.path})" if self.aggregate else self.path
|
|
49
|
+
|
|
50
|
+
if self.alias:
|
|
51
|
+
return f"{expr} AS {self.alias}"
|
|
52
|
+
return expr
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class FromClause:
|
|
57
|
+
"""Represents a FROM clause with containments."""
|
|
58
|
+
|
|
59
|
+
ehr_alias: str = "e"
|
|
60
|
+
ehr_id_param: str | None = None # Parameter name for EHR ID (not the value)
|
|
61
|
+
containments: list[str] = field(default_factory=list)
|
|
62
|
+
|
|
63
|
+
def to_string(self) -> str:
|
|
64
|
+
"""Convert to AQL string."""
|
|
65
|
+
parts = []
|
|
66
|
+
|
|
67
|
+
# EHR clause
|
|
68
|
+
if self.ehr_id_param:
|
|
69
|
+
parts.append(f"EHR {self.ehr_alias}[ehr_id/value=:{self.ehr_id_param}]")
|
|
70
|
+
else:
|
|
71
|
+
parts.append(f"EHR {self.ehr_alias}")
|
|
72
|
+
|
|
73
|
+
# Containments
|
|
74
|
+
for containment in self.containments:
|
|
75
|
+
parts.append(f"CONTAINS {containment}")
|
|
76
|
+
|
|
77
|
+
return " ".join(parts)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class WhereClause:
|
|
82
|
+
"""Represents a WHERE clause."""
|
|
83
|
+
|
|
84
|
+
conditions: list[str] = field(default_factory=list)
|
|
85
|
+
logic: str = "AND" # AND or OR
|
|
86
|
+
|
|
87
|
+
def to_string(self) -> str:
|
|
88
|
+
"""Convert to AQL string."""
|
|
89
|
+
if not self.conditions:
|
|
90
|
+
return ""
|
|
91
|
+
return f" {self.logic} ".join(self.conditions)
|
|
92
|
+
|
|
93
|
+
def add(self, condition: str) -> None:
|
|
94
|
+
"""Add a condition."""
|
|
95
|
+
self.conditions.append(condition)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class OrderByClause:
|
|
100
|
+
"""Represents an ORDER BY clause item."""
|
|
101
|
+
|
|
102
|
+
path: str
|
|
103
|
+
order: SortOrder = SortOrder.ASC
|
|
104
|
+
|
|
105
|
+
def to_string(self) -> str:
|
|
106
|
+
"""Convert to AQL string."""
|
|
107
|
+
return f"{self.path} {self.order.value}"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class AQLQuery:
|
|
112
|
+
"""Represents a complete AQL query."""
|
|
113
|
+
|
|
114
|
+
select_clauses: list[SelectClause] = field(default_factory=list)
|
|
115
|
+
from_clause: FromClause | None = None
|
|
116
|
+
where_clause: WhereClause | None = None
|
|
117
|
+
order_by_clauses: list[OrderByClause] = field(default_factory=list)
|
|
118
|
+
limit_value: int | None = None
|
|
119
|
+
offset_value: int | None = None
|
|
120
|
+
parameters: dict[str, Any] = field(default_factory=dict)
|
|
121
|
+
|
|
122
|
+
def to_string(self) -> str:
|
|
123
|
+
"""Convert to AQL query string."""
|
|
124
|
+
parts = []
|
|
125
|
+
|
|
126
|
+
# SELECT
|
|
127
|
+
if self.select_clauses:
|
|
128
|
+
select_items = ", ".join(s.to_string() for s in self.select_clauses)
|
|
129
|
+
parts.append(f"SELECT {select_items}")
|
|
130
|
+
else:
|
|
131
|
+
parts.append("SELECT *")
|
|
132
|
+
|
|
133
|
+
# FROM
|
|
134
|
+
if self.from_clause:
|
|
135
|
+
parts.append(f"FROM {self.from_clause.to_string()}")
|
|
136
|
+
|
|
137
|
+
# WHERE
|
|
138
|
+
if self.where_clause and self.where_clause.conditions:
|
|
139
|
+
parts.append(f"WHERE {self.where_clause.to_string()}")
|
|
140
|
+
|
|
141
|
+
# ORDER BY
|
|
142
|
+
if self.order_by_clauses:
|
|
143
|
+
order_items = ", ".join(o.to_string() for o in self.order_by_clauses)
|
|
144
|
+
parts.append(f"ORDER BY {order_items}")
|
|
145
|
+
|
|
146
|
+
# LIMIT and OFFSET
|
|
147
|
+
if self.limit_value is not None:
|
|
148
|
+
parts.append(f"LIMIT {self.limit_value}")
|
|
149
|
+
if self.offset_value is not None:
|
|
150
|
+
parts.append(f"OFFSET {self.offset_value}")
|
|
151
|
+
|
|
152
|
+
return " ".join(parts)
|
|
153
|
+
|
|
154
|
+
def __str__(self) -> str:
|
|
155
|
+
return self.to_string()
|
|
156
|
+
|
|
157
|
+
def with_parameters(self, **params: Any) -> AQLQuery:
|
|
158
|
+
"""Return a new query with additional parameters."""
|
|
159
|
+
new_params = {**self.parameters, **params}
|
|
160
|
+
return AQLQuery(
|
|
161
|
+
select_clauses=self.select_clauses,
|
|
162
|
+
from_clause=self.from_clause,
|
|
163
|
+
where_clause=self.where_clause,
|
|
164
|
+
order_by_clauses=self.order_by_clauses,
|
|
165
|
+
limit_value=self.limit_value,
|
|
166
|
+
offset_value=self.offset_value,
|
|
167
|
+
parameters=new_params,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class AQLBuilder:
|
|
172
|
+
"""Fluent builder for AQL queries.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> query = (
|
|
176
|
+
... AQLBuilder()
|
|
177
|
+
... .select("c/uid/value", alias="uid")
|
|
178
|
+
... .select("c/context/start_time/value", alias="time")
|
|
179
|
+
... .from_ehr("e")
|
|
180
|
+
... .contains_composition("c", "IDCR - Vital Signs Encounter.v1")
|
|
181
|
+
... .where("e/ehr_id/value = :ehr_id")
|
|
182
|
+
... .order_by("c/context/start_time/value", descending=True)
|
|
183
|
+
... .limit(10)
|
|
184
|
+
... .build()
|
|
185
|
+
... )
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def __init__(self) -> None:
|
|
189
|
+
self._select_clauses: list[SelectClause] = []
|
|
190
|
+
self._from_clause: FromClause | None = None
|
|
191
|
+
self._where_clause: WhereClause = WhereClause()
|
|
192
|
+
self._order_by_clauses: list[OrderByClause] = []
|
|
193
|
+
self._limit: int | None = None
|
|
194
|
+
self._offset: int | None = None
|
|
195
|
+
self._parameters: dict[str, Any] = {}
|
|
196
|
+
|
|
197
|
+
def select(
|
|
198
|
+
self,
|
|
199
|
+
path: str,
|
|
200
|
+
alias: str | None = None,
|
|
201
|
+
aggregate: str | None = None,
|
|
202
|
+
) -> AQLBuilder:
|
|
203
|
+
"""Add a SELECT clause item.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
path: The AQL path expression (e.g., "c/uid/value").
|
|
207
|
+
alias: Optional alias for the result column.
|
|
208
|
+
aggregate: Optional aggregate function (COUNT, MAX, MIN, SUM, AVG).
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Self for method chaining.
|
|
212
|
+
"""
|
|
213
|
+
self._select_clauses.append(SelectClause(path, alias, aggregate))
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def select_count(self, path: str = "*", alias: str | None = None) -> AQLBuilder:
|
|
217
|
+
"""Add a COUNT aggregate."""
|
|
218
|
+
return self.select(path, alias, "COUNT")
|
|
219
|
+
|
|
220
|
+
def select_max(self, path: str, alias: str | None = None) -> AQLBuilder:
|
|
221
|
+
"""Add a MAX aggregate."""
|
|
222
|
+
return self.select(path, alias, "MAX")
|
|
223
|
+
|
|
224
|
+
def select_min(self, path: str, alias: str | None = None) -> AQLBuilder:
|
|
225
|
+
"""Add a MIN aggregate."""
|
|
226
|
+
return self.select(path, alias, "MIN")
|
|
227
|
+
|
|
228
|
+
def from_ehr(
|
|
229
|
+
self,
|
|
230
|
+
alias: str = "e",
|
|
231
|
+
ehr_id: str | None = None,
|
|
232
|
+
ehr_id_param: str = "ehr_id_from",
|
|
233
|
+
) -> AQLBuilder:
|
|
234
|
+
"""Set the FROM EHR clause.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
alias: Alias for the EHR (default: "e").
|
|
238
|
+
ehr_id: Optional specific EHR ID to query (registered as parameter).
|
|
239
|
+
ehr_id_param: Parameter name for the EHR ID.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Self for method chaining.
|
|
243
|
+
"""
|
|
244
|
+
if ehr_id is not None:
|
|
245
|
+
self._from_clause = FromClause(ehr_alias=alias, ehr_id_param=ehr_id_param)
|
|
246
|
+
self._parameters[ehr_id_param] = ehr_id
|
|
247
|
+
else:
|
|
248
|
+
self._from_clause = FromClause(ehr_alias=alias)
|
|
249
|
+
return self
|
|
250
|
+
|
|
251
|
+
def contains(
|
|
252
|
+
self,
|
|
253
|
+
rm_type: str,
|
|
254
|
+
alias: str,
|
|
255
|
+
archetype_id: str | None = None,
|
|
256
|
+
) -> AQLBuilder:
|
|
257
|
+
"""Add a CONTAINS clause.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
rm_type: The RM type (COMPOSITION, OBSERVATION, etc.).
|
|
261
|
+
alias: Alias for the contained item.
|
|
262
|
+
archetype_id: Optional archetype ID filter (parameterized).
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Self for method chaining.
|
|
266
|
+
"""
|
|
267
|
+
if self._from_clause is None:
|
|
268
|
+
self._from_clause = FromClause()
|
|
269
|
+
|
|
270
|
+
if archetype_id:
|
|
271
|
+
param_name = f"{alias}_archetype_id"
|
|
272
|
+
containment = f"{rm_type} {alias}[archetype_id/value=:{param_name}]"
|
|
273
|
+
self._parameters[param_name] = archetype_id
|
|
274
|
+
else:
|
|
275
|
+
containment = f"{rm_type} {alias}"
|
|
276
|
+
|
|
277
|
+
self._from_clause.containments.append(containment)
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def contains_composition(
|
|
281
|
+
self,
|
|
282
|
+
alias: str = "c",
|
|
283
|
+
template_id: str | None = None,
|
|
284
|
+
archetype_id: str | None = None,
|
|
285
|
+
template_id_param: str = "template_id",
|
|
286
|
+
) -> AQLBuilder:
|
|
287
|
+
"""Add a CONTAINS COMPOSITION clause.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
alias: Alias for the composition.
|
|
291
|
+
template_id: Optional template ID filter (added as parameterized WHERE).
|
|
292
|
+
archetype_id: Optional archetype ID filter.
|
|
293
|
+
template_id_param: Parameter name for template ID.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Self for method chaining.
|
|
297
|
+
"""
|
|
298
|
+
if self._from_clause is None:
|
|
299
|
+
self._from_clause = FromClause()
|
|
300
|
+
|
|
301
|
+
if archetype_id:
|
|
302
|
+
containment = f"COMPOSITION {alias}[archetype_id/value=:{alias}_archetype_id]"
|
|
303
|
+
self._parameters[f"{alias}_archetype_id"] = archetype_id
|
|
304
|
+
else:
|
|
305
|
+
containment = f"COMPOSITION {alias}"
|
|
306
|
+
|
|
307
|
+
self._from_clause.containments.append(containment)
|
|
308
|
+
|
|
309
|
+
# Add template_id filter as parameterized WHERE clause
|
|
310
|
+
if template_id:
|
|
311
|
+
self._where_clause.add(
|
|
312
|
+
f"{alias}/archetype_details/template_id/value = :{template_id_param}"
|
|
313
|
+
)
|
|
314
|
+
self._parameters[template_id_param] = template_id
|
|
315
|
+
|
|
316
|
+
return self
|
|
317
|
+
|
|
318
|
+
def contains_observation(
|
|
319
|
+
self,
|
|
320
|
+
alias: str = "o",
|
|
321
|
+
archetype_id: str | None = None,
|
|
322
|
+
) -> AQLBuilder:
|
|
323
|
+
"""Add a CONTAINS OBSERVATION clause."""
|
|
324
|
+
return self.contains("OBSERVATION", alias, archetype_id)
|
|
325
|
+
|
|
326
|
+
def contains_evaluation(
|
|
327
|
+
self,
|
|
328
|
+
alias: str = "e",
|
|
329
|
+
archetype_id: str | None = None,
|
|
330
|
+
) -> AQLBuilder:
|
|
331
|
+
"""Add a CONTAINS EVALUATION clause."""
|
|
332
|
+
return self.contains("EVALUATION", alias, archetype_id)
|
|
333
|
+
|
|
334
|
+
def contains_instruction(
|
|
335
|
+
self,
|
|
336
|
+
alias: str = "i",
|
|
337
|
+
archetype_id: str | None = None,
|
|
338
|
+
) -> AQLBuilder:
|
|
339
|
+
"""Add a CONTAINS INSTRUCTION clause."""
|
|
340
|
+
return self.contains("INSTRUCTION", alias, archetype_id)
|
|
341
|
+
|
|
342
|
+
def contains_action(
|
|
343
|
+
self,
|
|
344
|
+
alias: str = "a",
|
|
345
|
+
archetype_id: str | None = None,
|
|
346
|
+
) -> AQLBuilder:
|
|
347
|
+
"""Add a CONTAINS ACTION clause."""
|
|
348
|
+
return self.contains("ACTION", alias, archetype_id)
|
|
349
|
+
|
|
350
|
+
def where(self, condition: str) -> AQLBuilder:
|
|
351
|
+
"""Add a WHERE condition.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
condition: The condition expression.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Self for method chaining.
|
|
358
|
+
"""
|
|
359
|
+
self._where_clause.add(condition)
|
|
360
|
+
return self
|
|
361
|
+
|
|
362
|
+
def and_where(self, condition: str) -> AQLBuilder:
|
|
363
|
+
"""Add an AND condition."""
|
|
364
|
+
return self.where(condition)
|
|
365
|
+
|
|
366
|
+
def where_ehr_id(self, ehr_alias: str = "e", param_name: str = "ehr_id") -> AQLBuilder:
|
|
367
|
+
"""Add a WHERE condition for EHR ID.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
ehr_alias: Alias for the EHR.
|
|
371
|
+
param_name: Parameter name for the EHR ID.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Self for method chaining.
|
|
375
|
+
"""
|
|
376
|
+
return self.where(f"{ehr_alias}/ehr_id/value = :{param_name}")
|
|
377
|
+
|
|
378
|
+
def where_template(
|
|
379
|
+
self,
|
|
380
|
+
composition_alias: str = "c",
|
|
381
|
+
template_id: str | None = None,
|
|
382
|
+
param_name: str = "template_id",
|
|
383
|
+
) -> AQLBuilder:
|
|
384
|
+
"""Add a WHERE condition for template ID.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
composition_alias: Alias for the composition.
|
|
388
|
+
template_id: The template ID value (registered as parameter if provided).
|
|
389
|
+
param_name: Parameter name for the template ID.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Self for method chaining.
|
|
393
|
+
"""
|
|
394
|
+
self.where(f"{composition_alias}/archetype_details/template_id/value = :{param_name}")
|
|
395
|
+
if template_id:
|
|
396
|
+
self._parameters[param_name] = template_id
|
|
397
|
+
return self
|
|
398
|
+
|
|
399
|
+
def where_time_range(
|
|
400
|
+
self,
|
|
401
|
+
path: str,
|
|
402
|
+
start: str | None = None,
|
|
403
|
+
end: str | None = None,
|
|
404
|
+
start_param: str = "start_time",
|
|
405
|
+
end_param: str = "end_time",
|
|
406
|
+
) -> AQLBuilder:
|
|
407
|
+
"""Add WHERE conditions for a time range.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
path: Path to the datetime field.
|
|
411
|
+
start: Start time (ISO format). If provided, registers as parameter.
|
|
412
|
+
end: End time (ISO format). If provided, registers as parameter.
|
|
413
|
+
start_param: Parameter name for start time.
|
|
414
|
+
end_param: Parameter name for end time.
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Self for method chaining.
|
|
418
|
+
"""
|
|
419
|
+
if start:
|
|
420
|
+
self.where(f"{path} >= :{start_param}")
|
|
421
|
+
self._parameters[start_param] = start
|
|
422
|
+
if end:
|
|
423
|
+
self.where(f"{path} <= :{end_param}")
|
|
424
|
+
self._parameters[end_param] = end
|
|
425
|
+
return self
|
|
426
|
+
|
|
427
|
+
def order_by(
|
|
428
|
+
self,
|
|
429
|
+
path: str,
|
|
430
|
+
descending: bool = False,
|
|
431
|
+
) -> AQLBuilder:
|
|
432
|
+
"""Add an ORDER BY clause.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
path: The path to order by.
|
|
436
|
+
descending: Whether to sort descending.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
Self for method chaining.
|
|
440
|
+
"""
|
|
441
|
+
order = SortOrder.DESC if descending else SortOrder.ASC
|
|
442
|
+
self._order_by_clauses.append(OrderByClause(path, order))
|
|
443
|
+
return self
|
|
444
|
+
|
|
445
|
+
def order_by_time(
|
|
446
|
+
self,
|
|
447
|
+
path: str = "c/context/start_time/value",
|
|
448
|
+
descending: bool = True,
|
|
449
|
+
) -> AQLBuilder:
|
|
450
|
+
"""Add ORDER BY for a time field (newest first by default)."""
|
|
451
|
+
return self.order_by(path, descending)
|
|
452
|
+
|
|
453
|
+
def limit(self, count: int) -> AQLBuilder:
|
|
454
|
+
"""Set the LIMIT.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
count: Maximum number of results.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Self for method chaining.
|
|
461
|
+
"""
|
|
462
|
+
self._limit = count
|
|
463
|
+
return self
|
|
464
|
+
|
|
465
|
+
def offset(self, count: int) -> AQLBuilder:
|
|
466
|
+
"""Set the OFFSET.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
count: Number of results to skip.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Self for method chaining.
|
|
473
|
+
"""
|
|
474
|
+
self._offset = count
|
|
475
|
+
return self
|
|
476
|
+
|
|
477
|
+
def paginate(self, page: int, page_size: int) -> AQLBuilder:
|
|
478
|
+
"""Set pagination.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
page: Page number (1-based).
|
|
482
|
+
page_size: Number of results per page.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
Self for method chaining.
|
|
486
|
+
"""
|
|
487
|
+
self._limit = page_size
|
|
488
|
+
self._offset = (page - 1) * page_size
|
|
489
|
+
return self
|
|
490
|
+
|
|
491
|
+
def param(self, name: str, value: Any) -> AQLBuilder:
|
|
492
|
+
"""Set a query parameter.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
name: Parameter name.
|
|
496
|
+
value: Parameter value.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
Self for method chaining.
|
|
500
|
+
"""
|
|
501
|
+
self._parameters[name] = value
|
|
502
|
+
return self
|
|
503
|
+
|
|
504
|
+
def build(self) -> AQLQuery:
|
|
505
|
+
"""Build the AQL query.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
The constructed AQLQuery object.
|
|
509
|
+
"""
|
|
510
|
+
return AQLQuery(
|
|
511
|
+
select_clauses=self._select_clauses,
|
|
512
|
+
from_clause=self._from_clause,
|
|
513
|
+
where_clause=self._where_clause if self._where_clause.conditions else None,
|
|
514
|
+
order_by_clauses=self._order_by_clauses,
|
|
515
|
+
limit_value=self._limit,
|
|
516
|
+
offset_value=self._offset,
|
|
517
|
+
parameters=self._parameters,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
def to_string(self) -> str:
|
|
521
|
+
"""Build and return the query string."""
|
|
522
|
+
return self.build().to_string()
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# Convenience functions for common queries
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def select_compositions(
|
|
529
|
+
ehr_id: str | None = None,
|
|
530
|
+
template_id: str | None = None,
|
|
531
|
+
limit: int = 100,
|
|
532
|
+
) -> AQLQuery:
|
|
533
|
+
"""Build a query to select compositions.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
ehr_id: Optional EHR ID filter.
|
|
537
|
+
template_id: Optional template ID filter.
|
|
538
|
+
limit: Maximum results.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
AQLQuery for compositions.
|
|
542
|
+
"""
|
|
543
|
+
builder = (
|
|
544
|
+
AQLBuilder()
|
|
545
|
+
.select("c/uid/value", alias="uid")
|
|
546
|
+
.select("c/archetype_details/template_id/value", alias="template_id")
|
|
547
|
+
.select("c/context/start_time/value", alias="start_time")
|
|
548
|
+
.select("c/composer/name", alias="composer")
|
|
549
|
+
.from_ehr()
|
|
550
|
+
.contains_composition()
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
if ehr_id:
|
|
554
|
+
builder.where_ehr_id()
|
|
555
|
+
builder.param("ehr_id", ehr_id)
|
|
556
|
+
if template_id:
|
|
557
|
+
builder.where_template(template_id=template_id)
|
|
558
|
+
|
|
559
|
+
return builder.order_by_time().limit(limit).build()
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def select_observations(
|
|
563
|
+
archetype_id: str,
|
|
564
|
+
ehr_id: str | None = None,
|
|
565
|
+
limit: int = 100,
|
|
566
|
+
) -> AQLQuery:
|
|
567
|
+
"""Build a query to select observations.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
archetype_id: Observation archetype ID.
|
|
571
|
+
ehr_id: Optional EHR ID filter.
|
|
572
|
+
limit: Maximum results.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
AQLQuery for observations.
|
|
576
|
+
"""
|
|
577
|
+
builder = (
|
|
578
|
+
AQLBuilder()
|
|
579
|
+
.select("o")
|
|
580
|
+
.from_ehr()
|
|
581
|
+
.contains_composition()
|
|
582
|
+
.contains_observation(archetype_id=archetype_id)
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
if ehr_id:
|
|
586
|
+
builder.where_ehr_id()
|
|
587
|
+
builder.param("ehr_id", ehr_id)
|
|
588
|
+
|
|
589
|
+
return builder.limit(limit).build()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REST client for EHRBase and openEHR CDR servers.
|
|
3
|
+
|
|
4
|
+
This module provides async HTTP clients for interacting with
|
|
5
|
+
openEHR Clinical Data Repositories.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .ehrbase import (
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
CompositionFormat,
|
|
11
|
+
CompositionResponse,
|
|
12
|
+
EHRBaseClient,
|
|
13
|
+
EHRBaseConfig,
|
|
14
|
+
EHRBaseError,
|
|
15
|
+
EHRResponse,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
QueryResponse,
|
|
18
|
+
ValidationError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"EHRBaseClient",
|
|
23
|
+
"EHRBaseConfig",
|
|
24
|
+
"EHRResponse",
|
|
25
|
+
"CompositionResponse",
|
|
26
|
+
"CompositionFormat",
|
|
27
|
+
"QueryResponse",
|
|
28
|
+
"EHRBaseError",
|
|
29
|
+
"AuthenticationError",
|
|
30
|
+
"NotFoundError",
|
|
31
|
+
"ValidationError",
|
|
32
|
+
]
|