Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_simpleobjects.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27**Simple struct-like classes.** 

28 

29""" 

30 

31import copy 

32# import logging 

33from typing import List, TYPE_CHECKING 

34 

35from pendulum import Date 

36 

37from cardinal_pythonlib.datetimefunc import format_datetime 

38from cardinal_pythonlib.reprfunc import auto_repr 

39 

40from camcops_server.cc_modules.cc_constants import DateFormat 

41 

42if TYPE_CHECKING: 

43 from camcops_server.cc_modules.cc_request import CamcopsRequest 

44 

45# log = logging.getLogger(__name__) 

46 

47# Prefer classes to collections.namedtuple; both support type checking but 

48# classes support better parameter checking (and refactoring) via PyCharm. 

49 

50 

51# ============================================================================= 

52# IdNumReference 

53# ============================================================================= 

54 

55class IdNumReference(object): 

56 """ 

57 A simple way of referring to an ID number. 

58 

59 It's not stored in the database -- it's just an object to be passed around 

60 that encapsulates ``which_idnum`` and ``idnum_value``. 

61 

62 As an example, suppose our administrator has defined ID type 

63 (``which_idnum``) 7 to be "NHS number". Then if a patient has NHS number 

64 9999999999, we might represent this ID of theirs as 

65 ``IdNumReference(which_idnum=7, idnum_value=9999999999)``. 

66 """ 

67 def __init__(self, which_idnum: int, idnum_value: int) -> None: 

68 self.which_idnum = which_idnum 

69 self.idnum_value = idnum_value 

70 

71 def __str__(self) -> str: 

72 return f"idnum{self.which_idnum}={self.idnum_value}" 

73 

74 def __repr__(self) -> str: 

75 return auto_repr(self) 

76 

77 def is_valid(self) -> bool: 

78 return ( 

79 self.which_idnum is not None and self.which_idnum > 0 and 

80 self.idnum_value is not None and self.idnum_value > 0 

81 ) 

82 

83 def __eq__(self, other: "IdNumReference") -> bool: 

84 if not isinstance(other, IdNumReference): 

85 return False 

86 return ( 

87 self.which_idnum == other.which_idnum and 

88 self.idnum_value == other.idnum_value 

89 ) 

90 

91 def description(self, req: "CamcopsRequest") -> str: 

92 if not self.is_valid(): 

93 return "[invalid_IdNumReference]" 

94 return f"{req.get_id_shortdesc(self.which_idnum)} = {self.idnum_value}" 

95 

96 

97# ============================================================================= 

98# HL7PatientIdentifier 

99# ============================================================================= 

100 

101# noinspection PyShadowingBuiltins 

102class HL7PatientIdentifier(object): 

103 """ 

104 Represents a patient identifier for the HL7 protocol. 

105 """ 

106 def __init__(self, pid: str, id_type: str, 

107 assigning_authority: str) -> None: 

108 self.pid = pid 

109 # ... called "pid" not "id" as type checker sometimes thinks "id" must 

110 # be integer, as in Python's id(object). 

111 self.id_type = id_type 

112 self.assigning_authority = assigning_authority 

113 

114 

115# ============================================================================= 

116# BarePatientInfo 

117# ============================================================================= 

118 

119class BarePatientInfo(object): 

120 """ 

121 Represents information about a patient using a simple object with no 

122 connection to a database. 

123 

124 In some situations we avoid using 

125 :class:`camcops_server.cc_modules.cc_patient.Patient`: specifically, when 

126 we would otherwise have to deal with mutual dependency problems and the use 

127 of the database (prior to full database initialization). 

128 """ 

129 def __init__(self, 

130 forename: str = None, 

131 surname: str = None, 

132 sex: str = None, 

133 dob: Date = None, 

134 address: str = None, 

135 email: str = None, 

136 gp: str = None, 

137 otherdetails: str = None, 

138 idnum_definitions: List[IdNumReference] = None) -> None: 

139 self.forename = forename 

140 self.surname = surname 

141 self.sex = sex 

142 self.dob = dob 

143 self.address = address 

144 self.email = email 

145 self.gp = gp 

146 self.otherdetails = otherdetails 

147 self.idnum_definitions = idnum_definitions or [] # type: List[IdNumReference] # noqa 

148 

149 def __str__(self) -> str: 

150 return ( 

151 "Patient(forename={f!r}, surname={sur!r}, sex={sex!r}, DOB={dob}, " 

152 "address={a!r}, email={email!r}, gp={gp!r}, otherdetails={o!r}, " 

153 "idnums={i})".format( 

154 f=self.forename, 

155 sur=self.surname, 

156 sex=self.sex, 

157 dob=format_datetime(self.dob, DateFormat.ISO8601_DATE_ONLY), 

158 a=self.address, 

159 email=self.email, 

160 gp=self.gp, 

161 o=self.otherdetails, 

162 i="[{}]".format(", ".join( 

163 str(idnum) for idnum in self.idnum_definitions)), 

164 ) 

165 ) 

166 

167 def __repr__(self) -> str: 

168 return auto_repr(self) 

169 

170 def add_idnum(self, idref: IdNumReference) -> None: 

171 """ 

172 Adds an ID number. No checks in relation to what's already present. 

173 

174 Args: 

175 idref: a :class:`IdNumReference` 

176 """ 

177 self.idnum_definitions.append(idref) 

178 

179 def __eq__(self, other: "BarePatientInfo") -> bool: 

180 """ 

181 Do all data elements match those of ``other``? 

182 """ 

183 if not isinstance(other, BarePatientInfo): 

184 return False 

185 return ( 

186 self.forename == other.forename and 

187 self.surname == other.surname and 

188 self.sex == other.sex and 

189 self.dob == other.dob and 

190 self.address == other.address and 

191 self.email == other.email and 

192 self.gp == other.gp and 

193 self.otherdetails == other.otherdetails and 

194 self.idnum_definitions == other.idnum_definitions 

195 ) 

196 

197 

198# ============================================================================= 

199# Raw XML value 

200# ============================================================================= 

201 

202class XmlSimpleValue(object): 

203 """ 

204 Represents XML lowest-level items. See functions in ``cc_xml.py``. 

205 """ 

206 def __init__(self, value) -> None: 

207 self.value = value 

208 

209 

210# ============================================================================= 

211# TaskExportOptions 

212# ============================================================================= 

213 

214class TaskExportOptions(object): 

215 """ 

216 Information-holding object for options controlling XML and other 

217 representations of tasks. 

218 """ 

219 def __init__(self, 

220 db_patient_id_per_row: bool = False, 

221 db_make_all_tables_even_empty: bool = False, 

222 db_include_summaries: bool = False, 

223 include_blobs: bool = False, 

224 xml_include_ancillary: bool = False, 

225 xml_include_calculated: bool = False, 

226 xml_include_comments: bool = True, 

227 xml_include_patient: bool = False, 

228 xml_include_plain_columns: bool = False, 

229 xml_include_snomed: bool = False, 

230 xml_skip_fields: List[str] = None, 

231 xml_sort_by_name: bool = True, 

232 xml_with_header_comments: bool = False) -> None: 

233 """ 

234 Args: 

235 db_patient_id_per_row: 

236 generates an anonymisation staging database -- that is, a 

237 database with patient IDs in every row of every table, suitable 

238 for feeding into an anonymisation system like CRATE 

239 (https://doi.org/10.1186%2Fs12911-017-0437-1). 

240 db_make_all_tables_even_empty: 

241 create all tables, even empty ones 

242 

243 include_blobs: 

244 include binary large objects (BLOBs) (applies to several export 

245 formats) 

246 

247 xml_include_ancillary: 

248 include ancillary tables as well as the main? 

249 xml_include_calculated: 

250 include fields calculated by the task 

251 xml_include_comments: 

252 include comments in XML? 

253 xml_include_patient: 

254 include patient details? 

255 xml_include_plain_columns: 

256 include the base columns 

257 xml_include_snomed: 

258 include SNOMED-CT codes, if available? 

259 xml_skip_fields: 

260 fieldnames to skip 

261 xml_sort_by_name: 

262 sort by field/attribute names? 

263 xml_with_header_comments: 

264 include header-style comments? 

265 """ 

266 self.db_patient_id_in_each_row = db_patient_id_per_row 

267 self.db_make_all_tables_even_empty = db_make_all_tables_even_empty 

268 self.db_include_summaries = db_include_summaries 

269 

270 self.include_blobs = include_blobs 

271 

272 self.xml_include_ancillary = xml_include_ancillary 

273 self.xml_include_calculated = xml_include_calculated 

274 self.xml_include_comments = xml_include_comments 

275 self.xml_include_patient = xml_include_patient 

276 self.xml_include_plain_columns = xml_include_plain_columns 

277 self.xml_include_snomed = xml_include_snomed 

278 self.xml_skip_fields = xml_skip_fields or [] # type: List[str] 

279 self.xml_sort_by_name = xml_sort_by_name 

280 self.xml_with_header_comments = xml_with_header_comments 

281 

282 def clone(self) -> "TaskExportOptions": 

283 """ 

284 Returns a copy of this object. 

285 """ 

286 return copy.copy(self)