labfreed 0.0.9__py2.py3-none-any.whl → 0.0.11__py2.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.
Potentially problematic release.
This version of labfreed might be problematic. Click here for more details.
- labfreed/DisplayNameExtension/DisplayNameExtension.py +5 -2
- labfreed/PAC_CAT/data_model copy.py +232 -0
- labfreed/PAC_CAT/data_model.py +319 -59
- labfreed/PAC_ID/data_model.py +42 -112
- labfreed/PAC_ID/extensions.py +55 -0
- labfreed/TREX/data_model.py +316 -396
- labfreed/TREX/parse.py +1 -69
- labfreed/TREX/unece_units.py +17 -1
- labfreed/__init__.py +1 -1
- labfreed/{PAC_ID/parse.py → parse_pac.py} +104 -59
- labfreed/utilities/base36.py +29 -13
- labfreed/utilities/extension_intertpreters.py +4 -0
- labfreed/utilities/utility_types.py +103 -0
- labfreed/{PAC_ID/well_known_segment_keys.py → utilities/well_known_keys.py} +1 -1
- labfreed/validation.py +3 -1
- {labfreed-0.0.9.dist-info → labfreed-0.0.11.dist-info}/METADATA +1 -1
- labfreed-0.0.11.dist-info/RECORD +22 -0
- labfreed/PAC_ID/serialize.py +0 -60
- labfreed/TREX/serialize.py +0 -3
- labfreed/conversion_tools/uncertainty.py +0 -32
- labfreed/conversion_tools/unit_utilities.py +0 -109
- labfreed-0.0.9.dist-info/RECORD +0 -22
- {labfreed-0.0.9.dist-info → labfreed-0.0.11.dist-info}/WHEEL +0 -0
- {labfreed-0.0.9.dist-info → labfreed-0.0.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pydantic import BaseModel
|
|
3
|
-
from
|
|
3
|
+
from labfreed.PAC_ID.extensions import Extension
|
|
4
4
|
from labfreed.utilities.base36 import from_base36, to_base36
|
|
5
5
|
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ class DisplayNames(Extension, BaseModel):
|
|
|
20
20
|
return '/'.join([to_base36(dn) for dn in self.display_names])
|
|
21
21
|
|
|
22
22
|
@staticmethod
|
|
23
|
-
def from_spec_fields(name, type, data):
|
|
23
|
+
def from_spec_fields(*, name, type, data):
|
|
24
24
|
if name != 'N':
|
|
25
25
|
logging.warning(f'Name {name} was given, but this extension should only be used with name "N". Will ignore input')
|
|
26
26
|
|
|
@@ -30,5 +30,8 @@ class DisplayNames(Extension, BaseModel):
|
|
|
30
30
|
display_names = [from_base36(b36) for b36 in data.split('/')]
|
|
31
31
|
|
|
32
32
|
return DisplayNames(display_names=display_names)
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return 'Display names: '+ ';'.join(self.display_names)
|
|
33
36
|
|
|
34
37
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# import re
|
|
2
|
+
# from typing import Optional
|
|
3
|
+
# from typing_extensions import Self
|
|
4
|
+
# from pydantic import Field, ValidationInfo, computed_field, conlist, model_validator, field_validator
|
|
5
|
+
|
|
6
|
+
# from abc import ABC, abstractproperty, abstractstaticmethod
|
|
7
|
+
|
|
8
|
+
# from labfreed.PAC_ID.data_model import PACID
|
|
9
|
+
|
|
10
|
+
# from ..utilities.well_known_keys import WellKnownKeys
|
|
11
|
+
# from labfreed.validation import BaseModelWithValidationMessages, ValidationMessage, hsegment_pattern, domain_name_pattern
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# # class IDSegment(BaseModelWithValidationMessages):
|
|
15
|
+
# # key:str|None = None
|
|
16
|
+
# # value:str
|
|
17
|
+
|
|
18
|
+
# # @model_validator(mode="after")
|
|
19
|
+
# # def validate_segment(self):
|
|
20
|
+
# # key = self.key or ""
|
|
21
|
+
# # value = self.value
|
|
22
|
+
|
|
23
|
+
# # # MUST be a valid hsegment according to RFC 1738, but without * (see PAC-ID Extension)
|
|
24
|
+
# # # This means it must be true for both, key and value
|
|
25
|
+
# # if not_allowed_chars := set(re.sub(hsegment_pattern, '', key)):
|
|
26
|
+
# # self.add_validation_message(
|
|
27
|
+
# # source=f"id segment key {key}",
|
|
28
|
+
# # type="Error",
|
|
29
|
+
# # msg=f"{' '.join(not_allowed_chars)} must not be used.",
|
|
30
|
+
# # recommendation = "The segment key must be a valid hsegment",
|
|
31
|
+
# # highlight_pattern = key,
|
|
32
|
+
# # highlight_sub = not_allowed_chars
|
|
33
|
+
# # )
|
|
34
|
+
|
|
35
|
+
# # if not_allowed_chars := set(re.sub(hsegment_pattern, '', value)):
|
|
36
|
+
# # self.add_validation_message(
|
|
37
|
+
# # source=f"id segment key {value}",
|
|
38
|
+
# # type="Error",
|
|
39
|
+
# # msg=f"{' '.join(not_allowed_chars)} must not be used.",
|
|
40
|
+
# # recommendation = "The segment key must be a valid hsegment",
|
|
41
|
+
# # highlight_pattern = value,
|
|
42
|
+
# # highlight_sub = not_allowed_chars
|
|
43
|
+
# # )
|
|
44
|
+
|
|
45
|
+
# # # Segment key SHOULD be limited to A-Z, 0-9, and -+..
|
|
46
|
+
# # if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', key)):
|
|
47
|
+
# # self.add_validation_message(
|
|
48
|
+
# # source=f"id segment key {key}",
|
|
49
|
+
# # type="Recommendation",
|
|
50
|
+
# # msg=f"{' '.join(not_recommended_chars)} should not be used.",
|
|
51
|
+
# # recommendation = "SHOULD be limited to A-Z, 0-9, and -+",
|
|
52
|
+
# # highlight_pattern = key,
|
|
53
|
+
# # highlight_sub = not_recommended_chars
|
|
54
|
+
# # )
|
|
55
|
+
|
|
56
|
+
# # # Segment key should be in Well know keys
|
|
57
|
+
# # if key and key not in [k.value for k in WellKnownKeys]:
|
|
58
|
+
# # self.add_validation_message(
|
|
59
|
+
# # source=f"id segment key {key}",
|
|
60
|
+
# # type="Recommendation",
|
|
61
|
+
# # msg=f"{key} is not a well known segment key.",
|
|
62
|
+
# # recommendation = "RECOMMENDED to be a well-known id segment key.",
|
|
63
|
+
# # highlight_pattern = key
|
|
64
|
+
# # )
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# # # Segment value SHOULD be limited to A-Z, 0-9, and -+..
|
|
68
|
+
# # if not_recommended_chars := set(re.sub(r'[A-Z0-9-:+]', '', value)):
|
|
69
|
+
# # self.add_validation_message(
|
|
70
|
+
# # source=f"id segment value {value}",
|
|
71
|
+
# # type="Recommendation",
|
|
72
|
+
# # msg=f"Characters {' '.join(not_recommended_chars)} should not be used.",
|
|
73
|
+
# # recommendation = "SHOULD be limited to A-Z, 0-9, and -+",
|
|
74
|
+
# # highlight_pattern = value,
|
|
75
|
+
# # highlight_sub = not_recommended_chars
|
|
76
|
+
# # )
|
|
77
|
+
|
|
78
|
+
# # # Segment value SHOULD be limited to A-Z, 0-9, and :-+ for new designs.
|
|
79
|
+
# # # this means that ":" in key or value is problematic
|
|
80
|
+
# # if ':' in key:
|
|
81
|
+
# # self.add_validation_message(
|
|
82
|
+
# # source=f"id segment key {key}",
|
|
83
|
+
# # type="Recommendation",
|
|
84
|
+
# # msg=f"Character ':' should not be used in segment key, since this character is used to separate key and value this can lead to undefined behaviour.",
|
|
85
|
+
# # highlight_pattern = key
|
|
86
|
+
# # )
|
|
87
|
+
# # if ':' in value:
|
|
88
|
+
# # self.add_validation_message(
|
|
89
|
+
# # source=f"id segment value {value}",
|
|
90
|
+
# # type="Recommendation",
|
|
91
|
+
# # msg=f"Character ':' should not be used in segment value, since this character is used to separate key and value this can lead to undefined behaviour.",
|
|
92
|
+
# # highlight_pattern = value
|
|
93
|
+
# # )
|
|
94
|
+
|
|
95
|
+
# # return self
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# class PAC_CAT(PACID):
|
|
104
|
+
|
|
105
|
+
# @computed_field
|
|
106
|
+
# @property
|
|
107
|
+
# def categories(self) -> list[Category]:
|
|
108
|
+
# categories = list()
|
|
109
|
+
# c = Category(segments=[])
|
|
110
|
+
# categories.append(c)
|
|
111
|
+
# for s in self.segments:
|
|
112
|
+
# # new category starts with "-"
|
|
113
|
+
# if s.value[0] == '-':
|
|
114
|
+
# cat_key = s.value
|
|
115
|
+
# c = Category(key=cat_key, segments=[])
|
|
116
|
+
# categories.append(c)
|
|
117
|
+
# else:
|
|
118
|
+
# c.segments.append(s)
|
|
119
|
+
|
|
120
|
+
# # the first category might have no segments. remove categories without segments
|
|
121
|
+
# if not categories[0].segments:
|
|
122
|
+
# categories = categories[1:]
|
|
123
|
+
|
|
124
|
+
# return categories
|
|
125
|
+
|
|
126
|
+
# @model_validator(mode='after')
|
|
127
|
+
# def check_keys_are_unique_in_each_category(self) -> Self:
|
|
128
|
+
# for c in self.categories:
|
|
129
|
+
# keys = [s.key for s in c.segments if s.key]
|
|
130
|
+
# duplicate_keys = [k for k in set(keys) if keys.count(k) > 1]
|
|
131
|
+
# if duplicate_keys:
|
|
132
|
+
# for k in duplicate_keys:
|
|
133
|
+
# self.add_validation_message(
|
|
134
|
+
# source=f"identifier {k}",
|
|
135
|
+
# type="Error",
|
|
136
|
+
# msg=f"Duplicate key {k} in category {c.key}",
|
|
137
|
+
# highlight_pattern = k
|
|
138
|
+
# )
|
|
139
|
+
# return self
|
|
140
|
+
|
|
141
|
+
# # @model_validator(mode='after')
|
|
142
|
+
# # def check_length(self) -> Self:
|
|
143
|
+
# # l = 0
|
|
144
|
+
# # for s in self.segments:
|
|
145
|
+
# # if s.key:
|
|
146
|
+
# # l += len(s.key)
|
|
147
|
+
# # l += 1 # for ":"
|
|
148
|
+
# # l += len(s.value)
|
|
149
|
+
# # l += len(self.segments) - 1 # account for "/" separating the segments
|
|
150
|
+
|
|
151
|
+
# # if l > 256:
|
|
152
|
+
# # self.add_validation_message(
|
|
153
|
+
# # source=f"identifier",
|
|
154
|
+
# # type="Error",
|
|
155
|
+
# # msg=f'Identifier is {l} characters long, Identifier must not exceed 256 characters.',
|
|
156
|
+
# # highlight_pattern = ""
|
|
157
|
+
# # )
|
|
158
|
+
# # return self
|
|
159
|
+
|
|
160
|
+
# @staticmethod
|
|
161
|
+
# def from_categories(categories:list[Category]) :
|
|
162
|
+
# segments = list()
|
|
163
|
+
# for c in categories:
|
|
164
|
+
# if c.key:
|
|
165
|
+
# segments.append(IDSegment(value=c.key))
|
|
166
|
+
# segments.extend(c.segments)
|
|
167
|
+
# return Identifier(segments=segments)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# # class PACID(BaseModelWithValidationMessages):
|
|
174
|
+
# # issuer:str
|
|
175
|
+
# # identifier: Identifier
|
|
176
|
+
|
|
177
|
+
# # @model_validator(mode="after")
|
|
178
|
+
# # def validate_issuer(self):
|
|
179
|
+
# # if not re.fullmatch(domain_name_pattern, self.issuer):
|
|
180
|
+
# # self.add_validation_message(
|
|
181
|
+
# # source="PAC-ID",
|
|
182
|
+
# # type="Error",
|
|
183
|
+
# # highlight_pattern=self.issuer,
|
|
184
|
+
# # msg=f"Issuer must be a valid domain name. "
|
|
185
|
+
# # )
|
|
186
|
+
|
|
187
|
+
# # # recommendation that A-Z, 0-9, -, and . should be used
|
|
188
|
+
# # if not_recommended_chars := set(re.sub(r'[A-Z0-9\.-]', '', self.issuer)):
|
|
189
|
+
# # self.add_validation_message(
|
|
190
|
+
# # source="PAC-ID",
|
|
191
|
+
# # type="Recommendation",
|
|
192
|
+
# # highlight_pattern=self.issuer,
|
|
193
|
+
# # highlight_sub=not_recommended_chars,
|
|
194
|
+
# # msg=f"Characters {' '.join(not_recommended_chars)} should not be used. Issuer SHOULD contain only the characters A-Z, 0-9, -, and . "
|
|
195
|
+
# # )
|
|
196
|
+
# # return self
|
|
197
|
+
|
|
198
|
+
# # def __str__(self):
|
|
199
|
+
# # id_segments = ''
|
|
200
|
+
# # for s in self.identifier.segments:
|
|
201
|
+
# # if s.key:
|
|
202
|
+
# # id_segments += f'/{s.key}:{s.value}'
|
|
203
|
+
# # else:
|
|
204
|
+
# # id_segments += f'/{s.value}'
|
|
205
|
+
|
|
206
|
+
# # out = f"HTTPS://PAC.{self.issuer}{id_segments}"
|
|
207
|
+
# # return out
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# # class PACID_With_Extensions(BaseModelWithValidationMessages):
|
|
213
|
+
# # pac_id: PACID
|
|
214
|
+
# # extensions: list[Extension] = Field(default_factory=list)
|
|
215
|
+
|
|
216
|
+
# # def __str__(self):
|
|
217
|
+
# # out = str(self.pac_id)
|
|
218
|
+
# # out += '*'.join(str(e) for e in self.extensions)
|
|
219
|
+
|
|
220
|
+
# # def get_extension_of_type(self, type:str) -> list[Extension]:
|
|
221
|
+
# # return [e for e in self.extensions if e.type == type]
|
|
222
|
+
|
|
223
|
+
# # def get_extension(self, name:str) -> Extension|None:
|
|
224
|
+
# # out = [e for e in self.extensions if e.name == name]
|
|
225
|
+
# # if not out:
|
|
226
|
+
# # return None
|
|
227
|
+
# # return out[0]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
labfreed/PAC_CAT/data_model.py
CHANGED
|
@@ -1,25 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations # optional in 3.11, but recommended for consistency
|
|
2
|
+
|
|
1
3
|
from abc import ABC
|
|
2
|
-
from
|
|
3
|
-
from pydantic import
|
|
4
|
+
from typing import Self
|
|
5
|
+
from pydantic import Field, computed_field, model_validator
|
|
6
|
+
|
|
7
|
+
from labfreed.validation import BaseModelWithValidationMessages
|
|
8
|
+
|
|
9
|
+
from ..PAC_ID.data_model import PACID, IDSegment
|
|
4
10
|
|
|
5
|
-
from ..PAC_ID.data_model import IDSegment, Category
|
|
6
11
|
|
|
7
|
-
class CATBase(BaseModel, ABC):
|
|
8
|
-
category_key:str
|
|
9
|
-
additional_segments: list[IDSegment] = Field(default_factory=list)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
class PAC_CAT(PACID):
|
|
14
|
+
'''
|
|
15
|
+
Extends a PAC-ID with interpretation of the identifier as categories
|
|
16
|
+
'''
|
|
17
|
+
categories:list[Category] = Field(default_factory=list())
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def identifier(self) -> list[IDSegment]:
|
|
21
|
+
out = list()
|
|
22
|
+
for category in self.categories:
|
|
23
|
+
out.append(IDSegment(value=category.key))
|
|
24
|
+
out.extend(category.segments)
|
|
25
|
+
return out
|
|
26
|
+
|
|
27
|
+
def get_category(self, key):
|
|
28
|
+
tmp = [c for c in self.categories if c.key == key]
|
|
29
|
+
if not tmp:
|
|
30
|
+
return None
|
|
31
|
+
return tmp[0]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_pac_id(cls, pac_id:PACID):
|
|
36
|
+
d = pac_id.model_dump()
|
|
37
|
+
issuer = d.get('issuer')
|
|
38
|
+
|
|
39
|
+
category_segments = cls._split_into_categories(pac_id.identifier)
|
|
40
|
+
categories = list()
|
|
41
|
+
for c in category_segments:
|
|
42
|
+
categories.append(cls.cat_from_cat_segments(c))
|
|
43
|
+
|
|
44
|
+
return PAC_CAT(issuer=issuer, categories=categories, identifier=pac_id.identifier)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def cat_from_cat_segments(cls, segments:list[IDSegment]):
|
|
49
|
+
segments = segments.copy()
|
|
50
|
+
category_key = segments[0].value
|
|
51
|
+
segments.pop(0)
|
|
52
|
+
|
|
53
|
+
mapping = {
|
|
54
|
+
'-MD': Material_Device,
|
|
55
|
+
'-MS': Material_Substance,
|
|
56
|
+
'-MC': Material_Consumable,
|
|
57
|
+
'-MM': Material_Misc,
|
|
58
|
+
'-DM': Data_Method,
|
|
59
|
+
'-DR': Data_Result,
|
|
60
|
+
'-DC': Data_Calibration,
|
|
61
|
+
'-DP': Data_Progress,
|
|
62
|
+
'-DS': Data_Static
|
|
63
|
+
}
|
|
64
|
+
cat = mapping.get(category_key) or Category
|
|
65
|
+
|
|
66
|
+
# implicit segment keys
|
|
67
|
+
model_dict = {v.alias: None for k, v in cat.model_fields.items() if v.alias and k not in ['key','additional_segments']}
|
|
68
|
+
for k, seg in zip(model_dict.keys(), segments.copy()):
|
|
69
|
+
if seg.key:
|
|
70
|
+
break
|
|
71
|
+
model_dict[k] = seg.value
|
|
72
|
+
segments.pop(0)
|
|
73
|
+
|
|
74
|
+
# try to fill model keys if not already set
|
|
75
|
+
for s in segments:
|
|
76
|
+
if s.key in model_dict and not model_dict.get(s.key):
|
|
77
|
+
model_dict[s.key] = s.value
|
|
78
|
+
segments.remove(s)
|
|
79
|
+
|
|
80
|
+
model_dict['additional_segments'] = segments
|
|
81
|
+
model_dict['key'] = category_key
|
|
82
|
+
return cat(**model_dict)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _split_into_categories(segments:list[IDSegment]):
|
|
86
|
+
categories = list()
|
|
87
|
+
c_segments = list()
|
|
88
|
+
categories.append(c_segments)
|
|
89
|
+
for s in segments:
|
|
90
|
+
# new category starts with "-"
|
|
91
|
+
if s.value[0] == '-':
|
|
92
|
+
cat_key = s.value
|
|
93
|
+
c = [s]
|
|
94
|
+
categories.append(c)
|
|
95
|
+
else:
|
|
96
|
+
c.append(s)
|
|
97
|
+
|
|
98
|
+
# first cat can be empty > remove
|
|
99
|
+
categories = [c for c in categories if len(c) > 0]
|
|
100
|
+
|
|
101
|
+
return categories
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# @computed_field
|
|
105
|
+
# @property
|
|
106
|
+
# def categories(self) -> list[Category]:
|
|
107
|
+
# categories = list()
|
|
108
|
+
# c = Category(segments=[])
|
|
109
|
+
# categories.append(c)
|
|
110
|
+
# for s in self.identifier:
|
|
111
|
+
# s:IDSegment = s
|
|
112
|
+
# # new category starts with "-"
|
|
113
|
+
# if s.value[0] == '-':
|
|
114
|
+
# cat_key = s.value
|
|
115
|
+
# c = Category(key=cat_key, segments=[])
|
|
116
|
+
# categories.append(c)
|
|
117
|
+
# else:
|
|
118
|
+
# c.segments.append(s)
|
|
119
|
+
|
|
120
|
+
# # the first category might have no segments. remove categories without segments
|
|
121
|
+
# if not categories[0].segments:
|
|
122
|
+
# categories = categories[1:]
|
|
123
|
+
|
|
124
|
+
# return categories
|
|
125
|
+
|
|
126
|
+
@model_validator(mode='after')
|
|
127
|
+
def check_keys_are_unique_in_each_category(self) -> Self:
|
|
128
|
+
for c in self.categories:
|
|
129
|
+
keys = [s.key for s in c.segments if s.key]
|
|
130
|
+
duplicate_keys = [k for k in set(keys) if keys.count(k) > 1]
|
|
131
|
+
if duplicate_keys:
|
|
132
|
+
for k in duplicate_keys:
|
|
133
|
+
self.add_validation_message(
|
|
134
|
+
source=f"identifier {k}",
|
|
135
|
+
type="Error",
|
|
136
|
+
msg=f"Duplicate key {k} in category {c.key}",
|
|
137
|
+
highlight_pattern = k
|
|
138
|
+
)
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# @classmethod
|
|
143
|
+
# def from_categories(cls, issuer:str, categories:list[Category]):
|
|
144
|
+
# return PAC_CAT(issuer=issuer, identifier=cls.identifier_from_categories(categories))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# @classmethod
|
|
148
|
+
# def identifier_from_categories(cls, categories:list[Category]) :
|
|
149
|
+
# segments = list()
|
|
150
|
+
# for c in categories:
|
|
151
|
+
# if c.key:
|
|
152
|
+
# segments.append(IDSegment(value=c.key))
|
|
153
|
+
# segments.extend(c.segments)
|
|
154
|
+
# return segments
|
|
13
155
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Category(BaseModelWithValidationMessages):
|
|
161
|
+
model_config = {
|
|
162
|
+
"populate_by_name": True
|
|
163
|
+
}
|
|
164
|
+
key:str
|
|
165
|
+
additional_segments: list[IDSegment] = Field(default_factory=list)
|
|
166
|
+
|
|
167
|
+
@computed_field
|
|
168
|
+
@property
|
|
169
|
+
def segments(self, use_short_notation=False) -> list[IDSegment]:
|
|
19
170
|
segments = []
|
|
20
171
|
can_omit_keys = use_short_notation # keeps track of whether keys can still be omitted. That is the case when the segment recommendation is followed
|
|
21
172
|
for field_name, field_info in self.model_fields.items():
|
|
22
|
-
if field_name in ['
|
|
173
|
+
if field_name in ['key', 'additional_segments']:
|
|
23
174
|
continue
|
|
24
175
|
if value := getattr(self, field_name):
|
|
25
176
|
if can_omit_keys:
|
|
@@ -31,79 +182,188 @@ class CATBase(BaseModel, ABC):
|
|
|
31
182
|
can_omit_keys = False
|
|
32
183
|
if self.additional_segments:
|
|
33
184
|
segments.extend(self.additional_segments)
|
|
34
|
-
|
|
35
|
-
|
|
185
|
+
|
|
186
|
+
return segments
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@model_validator(mode='after')
|
|
190
|
+
def warn_unusual_category_key(self):
|
|
191
|
+
''' this base class is instantiated only if the key is not a known category key'''
|
|
192
|
+
self.add_validation_message(
|
|
193
|
+
source=f"Category {self.key}",
|
|
194
|
+
type="Warning",
|
|
195
|
+
msg=f'Category key {self.key} is not a well known key. It is recommended to use well known keys only',
|
|
196
|
+
highlight_pattern = f"{self.key}"
|
|
197
|
+
)
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# def to_identifier_category(self, use_short_notation=False):
|
|
202
|
+
# '''Creates a Category with the correct segments.
|
|
203
|
+
# Segments are in order of the Pydantic model fields.
|
|
204
|
+
# Segment keys are omitted as long as the recommendation is followed.
|
|
205
|
+
# Additional segments are added at the end'''
|
|
206
|
+
# segments = []
|
|
207
|
+
# can_omit_keys = use_short_notation # keeps track of whether keys can still be omitted. That is the case when the segment recommendation is followed
|
|
208
|
+
# for field_name, field_info in self.model_fields.items():
|
|
209
|
+
# if field_name in ['category_key', 'additional_segments']:
|
|
210
|
+
# continue
|
|
211
|
+
# if value := getattr(self, field_name):
|
|
212
|
+
# if can_omit_keys:
|
|
213
|
+
# key = None
|
|
214
|
+
# else:
|
|
215
|
+
# key = field_info.alias
|
|
216
|
+
# segments.append(IDSegment(key= key, value= value) )
|
|
217
|
+
# else:
|
|
218
|
+
# can_omit_keys = False
|
|
219
|
+
# if self.additional_segments:
|
|
220
|
+
# segments.extend(self.additional_segments)
|
|
221
|
+
# return Category(key=self.key,
|
|
222
|
+
# segments=segments)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# def _apply_category_defaults(self, segments_in: list[IDSegment]):
|
|
227
|
+
|
|
228
|
+
# category_conventions = MappingProxyType(
|
|
229
|
+
# {
|
|
230
|
+
# '-MD': ['240', '21'],
|
|
231
|
+
# '-MS': ['240', '10', '20', '21', '250'],
|
|
232
|
+
# '-MC': ['240', '10', '20', '21', '250'],
|
|
233
|
+
# '-MM': ['240', '10', '20', '21', '250']
|
|
234
|
+
# }
|
|
235
|
+
# )
|
|
236
|
+
|
|
237
|
+
# segments = segments_in.copy()
|
|
238
|
+
# default_keys = None
|
|
239
|
+
# for s in segments:
|
|
240
|
+
# if not s.key and default_keys:
|
|
241
|
+
# s.key = default_keys.pop(0)
|
|
242
|
+
# else:
|
|
243
|
+
# default_keys = None
|
|
244
|
+
|
|
245
|
+
# # category starts: start with new defaults.
|
|
246
|
+
# if s.value in category_conventions.keys():
|
|
247
|
+
# default_keys = category_conventions.get(s.value).copy() #copy, so the entries can be popped when used
|
|
248
|
+
# return segments
|
|
36
249
|
|
|
37
250
|
|
|
38
251
|
|
|
39
252
|
|
|
40
|
-
class Material_Device(
|
|
41
|
-
|
|
42
|
-
model_number: str = Field( alias='240'
|
|
43
|
-
serial_number: str = Field( alias='21'
|
|
253
|
+
class Material_Device(Category):
|
|
254
|
+
key: str = Field(default='-MD', frozen=True)
|
|
255
|
+
model_number: str|None = Field( alias='240')
|
|
256
|
+
serial_number: str|None = Field( alias='21')
|
|
257
|
+
|
|
258
|
+
@model_validator(mode='after')
|
|
259
|
+
def validate_mandatory_fields(self):
|
|
260
|
+
if not self.model_number:
|
|
261
|
+
self.add_validation_message(
|
|
262
|
+
source=f"Category {self.key}",
|
|
263
|
+
type="Error",
|
|
264
|
+
msg=f'Category key {self.key} is missing mandatory field Model NUmber',
|
|
265
|
+
highlight_pattern = f"{self.key}"
|
|
266
|
+
)
|
|
267
|
+
if not self.serial_number:
|
|
268
|
+
self.add_validation_message(
|
|
269
|
+
source=f"Category {self.key}",
|
|
270
|
+
type="Error",
|
|
271
|
+
msg=f'Category key {self.key} is missing mandatory field Serial NUmber',
|
|
272
|
+
highlight_pattern = f"{self.key}"
|
|
273
|
+
)
|
|
44
274
|
|
|
45
|
-
class Material_Substance(
|
|
46
|
-
|
|
47
|
-
product_number:str =
|
|
275
|
+
class Material_Substance(Category):
|
|
276
|
+
key: str = Field(default='-MS', frozen=True)
|
|
277
|
+
product_number:str|None = Field( alias='240')
|
|
48
278
|
batch_number:str|None = Field(default=None, alias='10')
|
|
49
279
|
container_size:str|None = Field(default=None, alias='20')
|
|
50
280
|
container_number:str|None = Field(default=None, alias='21')
|
|
51
281
|
aliquot:str|None = Field(default=None, alias='250')
|
|
52
282
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
283
|
+
@model_validator(mode='after')
|
|
284
|
+
def validate_mandatory_fields(self):
|
|
285
|
+
if not self.product_number:
|
|
286
|
+
self.add_validation_message(
|
|
287
|
+
source=f"Category {self.key}",
|
|
288
|
+
type="Error",
|
|
289
|
+
msg=f'Category key {self.key} is missing mandatory field Product NUmber',
|
|
290
|
+
highlight_pattern = f"{self.key}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
class Material_Consumable(Category):
|
|
294
|
+
key: str = Field(default='-MC', frozen=True)
|
|
295
|
+
product_number:str|None = Field( alias='240')
|
|
56
296
|
batch_number:str|None = Field(default=None, alias='10')
|
|
57
297
|
packing_size:str|None = Field(default=None, alias='20')
|
|
58
298
|
serial_number:str|None = Field(default=None, alias='21')
|
|
59
299
|
aliquot:str|None = Field(default=None, alias='250')
|
|
60
300
|
|
|
301
|
+
@model_validator(mode='after')
|
|
302
|
+
def validate_mandatory_fields(self):
|
|
303
|
+
if not self.product_number:
|
|
304
|
+
self.add_validation_message(
|
|
305
|
+
source=f"Category {self.key}",
|
|
306
|
+
type="Error",
|
|
307
|
+
msg=f"Category key {self.key} is missing mandatory field 'Product Number'",
|
|
308
|
+
highlight_pattern = f"{self.key}"
|
|
309
|
+
)
|
|
310
|
+
|
|
61
311
|
class Material_Misc(Material_Consumable):
|
|
62
|
-
|
|
312
|
+
key: str = Field(default='-MM', frozen=True)
|
|
313
|
+
|
|
314
|
+
|
|
63
315
|
|
|
64
316
|
|
|
317
|
+
class Data_Abstract(Category, ABC):
|
|
318
|
+
key: str
|
|
319
|
+
id:str|None = Field( alias='21')
|
|
320
|
+
|
|
321
|
+
@model_validator(mode='after')
|
|
322
|
+
def validate_mandatory_fields(self):
|
|
323
|
+
if not self.id:
|
|
324
|
+
self.add_validation_message(
|
|
325
|
+
source=f"Category {self.key}",
|
|
326
|
+
type="Error",
|
|
327
|
+
msg=f"Category key {self.key} is missing mandatory field 'ID'",
|
|
328
|
+
highlight_pattern = f"{self.key}"
|
|
329
|
+
)
|
|
65
330
|
|
|
66
|
-
class Data_Result(
|
|
67
|
-
|
|
68
|
-
id:str = Field( alias='21', min_length=1)
|
|
331
|
+
class Data_Result(Category):
|
|
332
|
+
key: str = Field(default='-DR', frozen=True)
|
|
69
333
|
|
|
70
|
-
class Data_Method(
|
|
71
|
-
|
|
72
|
-
id:str = Field( alias='21', min_length=1)
|
|
334
|
+
class Data_Method(Category):
|
|
335
|
+
key: str = Field(default='-DM', frozen=True)
|
|
73
336
|
|
|
74
|
-
class Data_Calibration(
|
|
75
|
-
|
|
76
|
-
id:str = Field( alias='21', min_length=1)
|
|
337
|
+
class Data_Calibration(Category):
|
|
338
|
+
key: str = Field(default='-DC', frozen=True)
|
|
77
339
|
|
|
78
|
-
class Data_Progress(
|
|
79
|
-
|
|
80
|
-
id:str = Field( alias='21', min_length=1)
|
|
340
|
+
class Data_Progress(Category):
|
|
341
|
+
key: str = Field(default='-DP', frozen=True)
|
|
81
342
|
|
|
82
|
-
class Data_Static(
|
|
83
|
-
|
|
84
|
-
id:str = Field( alias='21', min_length=1)
|
|
343
|
+
class Data_Static(Category):
|
|
344
|
+
key: str = Field(default='-DS', frozen=True)
|
|
85
345
|
|
|
86
346
|
|
|
87
347
|
|
|
88
348
|
|
|
89
|
-
mapping = {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
349
|
+
# mapping = {
|
|
350
|
+
# '-MD': Material_Device,
|
|
351
|
+
# '-MS': Material_Substance,
|
|
352
|
+
# '-MC': Material_Consumable,
|
|
353
|
+
# '-MM': Material_Misc,
|
|
354
|
+
# '-DM': Data_Method,
|
|
355
|
+
# '-DR': Data_Result,
|
|
356
|
+
# '-DC': Data_Calibration,
|
|
357
|
+
# '-DP': Data_Progress,
|
|
358
|
+
# '-DS': Data_Static
|
|
359
|
+
# }
|
|
100
360
|
|
|
101
|
-
def CAT_from_category(category:Category) ->
|
|
102
|
-
|
|
361
|
+
# def CAT_from_category(category:Category) -> Category|None:
|
|
362
|
+
# raise NotImplementedError()
|
|
103
363
|
|
|
104
|
-
def CAT_from_category_key(category_key) ->
|
|
105
|
-
|
|
364
|
+
# def CAT_from_category_key(category_key) -> Category|None:
|
|
365
|
+
# return mapping.get(category_key)
|
|
106
366
|
|
|
107
367
|
|
|
108
|
-
if __name__ == "__main__":
|
|
109
|
-
|
|
368
|
+
# if __name__ == "__main__":
|
|
369
|
+
# pass
|