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/tasks/psychiatricclerking.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""" 

28 

29from typing import Dict, List, Optional 

30 

31import cardinal_pythonlib.rnc_web as ws 

32from sqlalchemy.sql.schema import Column 

33from sqlalchemy.sql.sqltypes import UnicodeText 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_ctvinfo import CtvInfo 

37from camcops_server.cc_modules.cc_request import CamcopsRequest 

38from camcops_server.cc_modules.cc_snomed import ( 

39 SnomedConcept, 

40 SnomedExpression, 

41 SnomedLookup, 

42) 

43from camcops_server.cc_modules.cc_sqlalchemy import Base 

44from camcops_server.cc_modules.cc_task import ( 

45 Task, 

46 TaskHasClinicianMixin, 

47 TaskHasPatientMixin, 

48) 

49 

50 

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

52# PsychiatricClerking 

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

54 

55class PsychiatricClerking(TaskHasPatientMixin, TaskHasClinicianMixin, Task, 

56 Base): 

57 """ 

58 Server implementation of the Clerking task. 

59 """ 

60 __tablename__ = "psychiatricclerking" 

61 shortname = "Clerking" 

62 

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

64 

65 location = Column("location", UnicodeText) 

66 contact_type = Column("contact_type", UnicodeText) 

67 reason_for_contact = Column("reason_for_contact", UnicodeText) 

68 presenting_issue = Column("presenting_issue", UnicodeText) 

69 systems_review = Column("systems_review", UnicodeText) 

70 collateral_history = Column("collateral_history", UnicodeText) 

71 

72 diagnoses_psychiatric = Column("diagnoses_psychiatric", UnicodeText) 

73 diagnoses_medical = Column("diagnoses_medical", UnicodeText) 

74 operations_procedures = Column("operations_procedures", UnicodeText) 

75 allergies_adverse_reactions = Column("allergies_adverse_reactions", UnicodeText) # noqa 

76 medications = Column("medications", UnicodeText) 

77 recreational_drug_use = Column("recreational_drug_use", UnicodeText) 

78 family_history = Column("family_history", UnicodeText) 

79 developmental_history = Column("developmental_history", UnicodeText) 

80 personal_history = Column("personal_history", UnicodeText) 

81 premorbid_personality = Column("premorbid_personality", UnicodeText) 

82 forensic_history = Column("forensic_history", UnicodeText) 

83 current_social_situation = Column("current_social_situation", UnicodeText) 

84 

85 mse_appearance_behaviour = Column("mse_appearance_behaviour", UnicodeText) 

86 mse_speech = Column("mse_speech", UnicodeText) 

87 mse_mood_subjective = Column("mse_mood_subjective", UnicodeText) 

88 mse_mood_objective = Column("mse_mood_objective", UnicodeText) 

89 mse_thought_form = Column("mse_thought_form", UnicodeText) 

90 mse_thought_content = Column("mse_thought_content", UnicodeText) 

91 mse_perception = Column("mse_perception", UnicodeText) 

92 mse_cognition = Column("mse_cognition", UnicodeText) 

93 mse_insight = Column("mse_insight", UnicodeText) 

94 

95 physical_examination_general = Column("physical_examination_general", UnicodeText) # noqa 

96 physical_examination_cardiovascular = Column("physical_examination_cardiovascular", UnicodeText) # noqa 

97 physical_examination_respiratory = Column("physical_examination_respiratory", UnicodeText) # noqa 

98 physical_examination_abdominal = Column("physical_examination_abdominal", UnicodeText) # noqa 

99 physical_examination_neurological = Column("physical_examination_neurological", UnicodeText) # noqa 

100 

101 assessment_scales = Column("assessment_scales", UnicodeText) 

102 investigations_results = Column("investigations_results", UnicodeText) 

103 

104 safety_alerts = Column("safety_alerts", UnicodeText) 

105 risk_assessment = Column("risk_assessment", UnicodeText) 

106 relevant_legal_information = Column("relevant_legal_information", UnicodeText) # noqa 

107 

108 current_problems = Column("current_problems", UnicodeText) 

109 patient_carer_concerns = Column("patient_carer_concerns", UnicodeText) 

110 impression = Column("impression", UnicodeText) 

111 management_plan = Column("management_plan", UnicodeText) 

112 information_given = Column("information_given", UnicodeText) 

113 

114 FIELDS_B = [ 

115 "location", "contact_type", "reason_for_contact", 

116 "presenting_issue", "systems_review", "collateral_history" 

117 ] 

118 FIELDS_C = [ 

119 "diagnoses_psychiatric", "diagnoses_medical", "operations_procedures", 

120 "allergies_adverse_reactions", "medications", "recreational_drug_use", 

121 "family_history", "developmental_history", "personal_history", 

122 "premorbid_personality", "forensic_history", "current_social_situation" 

123 ] 

124 FIELDS_MSE = [ 

125 "mse_appearance_behaviour", "mse_speech", "mse_mood_subjective", 

126 "mse_mood_objective", "mse_thought_form", "mse_thought_content", 

127 "mse_perception", "mse_cognition", "mse_insight" 

128 ] 

129 FIELDS_PE = [ 

130 "physical_examination_general", "physical_examination_cardiovascular", 

131 "physical_examination_respiratory", "physical_examination_abdominal", 

132 "physical_examination_neurological" 

133 ] 

134 FIELDS_D = [ 

135 "assessment_scales", "investigations_results" 

136 ] 

137 FIELDS_E = [ 

138 "safety_alerts", "risk_assessment", "relevant_legal_information" 

139 ] 

140 FIELDS_F = [ 

141 "current_problems", "patient_carer_concerns", "impression", 

142 "management_plan", "information_given" 

143 ] 

144 

145 @staticmethod 

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

147 _ = req.gettext 

148 return _("Psychiatric clerking") 

149 

150 def get_ctv_heading(self, req: CamcopsRequest, 

151 wstringname: str) -> CtvInfo: 

152 return CtvInfo( 

153 heading=self.wxstring(req, wstringname), 

154 skip_if_no_content=False 

155 ) 

156 

157 def get_ctv_subheading(self, req: CamcopsRequest, 

158 wstringname: str) -> CtvInfo: 

159 return CtvInfo( 

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

161 skip_if_no_content=False 

162 ) 

163 

164 def get_ctv_description_content(self, req: CamcopsRequest, 

165 x: str) -> CtvInfo: 

166 return CtvInfo( 

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

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

169 ) 

170 

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

172 infolist = [self.get_ctv_heading( 

173 req, "heading_current_contact")] 

174 for x in self.FIELDS_B: 

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

176 infolist.append(self.get_ctv_heading( 

177 req, "heading_background")) 

178 for x in self.FIELDS_C: 

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

180 infolist.append(self.get_ctv_heading( 

181 req, "heading_examination_investigations")) 

182 infolist.append(self.get_ctv_subheading( 

183 req, "mental_state_examination")) 

184 for x in self.FIELDS_MSE: 

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

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

187 for x in self.FIELDS_PE: 

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

189 infolist.append(self.get_ctv_subheading( 

190 req, "assessments_and_investigations")) 

191 for x in self.FIELDS_D: 

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

193 infolist.append(self.get_ctv_heading( 

194 req, "heading_risk_legal")) 

195 for x in self.FIELDS_E: 

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

197 infolist.append(self.get_ctv_heading( 

198 req, "heading_summary_plan")) 

199 for x in self.FIELDS_F: 

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

201 return infolist 

202 

203 # noinspection PyMethodOverriding 

204 @staticmethod 

205 def is_complete() -> bool: 

206 return True 

207 

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

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

210 CssClass=CssClass, 

211 s=self.wxstring(req, wstringname), 

212 ) 

213 

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

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

216 CssClass=CssClass, 

217 s=self.wxstring(req, wstringname) 

218 ) 

219 

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

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

222 CssClass=CssClass, 

223 s=self.wxstring(req, wstringname) 

224 ) 

225 

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

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

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

229 ) 

230 

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

232 return ( 

233 self.subsubheading(req, fieldname) + 

234 f'<div><b>{ws.webify(getattr(self, fieldname))}</b></div>' 

235 ) 

236 

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

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

239 html = "" 

240 html += self.heading( 

241 req, "heading_current_contact") 

242 for x in self.FIELDS_B: 

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

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

245 for x in self.FIELDS_C: 

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

247 html += self.heading( 

248 req, "heading_examination_investigations") 

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

250 for x in self.FIELDS_MSE: 

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

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

253 for x in self.FIELDS_PE: 

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

255 for x in self.FIELDS_D: 

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

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

258 for x in self.FIELDS_E: 

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

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

261 for x in self.FIELDS_F: 

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

263 return html 

264 

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

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

267 

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

269 if not contents: 

270 return 

271 nonlocal refinement 

272 concept = req.snomed(snomed_lookup) 

273 refinement[concept] = contents 

274 

275 # not location 

276 # not contact type 

277 add(SnomedLookup.PSYCLERK_REASON_FOR_REFERRAL, self.reason_for_contact) 

278 add(SnomedLookup.PSYCLERK_PRESENTING_ISSUE, self.presenting_issue) 

279 add(SnomedLookup.PSYCLERK_SYSTEMS_REVIEW, self.systems_review) 

280 add(SnomedLookup.PSYCLERK_COLLATERAL_HISTORY, self.collateral_history) 

281 

282 add(SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, 

283 self.diagnoses_medical) 

284 add(SnomedLookup.PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY, 

285 self.diagnoses_psychiatric) 

286 add(SnomedLookup.PSYCLERK_PROCEDURES, self.operations_procedures) 

287 add(SnomedLookup.PSYCLERK_ALLERGIES_ADVERSE_REACTIONS, 

288 self.allergies_adverse_reactions) 

289 add(SnomedLookup.PSYCLERK_MEDICATIONS_MEDICAL_DEVICES, self.medications) 

290 add(SnomedLookup.PSYCLERK_DRUG_SUBSTANCE_USE, 

291 self.recreational_drug_use) 

292 add(SnomedLookup.PSYCLERK_FAMILY_HISTORY, self.family_history) 

293 add(SnomedLookup.PSYCLERK_DEVELOPMENTAL_HISTORY, 

294 self.developmental_history) 

295 add(SnomedLookup.PSYCLERK_SOCIAL_PERSONAL_HISTORY, 

296 self.personal_history) 

297 add(SnomedLookup.PSYCLERK_PERSONALITY, self.premorbid_personality) 

298 add(SnomedLookup.PSYCLERK_PRISON_RECORD_CRIMINAL_ACTIVITY, 

299 self.forensic_history) 

300 add(SnomedLookup.PSYCLERK_SOCIAL_HISTORY_BASELINE, 

301 self.current_social_situation) 

302 

303 add(SnomedLookup.PSYCLERK_MSE_APPEARANCE, 

304 self.mse_appearance_behaviour) # duplication 

305 add(SnomedLookup.PSYCLERK_MSE_BEHAVIOUR, 

306 self.mse_appearance_behaviour) # duplication 

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

308 add(SnomedLookup.PSYCLERK_MSE_AFFECT, self.mse_mood_objective) 

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

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

311 # affect. Not perfect, but reasonable. 

312 add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_form) 

313 add(SnomedLookup.PSYCLERK_MSE_THOUGHT, self.mse_thought_content) 

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

315 add(SnomedLookup.PSYCLERK_MSE_PERCEPTION, self.mse_perception) 

316 add(SnomedLookup.PSYCLERK_MSE_COGNITION, self.mse_cognition) 

317 add(SnomedLookup.PSYCLERK_MSE_INSIGHT, self.mse_insight) 

318 

319 add(SnomedLookup.PSYCLERK_PHYSEXAM_GENERAL, 

320 self.physical_examination_general) 

321 add(SnomedLookup.PSYCLERK_PHYSEXAM_CARDIOVASCULAR, 

322 self.physical_examination_cardiovascular) 

323 add(SnomedLookup.PSYCLERK_PHYSEXAM_RESPIRATORY, 

324 self.physical_examination_respiratory) 

325 add(SnomedLookup.PSYCLERK_PHYSEXAM_ABDOMINAL, 

326 self.physical_examination_abdominal) 

327 add(SnomedLookup.PSYCLERK_PHYSEXAM_NEUROLOGICAL, 

328 self.physical_examination_neurological) 

329 

330 add(SnomedLookup.PSYCLERK_ASSESSMENT_SCALES, self.assessment_scales) 

331 add(SnomedLookup.PSYCLERK_INVESTIGATIONS_RESULTS, 

332 self.investigations_results) 

333 

334 add(SnomedLookup.PSYCLERK_SAFETY_ALERTS, self.safety_alerts) 

335 add(SnomedLookup.PSYCLERK_RISK_ASSESSMENT, self.risk_assessment) 

336 add(SnomedLookup.PSYCLERK_RELEVANT_LEGAL_INFORMATION, 

337 self.relevant_legal_information) 

338 

339 add(SnomedLookup.PSYCLERK_CURRENT_PROBLEMS, self.current_problems) 

340 add(SnomedLookup.PSYCLERK_PATIENT_CARER_CONCERNS, 

341 self.patient_carer_concerns) 

342 add(SnomedLookup.PSYCLERK_CLINICAL_NARRATIVE, self.impression) 

343 add(SnomedLookup.PSYCLERK_MANAGEMENT_PLAN, self.management_plan) 

344 add(SnomedLookup.PSYCLERK_INFORMATION_GIVEN, self.information_given) 

345 

346 codes = [SnomedExpression( 

347 req.snomed(SnomedLookup.PSYCHIATRIC_ASSESSMENT_PROCEDURE), 

348 refinement=refinement or None, 

349 )] 

350 return codes