oehrpy 0.1.0__py3-none-any.whl → 0.2.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 → oehrpy-0.2.0.dist-info}/METADATA +1 -1
- {oehrpy-0.1.0.dist-info → oehrpy-0.2.0.dist-info}/RECORD +8 -8
- openehr_sdk/aql/builder.py +9 -9
- openehr_sdk/client/__init__.py +6 -0
- openehr_sdk/client/ehrbase.py +230 -22
- openehr_sdk/rm/rm_types.py +1 -1
- {oehrpy-0.1.0.dist-info → oehrpy-0.2.0.dist-info}/WHEEL +0 -0
- {oehrpy-0.1.0.dist-info → oehrpy-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: oehrpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Python SDK for openEHR with type-safe Reference Model classes, template builders, and EHRBase client
|
|
5
5
|
Project-URL: Homepage, https://github.com/platzhersh/oehrpy
|
|
6
6
|
Project-URL: Documentation, https://github.com/platzhersh/oehrpy#readme
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
openehr_sdk/__init__.py,sha256=Rzs3NFgVa-P2ljZu2-OXcgkKBrhE_mAwWf9HnnVwrB8,1295
|
|
2
2
|
openehr_sdk/aql/__init__.py,sha256=auR0SKnnnAhzRzq2nU0YZSzUPKmAzYoXXdWs9Kxzoq8,958
|
|
3
|
-
openehr_sdk/aql/builder.py,sha256=
|
|
4
|
-
openehr_sdk/client/__init__.py,sha256=
|
|
5
|
-
openehr_sdk/client/ehrbase.py,sha256=
|
|
3
|
+
openehr_sdk/aql/builder.py,sha256=XptfUJVuMbUAa-LY-XFWxWDXFzBbMSHZStNloz2PAGo,17053
|
|
4
|
+
openehr_sdk/client/__init__.py,sha256=GTa8fnE-6tLCYGCthxF_PAVVoJ_ESVkPfVWFdlemAlE,822
|
|
5
|
+
openehr_sdk/client/ehrbase.py,sha256=ZVR7sySYfIc6aB5WZnnx3-TDnAjCT7vPFJZr61ziJww,28480
|
|
6
6
|
openehr_sdk/rm/__init__.py,sha256=yS5JAFaUuoo3D-k_Q9V78j2cBQKTW4EMK9LYEl34ZAc,457
|
|
7
|
-
openehr_sdk/rm/rm_types.py,sha256=
|
|
7
|
+
openehr_sdk/rm/rm_types.py,sha256=7DLcfubGplYPIhF6byBsrnEO0ayOw_bBeJbzrDqbZ-Q,50518
|
|
8
8
|
openehr_sdk/serialization/__init__.py,sha256=aucmds3NXlq1nQrjQNZnYR6PzqT63lAcCLh3og2AtM8,748
|
|
9
9
|
openehr_sdk/serialization/canonical.py,sha256=hQMLaKWne9Ep_mv0VrT7kJa0y89C37YEB5owRpACRag,6325
|
|
10
10
|
openehr_sdk/serialization/flat.py,sha256=w8hh6jGGsVHzvnjm1RlMoAhAxuhLumfuV_rrC5sH60A,13122
|
|
@@ -12,7 +12,7 @@ openehr_sdk/templates/__init__.py,sha256=_9Fwua5Mw_mM5n9CaKqXql12GSQyVvqdc_-J-jx
|
|
|
12
12
|
openehr_sdk/templates/builder_generator.py,sha256=oTYcsvmwGasUfGU3zmW9O79eCFyyprO4hkGJ5SvMB5I,13679
|
|
13
13
|
openehr_sdk/templates/builders.py,sha256=CJuOrobrCNWBbEyHbVJTzGqxApZ7CAyZrhUmY9ctvWg,13665
|
|
14
14
|
openehr_sdk/templates/opt_parser.py,sha256=WzOFYUHgAYTn7hk8LWghqP2gjiG9t3LWlYpQ3-999pg,12423
|
|
15
|
-
oehrpy-0.
|
|
16
|
-
oehrpy-0.
|
|
17
|
-
oehrpy-0.
|
|
18
|
-
oehrpy-0.
|
|
15
|
+
oehrpy-0.2.0.dist-info/METADATA,sha256=l4yTTO-SCYrJRjifCxqQ4wJUEN2i5gt8-jVuC-wCqKQ,11972
|
|
16
|
+
oehrpy-0.2.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
17
|
+
oehrpy-0.2.0.dist-info/licenses/LICENSE,sha256=2RQ4UN7dGDVJh_e9NTzuaUzdMWMDsy1jgLPxKxJahus,1073
|
|
18
|
+
oehrpy-0.2.0.dist-info/RECORD,,
|
openehr_sdk/aql/builder.py
CHANGED
|
@@ -66,7 +66,7 @@ class FromClause:
|
|
|
66
66
|
|
|
67
67
|
# EHR clause
|
|
68
68
|
if self.ehr_id_param:
|
|
69
|
-
parts.append(f"EHR {self.ehr_alias}[ehr_id/value
|
|
69
|
+
parts.append(f"EHR {self.ehr_alias}[ehr_id/value=${self.ehr_id_param}]")
|
|
70
70
|
else:
|
|
71
71
|
parts.append(f"EHR {self.ehr_alias}")
|
|
72
72
|
|
|
@@ -178,7 +178,7 @@ class AQLBuilder:
|
|
|
178
178
|
... .select("c/context/start_time/value", alias="time")
|
|
179
179
|
... .from_ehr("e")
|
|
180
180
|
... .contains_composition("c", "IDCR - Vital Signs Encounter.v1")
|
|
181
|
-
... .where("e/ehr_id/value =
|
|
181
|
+
... .where("e/ehr_id/value = $ehr_id")
|
|
182
182
|
... .order_by("c/context/start_time/value", descending=True)
|
|
183
183
|
... .limit(10)
|
|
184
184
|
... .build()
|
|
@@ -269,7 +269,7 @@ class AQLBuilder:
|
|
|
269
269
|
|
|
270
270
|
if archetype_id:
|
|
271
271
|
param_name = f"{alias}_archetype_id"
|
|
272
|
-
containment = f"{rm_type} {alias}[archetype_id/value
|
|
272
|
+
containment = f"{rm_type} {alias}[archetype_id/value=${param_name}]"
|
|
273
273
|
self._parameters[param_name] = archetype_id
|
|
274
274
|
else:
|
|
275
275
|
containment = f"{rm_type} {alias}"
|
|
@@ -299,7 +299,7 @@ class AQLBuilder:
|
|
|
299
299
|
self._from_clause = FromClause()
|
|
300
300
|
|
|
301
301
|
if archetype_id:
|
|
302
|
-
containment = f"COMPOSITION {alias}[archetype_id/value
|
|
302
|
+
containment = f"COMPOSITION {alias}[archetype_id/value=${alias}_archetype_id]"
|
|
303
303
|
self._parameters[f"{alias}_archetype_id"] = archetype_id
|
|
304
304
|
else:
|
|
305
305
|
containment = f"COMPOSITION {alias}"
|
|
@@ -309,7 +309,7 @@ class AQLBuilder:
|
|
|
309
309
|
# Add template_id filter as parameterized WHERE clause
|
|
310
310
|
if template_id:
|
|
311
311
|
self._where_clause.add(
|
|
312
|
-
f"{alias}/archetype_details/template_id/value =
|
|
312
|
+
f"{alias}/archetype_details/template_id/value = ${template_id_param}"
|
|
313
313
|
)
|
|
314
314
|
self._parameters[template_id_param] = template_id
|
|
315
315
|
|
|
@@ -373,7 +373,7 @@ class AQLBuilder:
|
|
|
373
373
|
Returns:
|
|
374
374
|
Self for method chaining.
|
|
375
375
|
"""
|
|
376
|
-
return self.where(f"{ehr_alias}/ehr_id/value =
|
|
376
|
+
return self.where(f"{ehr_alias}/ehr_id/value = ${param_name}")
|
|
377
377
|
|
|
378
378
|
def where_template(
|
|
379
379
|
self,
|
|
@@ -391,7 +391,7 @@ class AQLBuilder:
|
|
|
391
391
|
Returns:
|
|
392
392
|
Self for method chaining.
|
|
393
393
|
"""
|
|
394
|
-
self.where(f"{composition_alias}/archetype_details/template_id/value =
|
|
394
|
+
self.where(f"{composition_alias}/archetype_details/template_id/value = ${param_name}")
|
|
395
395
|
if template_id:
|
|
396
396
|
self._parameters[param_name] = template_id
|
|
397
397
|
return self
|
|
@@ -417,10 +417,10 @@ class AQLBuilder:
|
|
|
417
417
|
Self for method chaining.
|
|
418
418
|
"""
|
|
419
419
|
if start:
|
|
420
|
-
self.where(f"{path} >=
|
|
420
|
+
self.where(f"{path} >= ${start_param}")
|
|
421
421
|
self._parameters[start_param] = start
|
|
422
422
|
if end:
|
|
423
|
-
self.where(f"{path} <=
|
|
423
|
+
self.where(f"{path} <= ${end_param}")
|
|
424
424
|
self._parameters[end_param] = end
|
|
425
425
|
return self
|
|
426
426
|
|
openehr_sdk/client/__init__.py
CHANGED
|
@@ -9,13 +9,16 @@ from .ehrbase import (
|
|
|
9
9
|
AuthenticationError,
|
|
10
10
|
CompositionFormat,
|
|
11
11
|
CompositionResponse,
|
|
12
|
+
CompositionVersionResponse,
|
|
12
13
|
EHRBaseClient,
|
|
13
14
|
EHRBaseConfig,
|
|
14
15
|
EHRBaseError,
|
|
15
16
|
EHRResponse,
|
|
16
17
|
NotFoundError,
|
|
18
|
+
PreconditionFailedError,
|
|
17
19
|
QueryResponse,
|
|
18
20
|
ValidationError,
|
|
21
|
+
VersionedCompositionResponse,
|
|
19
22
|
)
|
|
20
23
|
|
|
21
24
|
__all__ = [
|
|
@@ -24,9 +27,12 @@ __all__ = [
|
|
|
24
27
|
"EHRResponse",
|
|
25
28
|
"CompositionResponse",
|
|
26
29
|
"CompositionFormat",
|
|
30
|
+
"CompositionVersionResponse",
|
|
27
31
|
"QueryResponse",
|
|
32
|
+
"VersionedCompositionResponse",
|
|
28
33
|
"EHRBaseError",
|
|
29
34
|
"AuthenticationError",
|
|
30
35
|
"NotFoundError",
|
|
36
|
+
"PreconditionFailedError",
|
|
31
37
|
"ValidationError",
|
|
32
38
|
]
|
openehr_sdk/client/ehrbase.py
CHANGED
|
@@ -72,6 +72,12 @@ class ValidationError(EHRBaseError):
|
|
|
72
72
|
pass
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
class PreconditionFailedError(EHRBaseError):
|
|
76
|
+
"""Version conflict — the If-Match header did not match (HTTP 412)."""
|
|
77
|
+
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
75
81
|
# Response dataclasses
|
|
76
82
|
|
|
77
83
|
|
|
@@ -110,13 +116,25 @@ class CompositionResponse:
|
|
|
110
116
|
@classmethod
|
|
111
117
|
def from_response(cls, data: dict[str, Any], ehr_id: str | None = None) -> CompositionResponse:
|
|
112
118
|
"""Create from API response."""
|
|
119
|
+
# Try canonical format first (uid is a dict with "value" key)
|
|
113
120
|
uid_data = data.get("uid")
|
|
114
121
|
uid = uid_data.get("value", "") if isinstance(uid_data, dict) else uid_data or ""
|
|
122
|
+
|
|
123
|
+
template_id = data.get("archetype_details", {}).get("template_id", {}).get("value")
|
|
124
|
+
archetype_id = data.get("archetype_details", {}).get("archetype_id", {}).get("value")
|
|
125
|
+
|
|
126
|
+
# For FLAT format responses, extract uid from */_uid key
|
|
127
|
+
if not uid:
|
|
128
|
+
for key, value in data.items():
|
|
129
|
+
if key.endswith("/_uid") and isinstance(value, str):
|
|
130
|
+
uid = value
|
|
131
|
+
break
|
|
132
|
+
|
|
115
133
|
return cls(
|
|
116
134
|
uid=uid,
|
|
117
135
|
ehr_id=ehr_id,
|
|
118
|
-
template_id=
|
|
119
|
-
archetype_id=
|
|
136
|
+
template_id=template_id,
|
|
137
|
+
archetype_id=archetype_id,
|
|
120
138
|
composition=data,
|
|
121
139
|
)
|
|
122
140
|
|
|
@@ -166,6 +184,58 @@ class TemplateResponse:
|
|
|
166
184
|
)
|
|
167
185
|
|
|
168
186
|
|
|
187
|
+
@dataclass
|
|
188
|
+
class VersionedCompositionResponse:
|
|
189
|
+
"""Response from versioned composition metadata endpoint."""
|
|
190
|
+
|
|
191
|
+
uid: str
|
|
192
|
+
owner_id: str | None = None
|
|
193
|
+
time_created: str | None = None
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def from_response(cls, data: dict[str, Any]) -> VersionedCompositionResponse:
|
|
197
|
+
"""Create from API response."""
|
|
198
|
+
uid_data = data.get("uid", {})
|
|
199
|
+
uid = uid_data.get("value", "") if isinstance(uid_data, dict) else uid_data or ""
|
|
200
|
+
owner_data = data.get("owner_id", {})
|
|
201
|
+
owner_id = owner_data.get("value", "") if isinstance(owner_data, dict) else owner_data
|
|
202
|
+
time_created_data = data.get("time_created", {})
|
|
203
|
+
time_created = (
|
|
204
|
+
time_created_data.get("value", "")
|
|
205
|
+
if isinstance(time_created_data, dict)
|
|
206
|
+
else time_created_data
|
|
207
|
+
)
|
|
208
|
+
return cls(uid=uid, owner_id=owner_id, time_created=time_created)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class CompositionVersionResponse:
|
|
213
|
+
"""Response from a specific composition version endpoint."""
|
|
214
|
+
|
|
215
|
+
version_uid: str
|
|
216
|
+
preceding_version_uid: str | None = None
|
|
217
|
+
lifecycle_state: str | None = None
|
|
218
|
+
commit_audit: dict[str, Any] | None = None
|
|
219
|
+
data: dict[str, Any] | None = None
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def from_response(cls, data: dict[str, Any]) -> CompositionVersionResponse:
|
|
223
|
+
"""Create from API response."""
|
|
224
|
+
uid_data = data.get("uid", {})
|
|
225
|
+
version_uid = uid_data.get("value", "") if isinstance(uid_data, dict) else uid_data or ""
|
|
226
|
+
preceding = data.get("preceding_version_uid", {})
|
|
227
|
+
preceding_uid = preceding.get("value", "") if isinstance(preceding, dict) else preceding
|
|
228
|
+
lifecycle = data.get("lifecycle_state", {})
|
|
229
|
+
lifecycle_state = lifecycle.get("value", "") if isinstance(lifecycle, dict) else lifecycle
|
|
230
|
+
return cls(
|
|
231
|
+
version_uid=version_uid,
|
|
232
|
+
preceding_version_uid=preceding_uid or None,
|
|
233
|
+
lifecycle_state=lifecycle_state or None,
|
|
234
|
+
commit_audit=data.get("commit_audit"),
|
|
235
|
+
data=data.get("data"),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
169
239
|
@dataclass
|
|
170
240
|
class EHRBaseConfig:
|
|
171
241
|
"""Configuration for EHRBase client."""
|
|
@@ -268,6 +338,11 @@ class EHRBaseClient:
|
|
|
268
338
|
"Resource not found",
|
|
269
339
|
status_code=response.status_code,
|
|
270
340
|
)
|
|
341
|
+
if response.status_code == 412:
|
|
342
|
+
raise PreconditionFailedError(
|
|
343
|
+
"Version conflict: the preceding version UID does not match the latest version",
|
|
344
|
+
status_code=response.status_code,
|
|
345
|
+
)
|
|
271
346
|
if response.status_code == 400 or response.status_code == 422:
|
|
272
347
|
try:
|
|
273
348
|
error_data = response.json()
|
|
@@ -318,24 +393,37 @@ class EHRBaseClient:
|
|
|
318
393
|
Returns:
|
|
319
394
|
EHRResponse with the created EHR details.
|
|
320
395
|
"""
|
|
321
|
-
headers = {}
|
|
396
|
+
headers: dict[str, str] = {"Prefer": "return=representation"}
|
|
322
397
|
if ehr_id:
|
|
323
|
-
headers["Prefer"] = "return=representation"
|
|
324
398
|
response = await self.client.put(
|
|
325
399
|
f"/rest/openehr/v1/ehr/{ehr_id}",
|
|
326
400
|
headers=headers,
|
|
327
401
|
)
|
|
328
402
|
else:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
403
|
+
body = None
|
|
404
|
+
if subject_id and subject_namespace:
|
|
405
|
+
body = {
|
|
406
|
+
"_type": "EHR_STATUS",
|
|
407
|
+
"archetype_node_id": "openEHR-EHR-EHR_STATUS.generic.v1",
|
|
408
|
+
"name": {"value": "EHR Status"},
|
|
409
|
+
"subject": {
|
|
410
|
+
"external_ref": {
|
|
411
|
+
"id": {
|
|
412
|
+
"_type": "GENERIC_ID",
|
|
413
|
+
"value": subject_id,
|
|
414
|
+
"scheme": "id_scheme",
|
|
415
|
+
},
|
|
416
|
+
"namespace": subject_namespace,
|
|
417
|
+
"type": "PERSON",
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
"is_modifiable": True,
|
|
421
|
+
"is_queryable": True,
|
|
422
|
+
}
|
|
335
423
|
response = await self.client.post(
|
|
336
424
|
"/rest/openehr/v1/ehr",
|
|
337
425
|
headers=headers,
|
|
338
|
-
|
|
426
|
+
json=body,
|
|
339
427
|
)
|
|
340
428
|
|
|
341
429
|
data = self._handle_response(response)
|
|
@@ -439,9 +527,17 @@ class EHRBaseClient:
|
|
|
439
527
|
"""
|
|
440
528
|
format_str = format.value if isinstance(format, CompositionFormat) else format
|
|
441
529
|
|
|
530
|
+
# Extract versioned object UID (uuid::system::version -> uuid::system)
|
|
531
|
+
uid_parts = composition_uid.split("::")
|
|
532
|
+
versioned_object_uid = "::".join(uid_parts[:2]) if len(uid_parts) >= 2 else composition_uid
|
|
533
|
+
|
|
534
|
+
params: dict[str, str] = {}
|
|
535
|
+
if format_str:
|
|
536
|
+
params["format"] = format_str
|
|
537
|
+
|
|
442
538
|
response = await self.client.get(
|
|
443
|
-
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{
|
|
444
|
-
params=
|
|
539
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{versioned_object_uid}",
|
|
540
|
+
params=params if params else None,
|
|
445
541
|
)
|
|
446
542
|
|
|
447
543
|
data = self._handle_response(response)
|
|
@@ -450,7 +546,8 @@ class EHRBaseClient:
|
|
|
450
546
|
async def update_composition(
|
|
451
547
|
self,
|
|
452
548
|
ehr_id: str,
|
|
453
|
-
|
|
549
|
+
versioned_object_uid: str,
|
|
550
|
+
preceding_version_uid: str,
|
|
454
551
|
composition: dict[str, Any],
|
|
455
552
|
template_id: str | None = None,
|
|
456
553
|
format: str | CompositionFormat = CompositionFormat.FLAT,
|
|
@@ -459,23 +556,27 @@ class EHRBaseClient:
|
|
|
459
556
|
|
|
460
557
|
Args:
|
|
461
558
|
ehr_id: The EHR ID.
|
|
462
|
-
|
|
559
|
+
versioned_object_uid: The composition's UUID (used in the request path).
|
|
560
|
+
preceding_version_uid: Full version string (e.g. ``uuid::domain::1``),
|
|
561
|
+
sent as the ``If-Match`` header for optimistic concurrency.
|
|
463
562
|
composition: The updated composition data.
|
|
464
563
|
template_id: Template ID.
|
|
465
564
|
format: Composition format.
|
|
466
565
|
|
|
467
566
|
Returns:
|
|
468
567
|
CompositionResponse with updated composition.
|
|
568
|
+
|
|
569
|
+
Raises:
|
|
570
|
+
PreconditionFailedError: If the preceding version UID does not match
|
|
571
|
+
the latest version (HTTP 412).
|
|
572
|
+
NotFoundError: If the composition has been deleted (HTTP 404).
|
|
469
573
|
"""
|
|
470
574
|
format_str = format.value if isinstance(format, CompositionFormat) else format
|
|
471
575
|
|
|
472
|
-
# Extract base UID without version
|
|
473
|
-
base_uid = composition_uid.split("::")[0] if "::" in composition_uid else composition_uid
|
|
474
|
-
|
|
475
576
|
headers = {
|
|
476
577
|
"Prefer": "return=representation",
|
|
477
578
|
"Content-Type": "application/json",
|
|
478
|
-
"If-Match":
|
|
579
|
+
"If-Match": preceding_version_uid,
|
|
479
580
|
}
|
|
480
581
|
|
|
481
582
|
params = {}
|
|
@@ -485,7 +586,7 @@ class EHRBaseClient:
|
|
|
485
586
|
params["format"] = format_str
|
|
486
587
|
|
|
487
588
|
response = await self.client.put(
|
|
488
|
-
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{
|
|
589
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{versioned_object_uid}",
|
|
489
590
|
json=composition,
|
|
490
591
|
headers=headers,
|
|
491
592
|
params=params if params else None,
|
|
@@ -505,13 +606,120 @@ class EHRBaseClient:
|
|
|
505
606
|
ehr_id: The EHR ID.
|
|
506
607
|
composition_uid: The composition UID.
|
|
507
608
|
"""
|
|
508
|
-
|
|
609
|
+
# Extract versioned object UID (uuid::system::version -> uuid::system)
|
|
610
|
+
uid_parts = composition_uid.split("::")
|
|
611
|
+
versioned_object_uid = "::".join(uid_parts[:2]) if len(uid_parts) >= 2 else composition_uid
|
|
509
612
|
|
|
510
613
|
response = await self.client.delete(
|
|
511
|
-
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{
|
|
614
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{versioned_object_uid}",
|
|
512
615
|
)
|
|
513
616
|
self._handle_response(response)
|
|
514
617
|
|
|
618
|
+
# Composition Versioning Operations
|
|
619
|
+
|
|
620
|
+
async def get_composition_at_time(
|
|
621
|
+
self,
|
|
622
|
+
ehr_id: str,
|
|
623
|
+
versioned_object_uid: str,
|
|
624
|
+
version_at_time: str,
|
|
625
|
+
format: str | CompositionFormat = CompositionFormat.CANONICAL,
|
|
626
|
+
) -> CompositionResponse:
|
|
627
|
+
"""Get a composition as it existed at a specific point in time.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
ehr_id: The EHR ID.
|
|
631
|
+
versioned_object_uid: The composition's UUID.
|
|
632
|
+
version_at_time: ISO 8601 timestamp.
|
|
633
|
+
format: Desired response format.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
CompositionResponse with the composition at that time.
|
|
637
|
+
"""
|
|
638
|
+
format_str = format.value if isinstance(format, CompositionFormat) else format
|
|
639
|
+
|
|
640
|
+
params: dict[str, str] = {"version_at_time": version_at_time}
|
|
641
|
+
if format_str:
|
|
642
|
+
params["format"] = format_str
|
|
643
|
+
|
|
644
|
+
response = await self.client.get(
|
|
645
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/composition/{versioned_object_uid}",
|
|
646
|
+
params=params,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
data = self._handle_response(response)
|
|
650
|
+
return CompositionResponse.from_response(data, ehr_id)
|
|
651
|
+
|
|
652
|
+
async def get_versioned_composition(
|
|
653
|
+
self,
|
|
654
|
+
ehr_id: str,
|
|
655
|
+
versioned_object_uid: str,
|
|
656
|
+
) -> VersionedCompositionResponse:
|
|
657
|
+
"""Get versioned composition metadata.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
ehr_id: The EHR ID.
|
|
661
|
+
versioned_object_uid: The composition's UUID.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
VersionedCompositionResponse with metadata (UID, owner ID, time created).
|
|
665
|
+
"""
|
|
666
|
+
response = await self.client.get(
|
|
667
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/versioned_composition/{versioned_object_uid}",
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
data = self._handle_response(response)
|
|
671
|
+
return VersionedCompositionResponse.from_response(data)
|
|
672
|
+
|
|
673
|
+
async def get_composition_version(
|
|
674
|
+
self,
|
|
675
|
+
ehr_id: str,
|
|
676
|
+
versioned_object_uid: str,
|
|
677
|
+
version_uid: str,
|
|
678
|
+
) -> CompositionVersionResponse:
|
|
679
|
+
"""Get a specific version of a composition.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
ehr_id: The EHR ID.
|
|
683
|
+
versioned_object_uid: The composition's UUID.
|
|
684
|
+
version_uid: The specific version UID (e.g. ``uuid::domain::1``).
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
CompositionVersionResponse with full version and audit metadata.
|
|
688
|
+
"""
|
|
689
|
+
response = await self.client.get(
|
|
690
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/versioned_composition/{versioned_object_uid}/version/{version_uid}",
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
data = self._handle_response(response)
|
|
694
|
+
return CompositionVersionResponse.from_response(data)
|
|
695
|
+
|
|
696
|
+
async def list_composition_versions(
|
|
697
|
+
self,
|
|
698
|
+
ehr_id: str,
|
|
699
|
+
versioned_object_uid: str,
|
|
700
|
+
) -> list[CompositionVersionResponse]:
|
|
701
|
+
"""List all versions of a composition.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
ehr_id: The EHR ID.
|
|
705
|
+
versioned_object_uid: The composition's UUID.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
List of CompositionVersionResponse with version descriptors.
|
|
709
|
+
"""
|
|
710
|
+
response = await self.client.get(
|
|
711
|
+
f"/rest/openehr/v1/ehr/{ehr_id}/versioned_composition/{versioned_object_uid}/version",
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
data = self._handle_response(response)
|
|
715
|
+
if isinstance(data, list):
|
|
716
|
+
return [CompositionVersionResponse.from_response(v) for v in data]
|
|
717
|
+
# Some servers return the list under a key
|
|
718
|
+
versions = data.get("versions", data.get("items", []))
|
|
719
|
+
if isinstance(versions, list):
|
|
720
|
+
return [CompositionVersionResponse.from_response(v) for v in versions]
|
|
721
|
+
return []
|
|
722
|
+
|
|
515
723
|
# Query Operations
|
|
516
724
|
|
|
517
725
|
async def query(
|
openehr_sdk/rm/rm_types.py
CHANGED
|
@@ -1030,7 +1030,7 @@ class HISTORY(BaseModel):
|
|
|
1030
1030
|
archetype_details: ARCHETYPED | None = None
|
|
1031
1031
|
feeder_audit: FEEDER_AUDIT | None = None
|
|
1032
1032
|
links: list[LINK] | None = None
|
|
1033
|
-
origin: DV_DATE_TIME | None
|
|
1033
|
+
origin: DV_DATE_TIME | None = None
|
|
1034
1034
|
period: DV_DURATION | None = None
|
|
1035
1035
|
duration: DV_DURATION | None = None
|
|
1036
1036
|
summary: Any | None = None
|
|
File without changes
|
|
File without changes
|