Coverage for tasks/psychiatricclerking.py : 43%

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
3"""
4camcops_server/tasks/psychiatricclerking.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
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.
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.
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/>.
25===============================================================================
27"""
29from typing import Dict, List, Optional
31import cardinal_pythonlib.rnc_web as ws
32from sqlalchemy.sql.schema import Column
33from sqlalchemy.sql.sqltypes import UnicodeText
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)
51# =============================================================================
52# PsychiatricClerking
53# =============================================================================
55class PsychiatricClerking(TaskHasPatientMixin, TaskHasClinicianMixin, Task,
56 Base):
57 """
58 Server implementation of the Clerking task.
59 """
60 __tablename__ = "psychiatricclerking"
61 shortname = "Clerking"
63 # FIELDSPEC_A = CLINICIAN_FIELDSPECS # replaced by has_clinician, then by TaskHasClinicianMixin # noqa
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)
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)
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)
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
101 assessment_scales = Column("assessment_scales", UnicodeText)
102 investigations_results = Column("investigations_results", UnicodeText)
104 safety_alerts = Column("safety_alerts", UnicodeText)
105 risk_assessment = Column("risk_assessment", UnicodeText)
106 relevant_legal_information = Column("relevant_legal_information", UnicodeText) # noqa
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)
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 ]
145 @staticmethod
146 def longname(req: "CamcopsRequest") -> str:
147 _ = req.gettext
148 return _("Psychiatric clerking")
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 )
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 )
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 )
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
203 # noinspection PyMethodOverriding
204 @staticmethod
205 def is_complete() -> bool:
206 return True
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 )
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 )
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 )
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 )
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 )
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
265 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
266 refinement = {} # type: Dict[SnomedConcept, str]
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
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)
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)
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)
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)
330 add(SnomedLookup.PSYCLERK_ASSESSMENT_SCALES, self.assessment_scales)
331 add(SnomedLookup.PSYCLERK_INVESTIGATIONS_RESULTS,
332 self.investigations_results)
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)
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)
346 codes = [SnomedExpression(
347 req.snomed(SnomedLookup.PSYCHIATRIC_ASSESSMENT_PROCEDURE),
348 refinement=refinement or None,
349 )]
350 return codes