|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +from senaite.astm import records |
| 4 | +from senaite.astm.fields import ComponentField |
| 5 | +from senaite.astm.fields import DateField |
| 6 | +from senaite.astm.fields import DateTimeField |
| 7 | +from senaite.astm.fields import JSONListField |
| 8 | +from senaite.astm.fields import NotUsedField |
| 9 | +from senaite.astm.fields import SetField |
| 10 | +from senaite.astm.fields import TextField |
| 11 | +from senaite.astm.mapping import Component |
| 12 | + |
| 13 | +VERSION = "1.0.0" |
| 14 | +HEADER_RX = r".*(?<=\|)(LIS|ABX)(?=\|).*" |
| 15 | + |
| 16 | + |
| 17 | +def get_metadata(wrapper): |
| 18 | + """Additional metadata |
| 19 | +
|
| 20 | + :param wrapper: The wrapper instance |
| 21 | + :returns: dictionary of additional metadata |
| 22 | + """ |
| 23 | + return { |
| 24 | + "version": VERSION, |
| 25 | + "header_rx": HEADER_RX, |
| 26 | + } |
| 27 | + |
| 28 | + |
| 29 | +def get_mapping(): |
| 30 | + """Returns the wrappers for this instrument |
| 31 | + """ |
| 32 | + return { |
| 33 | + "H": HeaderRecord, |
| 34 | + "P": PatientRecord, |
| 35 | + "O": OrderRecord, |
| 36 | + "R": ResultRecord, |
| 37 | + "C": CommentRecord, |
| 38 | + "Q": RequestInformationRecord, |
| 39 | + "M": ManufacturerInfoRecord, |
| 40 | + "L": TerminatorRecord, |
| 41 | + } |
| 42 | + |
| 43 | + |
| 44 | +class HeaderRecord(records.HeaderRecord): |
| 45 | + """Message Header Record (H) |
| 46 | + """ |
| 47 | + |
| 48 | + # Note: Although the field comes in as a single value text, we need to nest |
| 49 | + # it for senaite.core.astm.consumer.get_sender_name to work properly |
| 50 | + sender = ComponentField( |
| 51 | + Component.build( |
| 52 | + TextField(name="name"), |
| 53 | + )) |
| 54 | + |
| 55 | + processing_id = SetField( |
| 56 | + field=TextField(), |
| 57 | + # P: Patient message, Q: Quality control message, D: Technician |
| 58 | + values=("P", "Q", "D")) |
| 59 | + |
| 60 | + version = TextField() |
| 61 | + |
| 62 | + |
| 63 | +class PatientRecord(records.PatientRecord): |
| 64 | + """Patient Information Record (P) |
| 65 | + """ |
| 66 | + |
| 67 | + # Patient Id (Advised on ABX Pentra XL80 for workflow management) |
| 68 | + laboratory_id = TextField(length=25) |
| 69 | + |
| 70 | + # format: Name^First name |
| 71 | + name = ComponentField( |
| 72 | + Component.build( |
| 73 | + TextField(name="name"), |
| 74 | + TextField(name="first_name"), |
| 75 | + )) |
| 76 | + |
| 77 | + # format: YYYYMMDD |
| 78 | + birthdate = DateField() |
| 79 | + |
| 80 | + # M, F or U |
| 81 | + sex = SetField( |
| 82 | + field=TextField(), |
| 83 | + length=1, |
| 84 | + values=("M", "F", "U")) |
| 85 | + |
| 86 | + |
| 87 | +class OrderRecord(records.OrderRecord): |
| 88 | + """Order Record (O) |
| 89 | + """ |
| 90 | + |
| 91 | + # Note: Field 9.4.3 "Sample ID" for ABX Pentra XL80 and Pentra XLR (Only |
| 92 | + # from Instrument to Host) is presented as follows: |
| 93 | + # SampleID^Rack(2 digits max.)^TubePosition(2 digits max.), e.g. |
| 94 | + # 45264012^02^08 |
| 95 | + sample_id = ComponentField( |
| 96 | + Component.build( |
| 97 | + TextField(name="sample_id"), |
| 98 | + TextField(name="rack"), |
| 99 | + TextField(name="position"), |
| 100 | + ) |
| 101 | + ) |
| 102 | + |
| 103 | + # Note: Field 9.4.5 "Universal test ID" must be filled by the parameters |
| 104 | + # panel requested (CBC or DIF or RET or DIR or CBR): |
| 105 | + # Refer to Special characteristics for HORIBA Medical data on page 16.). |
| 106 | + test = ComponentField( |
| 107 | + Component.build( |
| 108 | + NotUsedField(name="_"), |
| 109 | + NotUsedField(name="__"), |
| 110 | + NotUsedField(name="___"), |
| 111 | + SetField( |
| 112 | + name="testname", |
| 113 | + field=TextField(), |
| 114 | + values=("CBC", "DIF", "RET", "DIR", "CBR")), |
| 115 | + )) |
| 116 | + |
| 117 | + # format: YYYYMMDDHHMMSS |
| 118 | + sampled_at = DateTimeField() |
| 119 | + |
| 120 | + # format: YYYYMMDDHHMMSS |
| 121 | + collected_at = DateTimeField() |
| 122 | + |
| 123 | + biomaterial = TextField() |
| 124 | + report_type = SetField( |
| 125 | + field=TextField(), |
| 126 | + length=1, |
| 127 | + values=("F", "C")) # F: final; C: correction |
| 128 | + |
| 129 | + |
| 130 | +class CommentRecord(records.CommentRecord): |
| 131 | + """Comment Record (C) |
| 132 | + """ |
| 133 | + # I: clinical instrument system |
| 134 | + source = SetField( |
| 135 | + field=TextField(), |
| 136 | + length=1, |
| 137 | + values=("I")) |
| 138 | + |
| 139 | + # Text (Refer to Table 23 - Analytical alarms, page 18; Table 24 - Analyzer |
| 140 | + # alarms, page 18; Table 25 - Suspected pathologies, page 18. |
| 141 | + # |
| 142 | + # Note: values come in either as a string or a list with variable length. |
| 143 | + # Therefore, we simply handle it as a JSON list |
| 144 | + data = JSONListField() |
| 145 | + |
| 146 | + # G:Free text |
| 147 | + # I: Instrument flag comment |
| 148 | + # L: Comment from host (Patient order) P80 V1.1 and above |
| 149 | + ctype = SetField( |
| 150 | + field=TextField(), |
| 151 | + length=1, |
| 152 | + values=("G", "I", "L")) |
| 153 | + |
| 154 | + |
| 155 | +class ResultRecord(records.ResultRecord): |
| 156 | + """Record to transmit analytical data. |
| 157 | + """ |
| 158 | + |
| 159 | + # Note: Field 10.1.3 "Universal TestID" for ABX Pentra XL80 and Pentra XLR |
| 160 | + # includes the dilution ratio as follows: ^^^Result name in english^LOINC |
| 161 | + # code^CDR (CDR=1 or 2 or 3 or 5). Results are returned in between (). |
| 162 | + test = ComponentField( |
| 163 | + Component.build( |
| 164 | + NotUsedField(name='_'), |
| 165 | + NotUsedField(name='__'), |
| 166 | + NotUsedField(name='___'), |
| 167 | + TextField(name='result_name'), |
| 168 | + TextField(name='loinc_code'), |
| 169 | + SetField( |
| 170 | + name="testname", |
| 171 | + field=TextField(), |
| 172 | + values=("1", "2", "3", "5")), |
| 173 | + )) |
| 174 | + |
| 175 | + value = TextField() |
| 176 | + |
| 177 | + units = SetField( |
| 178 | + field=TextField(), |
| 179 | + length=1, |
| 180 | + values=("1", "2", "3", "4")) |
| 181 | + |
| 182 | + abnormal_flag = SetField( |
| 183 | + field=TextField(), |
| 184 | + length=2, |
| 185 | + values=("L", "H", "LL", "HH", ">")) |
| 186 | + |
| 187 | + # W: suspicion |
| 188 | + # N: rejected result |
| 189 | + # F: final result |
| 190 | + # X: Parameter exceeding the capacity |
| 191 | + # (ABX Pentra 80 / ABX Pentra XL80 / Pentra XLR) |
| 192 | + # M: Value input manually (ABX Pentra XL80 / Pentra XLR) |
| 193 | + # D: Value obtained by dilution (ABX Pentra XL80 / Pentra XLR) |
| 194 | + status = SetField( |
| 195 | + field=TextField(), |
| 196 | + length=1, |
| 197 | + values=("W", "N", "F", "X", "M", "D")) |
| 198 | + |
| 199 | + # Operator Code + Name |
| 200 | + operator = TextField() |
| 201 | + |
| 202 | + completed_at = DateTimeField() |
| 203 | + |
| 204 | + |
| 205 | +class RequestInformationRecord(records.RequestInformationRecord): |
| 206 | + """Request information Record (Q) |
| 207 | + """ |
| 208 | + |
| 209 | + |
| 210 | +class ManufacturerInfoRecord(records.ManufacturerInfoRecord): |
| 211 | + """Manufacturer Specific Records (M) |
| 212 | + """ |
| 213 | + |
| 214 | + |
| 215 | +class TerminatorRecord(records.TerminatorRecord): |
| 216 | + """Message Termination Record (L) |
| 217 | + """ |
0 commit comments