Coverage for tasks/psychiatricclerking.py: 43%
184 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/psychiatricclerking.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
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.
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.
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/>.
26===============================================================================
28"""
30from typing import Dict, List, Optional
32import cardinal_pythonlib.rnc_web as ws
33from sqlalchemy.sql.schema import Column
34from sqlalchemy.sql.sqltypes import UnicodeText
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)
52# =============================================================================
53# PsychiatricClerking
54# =============================================================================
57class PsychiatricClerking(
58 TaskHasPatientMixin, TaskHasClinicianMixin, Task, Base
59):
60 """
61 Server implementation of the Clerking task.
62 """
64 __tablename__ = "psychiatricclerking"
65 shortname = "Clerking"
66 info_filename_stem = "clinical"
68 # FIELDSPEC_A = CLINICIAN_FIELDSPECS # replaced by has_clinician, then by TaskHasClinicianMixin # noqa
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)
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)
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)
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 )
118 assessment_scales = Column("assessment_scales", UnicodeText)
119 investigations_results = Column("investigations_results", UnicodeText)
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 )
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)
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 ]
187 @staticmethod
188 def longname(req: "CamcopsRequest") -> str:
189 _ = req.gettext
190 return _("Psychiatric clerking")
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 )
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 )
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 )
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
246 # noinspection PyMethodOverriding
247 @staticmethod
248 def is_complete() -> bool:
249 return True
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 )
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 )
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 )
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 )
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 )
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
303 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
304 refinement = {} # type: Dict[SnomedConcept, str]
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
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)
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 )
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)
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 )
398 add(SnomedLookup.PSYCLERK_ASSESSMENT_SCALES, self.assessment_scales)
399 add(
400 SnomedLookup.PSYCLERK_INVESTIGATIONS_RESULTS,
401 self.investigations_results,
402 )
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 )
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)
420 codes = [
421 SnomedExpression(
422 req.snomed(
423 SnomedLookup.DIAGNOSTIC_PSYCHIATRIC_INTERVIEW_PROCEDURE
424 ),
425 refinement=refinement or None,
426 )
427 ]
428 return codes