Coverage for tasks/psychiatricclerking.py: 43%

184 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/psychiatricclerking.py 

5 

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

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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

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

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

16 (at your option) any later version. 

17 

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

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

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

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

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

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import Dict, List, Optional 

31 

32import cardinal_pythonlib.rnc_web as ws 

33from sqlalchemy.sql.schema import Column 

34from sqlalchemy.sql.sqltypes import UnicodeText 

35 

36from camcops_server.cc_modules.cc_constants import CssClass 

37from camcops_server.cc_modules.cc_ctvinfo import CtvInfo 

38from camcops_server.cc_modules.cc_request import CamcopsRequest 

39from camcops_server.cc_modules.cc_snomed import ( 

40 SnomedConcept, 

41 SnomedExpression, 

42 SnomedLookup, 

43) 

44from camcops_server.cc_modules.cc_sqlalchemy import Base 

45from camcops_server.cc_modules.cc_task import ( 

46 Task, 

47 TaskHasClinicianMixin, 

48 TaskHasPatientMixin, 

49) 

50 

51 

52# ============================================================================= 

53# PsychiatricClerking 

54# ============================================================================= 

55 

56 

57class PsychiatricClerking( 

58 TaskHasPatientMixin, TaskHasClinicianMixin, Task, Base 

59): 

60 """ 

61 Server implementation of the Clerking task. 

62 """ 

63 

64 __tablename__ = "psychiatricclerking" 

65 shortname = "Clerking" 

66 info_filename_stem = "clinical" 

67 

68 # FIELDSPEC_A = CLINICIAN_FIELDSPECS # replaced by has_clinician, then by TaskHasClinicianMixin # noqa 

69 

70 location = Column("location", UnicodeText) 

71 contact_type = Column("contact_type", UnicodeText) 

72 reason_for_contact = Column("reason_for_contact", UnicodeText) 

73 presenting_issue = Column("presenting_issue", UnicodeText) 

74 systems_review = Column("systems_review", UnicodeText) 

75 collateral_history = Column("collateral_history", UnicodeText) 

76 

77 diagnoses_psychiatric = Column("diagnoses_psychiatric", UnicodeText) 

78 diagnoses_medical = Column("diagnoses_medical", UnicodeText) 

79 operations_procedures = Column("operations_procedures", UnicodeText) 

80 allergies_adverse_reactions = Column( 

81 "allergies_adverse_reactions", UnicodeText 

82 ) 

83 medications = Column("medications", UnicodeText) 

84 recreational_drug_use = Column("recreational_drug_use", UnicodeText) 

85 family_history = Column("family_history", UnicodeText) 

86 developmental_history = Column("developmental_history", UnicodeText) 

87 personal_history = Column("personal_history", UnicodeText) 

88 premorbid_personality = Column("premorbid_personality", UnicodeText) 

89 forensic_history = Column("forensic_history", UnicodeText) 

90 current_social_situation = Column("current_social_situation", UnicodeText) 

91 

92 mse_appearance_behaviour = Column("mse_appearance_behaviour", UnicodeText) 

93 mse_speech = Column("mse_speech", UnicodeText) 

94 mse_mood_subjective = Column("mse_mood_subjective", UnicodeText) 

95 mse_mood_objective = Column("mse_mood_objective", UnicodeText) 

96 mse_thought_form = Column("mse_thought_form", UnicodeText) 

97 mse_thought_content = Column("mse_thought_content", UnicodeText) 

98 mse_perception = Column("mse_perception", UnicodeText) 

99 mse_cognition = Column("mse_cognition", UnicodeText) 

100 mse_insight = Column("mse_insight", UnicodeText) 

101 

102 physical_examination_general = Column( 

103 "physical_examination_general", UnicodeText 

104 ) 

105 physical_examination_cardiovascular = Column( 

106 "physical_examination_cardiovascular", UnicodeText 

107 ) 

108 physical_examination_respiratory = Column( 

109 "physical_examination_respiratory", UnicodeText 

110 ) 

111 physical_examination_abdominal = Column( 

112 "physical_examination_abdominal", UnicodeText 

113 ) 

114 physical_examination_neurological = Column( 

115 "physical_examination_neurological", UnicodeText 

116 ) 

117 

118 assessment_scales = Column("assessment_scales", UnicodeText) 

119 investigations_results = Column("investigations_results", UnicodeText) 

120 

121 safety_alerts = Column("safety_alerts", UnicodeText) 

122 risk_assessment = Column("risk_assessment", UnicodeText) 

123 relevant_legal_information = Column( 

124 "relevant_legal_information", UnicodeText 

125 ) 

126 

127 current_problems = Column("current_problems", UnicodeText) 

128 patient_carer_concerns = Column("patient_carer_concerns", UnicodeText) 

129 impression = Column("impression", UnicodeText) 

130 management_plan = Column("management_plan", UnicodeText) 

131 information_given = Column("information_given", UnicodeText) 

132 

133 FIELDS_B = [ 

134 "location", 

135 "contact_type", 

136 "reason_for_contact", 

137 "presenting_issue", 

138 "systems_review", 

139 "collateral_history", 

140 ] 

141 FIELDS_C = [ 

142 "diagnoses_psychiatric", 

143 "diagnoses_medical", 

144 "operations_procedures", 

145 "allergies_adverse_reactions", 

146 "medications", 

147 "recreational_drug_use", 

148 "family_history", 

149 "developmental_history", 

150 "personal_history", 

151 "premorbid_personality", 

152 "forensic_history", 

153 "current_social_situation", 

154 ] 

155 FIELDS_MSE = [ 

156 "mse_appearance_behaviour", 

157 "mse_speech", 

158 "mse_mood_subjective", 

159 "mse_mood_objective", 

160 "mse_thought_form", 

161 "mse_thought_content", 

162 "mse_perception", 

163 "mse_cognition", 

164 "mse_insight", 

165 ] 

166 FIELDS_PE = [ 

167 "physical_examination_general", 

168 "physical_examination_cardiovascular", 

169 "physical_examination_respiratory", 

170 "physical_examination_abdominal", 

171 "physical_examination_neurological", 

172 ] 

173 FIELDS_D = ["assessment_scales", "investigations_results"] 

174 FIELDS_E = [ 

175 "safety_alerts", 

176 "risk_assessment", 

177 "relevant_legal_information", 

178 ] 

179 FIELDS_F = [ 

180 "current_problems", 

181 "patient_carer_concerns", 

182 "impression", 

183 "management_plan", 

184 "information_given", 

185 ] 

186 

187 @staticmethod 

188 def longname(req: "CamcopsRequest") -> str: 

189 _ = req.gettext 

190 return _("Psychiatric clerking") 

191 

192 def get_ctv_heading( 

193 self, req: CamcopsRequest, wstringname: str 

194 ) -> CtvInfo: 

195 return CtvInfo( 

196 heading=self.wxstring(req, wstringname), skip_if_no_content=False 

197 ) 

198 

199 def get_ctv_subheading( 

200 self, req: CamcopsRequest, wstringname: str 

201 ) -> CtvInfo: 

202 return CtvInfo( 

203 subheading=self.wxstring(req, wstringname), 

204 skip_if_no_content=False, 

205 ) 

206 

207 def get_ctv_description_content( 

208 self, req: CamcopsRequest, x: str 

209 ) -> CtvInfo: 

210 return CtvInfo( 

211 description=self.wxstring(req, x), 

212 content=ws.webify(getattr(self, x)), 

213 ) 

214 

215 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

216 infolist = [self.get_ctv_heading(req, "heading_current_contact")] 

217 for x in self.FIELDS_B: 

218 infolist.append(self.get_ctv_description_content(req, x)) 

219 infolist.append(self.get_ctv_heading(req, "heading_background")) 

220 for x in self.FIELDS_C: 

221 infolist.append(self.get_ctv_description_content(req, x)) 

222 infolist.append( 

223 self.get_ctv_heading(req, "heading_examination_investigations") 

224 ) 

225 infolist.append( 

226 self.get_ctv_subheading(req, "mental_state_examination") 

227 ) 

228 for x in self.FIELDS_MSE: 

229 infolist.append(self.get_ctv_description_content(req, x)) 

230 infolist.append(self.get_ctv_subheading(req, "physical_examination")) 

231 for x in self.FIELDS_PE: 

232 infolist.append(self.get_ctv_description_content(req, x)) 

233 infolist.append( 

234 self.get_ctv_subheading(req, "assessments_and_investigations") 

235 ) 

236 for x in self.FIELDS_D: 

237 infolist.append(self.get_ctv_description_content(req, x)) 

238 infolist.append(self.get_ctv_heading(req, "heading_risk_legal")) 

239 for x in self.FIELDS_E: 

240 infolist.append(self.get_ctv_description_content(req, x)) 

241 infolist.append(self.get_ctv_heading(req, "heading_summary_plan")) 

242 for x in self.FIELDS_F: 

243 infolist.append(self.get_ctv_description_content(req, x)) 

244 return infolist 

245 

246 # noinspection PyMethodOverriding 

247 @staticmethod 

248 def is_complete() -> bool: 

249 return True 

250 

251 def heading(self, req: CamcopsRequest, wstringname: str) -> str: 

252 return '<div class="{CssClass.HEADING}">{s}</div>'.format( 

253 CssClass=CssClass, s=self.wxstring(req, wstringname) 

254 ) 

255 

256 def subheading(self, req: CamcopsRequest, wstringname: str) -> str: 

257 return '<div class="{CssClass.SUBHEADING}">{s}</div>'.format( 

258 CssClass=CssClass, s=self.wxstring(req, wstringname) 

259 ) 

260 

261 def subsubheading(self, req: CamcopsRequest, wstringname: str) -> str: 

262 return '<div class="{CssClass.SUBSUBHEADING}">{s}</div>'.format( 

263 CssClass=CssClass, s=self.wxstring(req, wstringname) 

264 ) 

265 

266 def subhead_text(self, req: CamcopsRequest, fieldname: str) -> str: 

267 return self.subheading(req, fieldname) + "<div><b>{}</b></div>".format( 

268 ws.webify(getattr(self, fieldname)) 

269 ) 

270 

271 def subsubhead_text(self, req: CamcopsRequest, fieldname: str) -> str: 

272 return ( 

273 self.subsubheading(req, fieldname) 

274 + f"<div><b>{ws.webify(getattr(self, fieldname))}</b></div>" 

275 ) 

276 

277 def get_task_html(self, req: CamcopsRequest) -> str: 

278 # Avoid tables - PDF generator crashes if text is too long. 

279 html = "" 

280 html += self.heading(req, "heading_current_contact") 

281 for x in self.FIELDS_B: 

282 html += self.subhead_text(req, x) 

283 html += self.heading(req, "heading_background") 

284 for x in self.FIELDS_C: 

285 html += self.subhead_text(req, x) 

286 html += self.heading(req, "heading_examination_investigations") 

287 html += self.subheading(req, "mental_state_examination") 

288 for x in self.FIELDS_MSE: 

289 html += self.subsubhead_text(req, x) 

290 html += self.subheading(req, "physical_examination") 

291 for x in self.FIELDS_PE: 

292 html += self.subsubhead_text(req, x) 

293 for x in self.FIELDS_D: 

294 html += self.subhead_text(req, x) 

295 html += self.heading(req, "heading_risk_legal") 

296 for x in self.FIELDS_E: 

297 html += self.subhead_text(req, x) 

298 html += self.heading(req, "heading_summary_plan") 

299 for x in self.FIELDS_F: 

300 html += self.subhead_text(req, x) 

301 return html 

302 

303 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]: 

304 refinement = {} # type: Dict[SnomedConcept, str] 

305 

306 def add(snomed_lookup: str, contents: Optional[str]) -> None: 

307 if not contents: 

308 return 

309 nonlocal refinement 

310 concept = req.snomed(snomed_lookup) 

311 refinement[concept] = contents 

312 

313 # not location 

314 # not contact type 

315 add(SnomedLookup.PSYCLERK_REASON_FOR_REFERRAL, self.reason_for_contact) 

316 add(SnomedLookup.PSYCLERK_PRESENTING_ISSUE, self.presenting_issue) 

317 add(SnomedLookup.PSYCLERK_SYSTEMS_REVIEW, self.systems_review) 

318 add(SnomedLookup.PSYCLERK_COLLATERAL_HISTORY, self.collateral_history) 

319 

320 add( 

321 SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, 

322 self.diagnoses_medical, 

323 ) 

324 add( 

325 SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, 

326 self.diagnoses_psychiatric, 

327 ) 

328 add(SnomedLookup.PSYCLERK_PROCEDURES, self.operations_procedures) 

329 add( 

330 SnomedLookup.PSYCLERK_ALLERGIES_ADVERSE_REACTIONS, 

331 self.allergies_adverse_reactions, 

332 ) 

333 add( 

334 SnomedLookup.PSYCLERK_MEDICATIONS_MEDICAL_DEVICES, self.medications 

335 ) 

336 add( 

337 SnomedLookup.PSYCLERK_DRUG_SUBSTANCE_USE, 

338 self.recreational_drug_use, 

339 ) 

340 add(SnomedLookup.PSYCLERK_FAMILY_HISTORY, self.family_history) 

341 add( 

342 SnomedLookup.PSYCLERK_DEVELOPMENTAL_HISTORY, 

343 self.developmental_history, 

344 ) 

345 add( 

346 SnomedLookup.PSYCLERK_SOCIAL_PERSONAL_HISTORY, 

347 self.personal_history, 

348 ) 

349 add(SnomedLookup.PSYCLERK_PERSONALITY, self.premorbid_personality) 

350 add( 

351 SnomedLookup.PSYCLERK_PRISON_RECORD_CRIMINAL_ACTIVITY, 

352 self.forensic_history, 

353 ) 

354 add( 

355 SnomedLookup.PSYCLERK_SOCIAL_HISTORY_BASELINE, 

356 self.current_social_situation, 

357 ) 

358 

359 add( 

360 SnomedLookup.PSYCLERK_MSE_APPEARANCE, self.mse_appearance_behaviour 

361 ) # duplication 

362 add( 

363 SnomedLookup.PSYCLERK_MSE_BEHAVIOUR, self.mse_appearance_behaviour 

364 ) # duplication 

365 add(SnomedLookup.PSYCLERK_MSE_MOOD, self.mse_mood_subjective) # close 

366 add(SnomedLookup.PSYCLERK_MSE_AFFECT, self.mse_mood_objective) 

367 # ... Logic here: "objective mood" is certainly affect (emotional 

368 # weather). "Subjective mood" is both mood (emotional climate) and 

369 # affect. Not perfect, but reasonable. 

370 add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_form) 

371 add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_content) 

372 # ... No way of disambiguating the two in SNOMED-CT. 

373 add(SnomedLookup.PSYCLERK_MSE_PERCEPTION, self.mse_perception) 

374 add(SnomedLookup.PSYCLERK_MSE_COGNITION, self.mse_cognition) 

375 add(SnomedLookup.PSYCLERK_MSE_INSIGHT, self.mse_insight) 

376 

377 add( 

378 SnomedLookup.PSYCLERK_PHYSEXAM_GENERAL, 

379 self.physical_examination_general, 

380 ) 

381 add( 

382 SnomedLookup.PSYCLERK_PHYSEXAM_CARDIOVASCULAR, 

383 self.physical_examination_cardiovascular, 

384 ) 

385 add( 

386 SnomedLookup.PSYCLERK_PHYSEXAM_RESPIRATORY, 

387 self.physical_examination_respiratory, 

388 ) 

389 add( 

390 SnomedLookup.PSYCLERK_PHYSEXAM_ABDOMINAL, 

391 self.physical_examination_abdominal, 

392 ) 

393 add( 

394 SnomedLookup.PSYCLERK_PHYSEXAM_NEUROLOGICAL, 

395 self.physical_examination_neurological, 

396 ) 

397 

398 add(SnomedLookup.PSYCLERK_ASSESSMENT_SCALES, self.assessment_scales) 

399 add( 

400 SnomedLookup.PSYCLERK_INVESTIGATIONS_RESULTS, 

401 self.investigations_results, 

402 ) 

403 

404 add(SnomedLookup.PSYCLERK_SAFETY_ALERTS, self.safety_alerts) 

405 add(SnomedLookup.PSYCLERK_RISK_ASSESSMENT, self.risk_assessment) 

406 add( 

407 SnomedLookup.PSYCLERK_RELEVANT_LEGAL_INFORMATION, 

408 self.relevant_legal_information, 

409 ) 

410 

411 add(SnomedLookup.PSYCLERK_CURRENT_PROBLEMS, self.current_problems) 

412 add( 

413 SnomedLookup.PSYCLERK_PATIENT_CARER_CONCERNS, 

414 self.patient_carer_concerns, 

415 ) 

416 add(SnomedLookup.PSYCLERK_CLINICAL_NARRATIVE, self.impression) 

417 add(SnomedLookup.PSYCLERK_MANAGEMENT_PLAN, self.management_plan) 

418 add(SnomedLookup.PSYCLERK_INFORMATION_GIVEN, self.information_given) 

419 

420 codes = [ 

421 SnomedExpression( 

422 req.snomed( 

423 SnomedLookup.DIAGNOSTIC_PSYCHIATRIC_INTERVIEW_PROCEDURE 

424 ), 

425 refinement=refinement or None, 

426 ) 

427 ] 

428 return codes