Coverage for tasks/cpft_lps.py: 51%
366 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/cpft_lps.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"""
30import logging
31from typing import Any, List, Optional, Type
33from cardinal_pythonlib.classes import classproperty
34from cardinal_pythonlib.datetimefunc import format_datetime
35from cardinal_pythonlib.logs import BraceStyleAdapter
36import cardinal_pythonlib.rnc_web as ws
37import pyramid.httpexceptions as exc
38from sqlalchemy.sql.expression import and_, exists, select
39from sqlalchemy.sql.selectable import SelectBase
40from sqlalchemy.sql.schema import Column
41from sqlalchemy.sql.sqltypes import Date, Integer, UnicodeText
43from camcops_server.cc_modules.cc_constants import (
44 CssClass,
45 DateFormat,
46 INVALID_VALUE,
47)
48from camcops_server.cc_modules.cc_ctvinfo import CtvInfo
49from camcops_server.cc_modules.cc_forms import (
50 LinkingIdNumSelector,
51 ReportParamSchema,
52)
53from camcops_server.cc_modules.cc_html import (
54 answer,
55 get_yes_no_none,
56 subheading_spanning_four_columns,
57 subheading_spanning_two_columns,
58 tr_qa,
59 tr_span_col,
60)
61from camcops_server.cc_modules.cc_nhs import (
62 get_nhs_dd_ethnic_category_code,
63 get_nhs_dd_person_marital_status,
64 PV_NHS_ETHNIC_CATEGORY,
65 PV_NHS_MARITAL_STATUS,
66)
67from camcops_server.cc_modules.cc_patient import Patient
68from camcops_server.cc_modules.cc_patientidnum import PatientIdNum
69from camcops_server.cc_modules.cc_pyramid import ViewParam
70from camcops_server.cc_modules.cc_report import Report
71from camcops_server.cc_modules.cc_request import CamcopsRequest
72from camcops_server.cc_modules.cc_sqla_coltypes import (
73 BoolColumn,
74 CamcopsColumn,
75 CharColType,
76 PendulumDateTimeAsIsoTextColType,
77 DiagnosticCodeColType,
78 PermittedValueChecker,
79)
80from camcops_server.cc_modules.cc_task import (
81 Task,
82 TaskHasClinicianMixin,
83 TaskHasPatientMixin,
84)
85from camcops_server.tasks.psychiatricclerking import PsychiatricClerking
87log = BraceStyleAdapter(logging.getLogger(__name__))
90# =============================================================================
91# CPFT_LPS_Referral
92# =============================================================================
95class CPFTLPSReferral(TaskHasPatientMixin, Task):
96 """
97 Server implementation of the CPFT_LPS_Referral task.
98 """
100 __tablename__ = "cpft_lps_referral"
101 shortname = "CPFT_LPS_Referral"
102 info_filename_stem = "clinical"
104 referral_date_time = Column(
105 "referral_date_time", PendulumDateTimeAsIsoTextColType
106 )
107 lps_division = CamcopsColumn(
108 "lps_division", UnicodeText, exempt_from_anonymisation=True
109 )
110 referral_priority = CamcopsColumn(
111 "referral_priority", UnicodeText, exempt_from_anonymisation=True
112 )
113 referral_method = CamcopsColumn(
114 "referral_method", UnicodeText, exempt_from_anonymisation=True
115 )
116 referrer_name = Column("referrer_name", UnicodeText)
117 referrer_contact_details = Column("referrer_contact_details", UnicodeText)
118 referring_consultant = Column("referring_consultant", UnicodeText)
119 referring_specialty = CamcopsColumn(
120 "referring_specialty", UnicodeText, exempt_from_anonymisation=True
121 )
122 referring_specialty_other = Column(
123 "referring_specialty_other", UnicodeText
124 )
125 patient_location = Column("patient_location", UnicodeText)
126 admission_date = Column("admission_date", Date)
127 estimated_discharge_date = Column("estimated_discharge_date", Date)
128 patient_aware_of_referral = BoolColumn("patient_aware_of_referral")
129 interpreter_required = BoolColumn("interpreter_required")
130 sensory_impairment = BoolColumn("sensory_impairment")
131 marital_status_code = CamcopsColumn(
132 "marital_status_code",
133 CharColType,
134 permitted_value_checker=PermittedValueChecker(
135 permitted_values=PV_NHS_MARITAL_STATUS
136 ),
137 )
138 ethnic_category_code = CamcopsColumn(
139 "ethnic_category_code",
140 CharColType,
141 permitted_value_checker=PermittedValueChecker(
142 permitted_values=PV_NHS_ETHNIC_CATEGORY
143 ),
144 )
145 admission_reason_overdose = BoolColumn("admission_reason_overdose")
146 admission_reason_self_harm_not_overdose = BoolColumn(
147 "admission_reason_self_harm_not_overdose",
148 constraint_name="ck_cpft_lps_referral_arshno",
149 )
150 admission_reason_confusion = BoolColumn("admission_reason_confusion")
151 admission_reason_trauma = BoolColumn("admission_reason_trauma")
152 admission_reason_falls = BoolColumn("admission_reason_falls")
153 admission_reason_infection = BoolColumn("admission_reason_infection")
154 admission_reason_poor_adherence = BoolColumn(
155 "admission_reason_poor_adherence",
156 constraint_name="ck_cpft_lps_referral_adpa",
157 )
158 admission_reason_other = BoolColumn("admission_reason_other")
159 existing_psychiatric_teams = Column(
160 "existing_psychiatric_teams", UnicodeText
161 )
162 care_coordinator = Column("care_coordinator", UnicodeText)
163 other_contact_details = Column("other_contact_details", UnicodeText)
164 referral_reason = Column("referral_reason", UnicodeText)
166 @staticmethod
167 def longname(req: "CamcopsRequest") -> str:
168 _ = req.gettext
169 return _("CPFT LPS – referral")
171 def is_complete(self) -> bool:
172 return bool(
173 self.patient_location
174 and self.referral_reason
175 and self.field_contents_valid()
176 )
178 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
179 return [
180 CtvInfo(
181 heading=ws.webify(self.wxstring(req, "f_referral_reason_t")),
182 content=self.referral_reason,
183 )
184 ]
186 @staticmethod
187 def four_column_row(
188 q1: str, a1: Any, q2: str, a2: Any, default: str = ""
189 ) -> str:
190 return f"""
191 <tr>
192 <td>{q1}</td><td>{answer(a1, default=default)}</td>
193 <td>{q2}</td><td>{answer(a2, default=default)}</td>
194 </tr>
195 """
197 @staticmethod
198 def tr_qa(q: str, a: Any, default: str = "") -> str:
199 return f"""
200 <tr>
201 <td colspan="2">{q}</td>
202 <td colspan="2"><b>{default if a is None else a}</b></td>
203 </tr>
204 """
206 def get_task_html(self, req: CamcopsRequest) -> str:
207 person_marital_status = get_nhs_dd_person_marital_status(req)
208 ethnic_category_code = get_nhs_dd_ethnic_category_code(req)
209 if self.lps_division == "G":
210 banner_class = CssClass.BANNER_REFERRAL_GENERAL_ADULT
211 division_name = self.wxstring(req, "service_G")
212 elif self.lps_division == "O":
213 banner_class = CssClass.BANNER_REFERRAL_OLD_AGE
214 division_name = self.wxstring(req, "service_O")
215 elif self.lps_division == "S":
216 banner_class = CssClass.BANNER_REFERRAL_SUBSTANCE_MISUSE
217 division_name = self.wxstring(req, "service_S")
218 else:
219 banner_class = ""
220 division_name = None
222 if self.referral_priority == "R":
223 priority_name = self.wxstring(req, "priority_R")
224 elif self.referral_priority == "U":
225 priority_name = self.wxstring(req, "priority_U")
226 elif self.referral_priority == "E":
227 priority_name = self.wxstring(req, "priority_E")
228 else:
229 priority_name = None
231 potential_admission_reasons = [
232 "admission_reason_overdose",
233 "admission_reason_self_harm_not_overdose",
234 "admission_reason_confusion",
235 "admission_reason_trauma",
236 "admission_reason_falls",
237 "admission_reason_infection",
238 "admission_reason_poor_adherence",
239 "admission_reason_other",
240 ]
241 admission_reasons = []
242 for r in potential_admission_reasons:
243 if getattr(self, r):
244 admission_reasons.append(self.wxstring(req, "f_" + r))
246 h = f"""
247 <div class="{CssClass.BANNER} {banner_class}">
248 {answer(division_name, default_for_blank_strings=True)}
249 referral at {
250 answer(format_datetime(
251 self.referral_date_time,
252 DateFormat.SHORT_DATETIME_WITH_DAY_NO_TZ,
253 default=None))}
254 </div>
255 <div class="{CssClass.SUMMARY}">
256 <table class="{CssClass.SUMMARY}">
257 {self.get_is_complete_tr(req)}
258 </table>
259 </div>
260 <table class="{CssClass.TASKDETAIL}">
261 <col width="25%">
262 <col width="25%">
263 <col width="25%">
264 <col width="25%">
265 """
266 h += subheading_spanning_four_columns(
267 self.wxstring(req, "t_about_referral")
268 )
269 h += """
270 <tr>
271 <td>{q_method}</td>
272 <td>{a_method}</td>
273 <td>{q_priority}</td>
274 <td class="{CssClass.HIGHLIGHT}">{a_priority}</td>
275 </tr>
276 """.format(
277 CssClass=CssClass,
278 q_method=self.wxstring(req, "f_referral_method"),
279 a_method=answer(self.referral_method),
280 q_priority=self.wxstring(req, "f_referral_priority"),
281 a_priority=(
282 answer(self.referral_priority, default_for_blank_strings=True)
283 + ": " # noqa
284 + answer(priority_name)
285 ),
286 )
287 h += self.four_column_row(
288 self.wxstring(req, "f_referrer_name"),
289 self.referrer_name,
290 self.wxstring(req, "f_referring_specialty"),
291 self.referring_specialty,
292 )
293 h += self.four_column_row(
294 self.wxstring(req, "f_referrer_contact_details"),
295 self.referrer_contact_details,
296 self.wxstring(req, "f_referring_specialty_other"),
297 self.referring_specialty_other,
298 )
299 h += self.four_column_row(
300 self.wxstring(req, "f_referring_consultant"),
301 self.referring_consultant,
302 "",
303 "",
304 )
305 h += subheading_spanning_four_columns(self.wxstring(req, "t_patient"))
306 h += """
307 <tr>
308 <td>{q_when}</td>
309 <td>{a_when}</td>
310 <td>{q_where}</td>
311 <td class="{CssClass.HIGHLIGHT}">{a_where}</td>
312 </tr>
313 """.format(
314 CssClass=CssClass,
315 q_when=self.wxstring(req, "f_admission_date"),
316 a_when=answer(
317 format_datetime(
318 self.admission_date, DateFormat.LONG_DATE, default=None
319 ),
320 "",
321 ),
322 q_where=self.wxstring(req, "f_patient_location"),
323 a_where=answer(self.patient_location),
324 )
325 h += self.four_column_row(
326 self.wxstring(req, "f_estimated_discharge_date"),
327 format_datetime(
328 self.estimated_discharge_date, DateFormat.LONG_DATE, ""
329 ),
330 self.wxstring(req, "f_patient_aware_of_referral"),
331 get_yes_no_none(req, self.patient_aware_of_referral),
332 )
333 h += self.four_column_row(
334 self.wxstring(req, "f_marital_status"),
335 person_marital_status.get(self.marital_status_code, INVALID_VALUE),
336 self.wxstring(req, "f_interpreter_required"),
337 get_yes_no_none(req, self.interpreter_required),
338 )
339 h += self.four_column_row(
340 self.wxstring(req, "f_ethnic_category"),
341 ethnic_category_code.get(self.ethnic_category_code, INVALID_VALUE),
342 self.wxstring(req, "f_sensory_impairment"),
343 get_yes_no_none(req, self.sensory_impairment),
344 )
345 h += subheading_spanning_four_columns(
346 self.wxstring(req, "t_admission_reason")
347 )
348 h += tr_span_col(answer(", ".join(admission_reasons), ""), cols=4)
349 h += subheading_spanning_four_columns(
350 self.wxstring(req, "t_other_people")
351 )
352 h += self.tr_qa(
353 self.wxstring(req, "f_existing_psychiatric_teams"),
354 self.existing_psychiatric_teams,
355 "",
356 )
357 h += self.tr_qa(
358 self.wxstring(req, "f_care_coordinator"), self.care_coordinator, ""
359 )
360 h += self.tr_qa(
361 self.wxstring(req, "f_other_contact_details"),
362 self.other_contact_details,
363 "",
364 )
365 h += subheading_spanning_four_columns(
366 self.wxstring(req, "t_referral_reason")
367 )
368 h += tr_span_col(answer(self.referral_reason, ""), cols=4)
369 h += """
370 </table>
371 """
372 return h
375# =============================================================================
376# CPFT_LPS_ResetResponseClock
377# =============================================================================
380class CPFTLPSResetResponseClock(
381 TaskHasPatientMixin, TaskHasClinicianMixin, Task
382):
383 """
384 Server implementation of the CPFT_LPS_ResetResponseClock task.
385 """
387 __tablename__ = "cpft_lps_resetresponseclock"
388 shortname = "CPFT_LPS_ResetResponseClock"
389 info_filename_stem = "clinical"
391 reset_start_time_to = Column(
392 "reset_start_time_to", PendulumDateTimeAsIsoTextColType
393 )
394 reason = Column("reason", UnicodeText)
396 @staticmethod
397 def longname(req: "CamcopsRequest") -> str:
398 _ = req.gettext
399 return _("CPFT LPS – reset response clock")
401 def is_complete(self) -> bool:
402 return bool(
403 self.reset_start_time_to
404 and self.reason
405 and self.field_contents_valid()
406 )
408 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
409 return [CtvInfo(content=self.reason)]
411 def get_task_html(self, req: CamcopsRequest) -> str:
412 h = f"""
413 <div class="{CssClass.SUMMARY}">
414 <table class="{CssClass.SUMMARY}">
415 {self.get_is_complete_tr(req)}
416 </table>
417 </div>
418 <table class="{CssClass.TASKDETAIL}">
419 <col width="25%">
420 <col width="75%">
421 """
422 h += tr_qa(
423 self.wxstring(req, "to"),
424 format_datetime(
425 self.reset_start_time_to,
426 DateFormat.LONG_DATETIME_WITH_DAY,
427 default=None,
428 ),
429 )
430 h += tr_qa(self.wxstring(req, "reason"), self.reason)
431 h += """
432 </table>
433 """
434 return h
437# =============================================================================
438# CPFT_LPS_Discharge
439# =============================================================================
442class CPFTLPSDischarge(TaskHasPatientMixin, TaskHasClinicianMixin, Task):
443 """
444 Server implementation of the CPFT_LPS_Discharge task.
445 """
447 __tablename__ = "cpft_lps_discharge"
448 shortname = "CPFT_LPS_Discharge"
449 info_filename_stem = "clinical"
451 discharge_date = Column("discharge_date", Date)
452 discharge_reason_code = CamcopsColumn(
453 "discharge_reason_code", UnicodeText, exempt_from_anonymisation=True
454 )
456 leaflet_or_discharge_card_given = BoolColumn(
457 "leaflet_or_discharge_card_given",
458 constraint_name="ck_cpft_lps_discharge_lodcg",
459 )
460 frequent_attender = BoolColumn("frequent_attender")
461 patient_wanted_copy_of_letter = BoolColumn(
462 # Was previously text! That wasn't right.
463 "patient_wanted_copy_of_letter"
464 )
465 gaf_at_first_assessment = CamcopsColumn(
466 "gaf_at_first_assessment",
467 Integer,
468 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=100),
469 )
470 gaf_at_discharge = CamcopsColumn(
471 "gaf_at_discharge",
472 Integer,
473 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=100),
474 )
476 referral_reason_self_harm_overdose = BoolColumn(
477 "referral_reason_self_harm_overdose",
478 constraint_name="ck_cpft_lps_discharge_rrshoverdose",
479 )
480 referral_reason_self_harm_other = BoolColumn(
481 "referral_reason_self_harm_other",
482 constraint_name="ck_cpft_lps_discharge_rrshother",
483 )
484 referral_reason_suicidal_ideas = BoolColumn(
485 "referral_reason_suicidal_ideas",
486 constraint_name="ck_cpft_lps_discharge_rrsuicidal",
487 )
488 referral_reason_behavioural_disturbance = BoolColumn(
489 "referral_reason_behavioural_disturbance",
490 constraint_name="ck_cpft_lps_discharge_behavdisturb",
491 )
492 referral_reason_low_mood = BoolColumn("referral_reason_low_mood")
493 referral_reason_elevated_mood = BoolColumn("referral_reason_elevated_mood")
494 referral_reason_psychosis = BoolColumn("referral_reason_psychosis")
495 referral_reason_pre_transplant = BoolColumn(
496 "referral_reason_pre_transplant",
497 constraint_name="ck_cpft_lps_discharge_pretransplant",
498 )
499 referral_reason_post_transplant = BoolColumn(
500 "referral_reason_post_transplant",
501 constraint_name="ck_cpft_lps_discharge_posttransplant",
502 )
503 referral_reason_delirium = BoolColumn("referral_reason_delirium")
504 referral_reason_anxiety = BoolColumn("referral_reason_anxiety")
505 referral_reason_somatoform_mus = BoolColumn(
506 "referral_reason_somatoform_mus",
507 constraint_name="ck_cpft_lps_discharge_mus",
508 )
509 referral_reason_motivation_adherence = BoolColumn(
510 "referral_reason_motivation_adherence",
511 constraint_name="ck_cpft_lps_discharge_motivadherence",
512 )
513 referral_reason_capacity = BoolColumn("referral_reason_capacity")
514 referral_reason_eating_disorder = BoolColumn(
515 "referral_reason_eating_disorder",
516 constraint_name="ck_cpft_lps_discharge_eatingdis",
517 )
518 referral_reason_safeguarding = BoolColumn("referral_reason_safeguarding")
519 referral_reason_discharge_placement = BoolColumn(
520 "referral_reason_discharge_placement",
521 constraint_name="ck_cpft_lps_discharge_dcplacement",
522 )
523 referral_reason_cognitive_problem = BoolColumn(
524 "referral_reason_cognitive_problem",
525 constraint_name="ck_cpft_lps_discharge_cognitiveprob",
526 )
527 referral_reason_substance_alcohol = BoolColumn(
528 "referral_reason_substance_alcohol",
529 constraint_name="ck_cpft_lps_discharge_alcohol",
530 )
531 referral_reason_substance_other = BoolColumn(
532 "referral_reason_substance_other",
533 constraint_name="ck_cpft_lps_discharge_substanceother",
534 )
535 referral_reason_other = BoolColumn("referral_reason_other")
536 referral_reason_transplant_organ = CamcopsColumn(
537 "referral_reason_transplant_organ",
538 UnicodeText,
539 exempt_from_anonymisation=True,
540 )
541 referral_reason_other_detail = Column(
542 "referral_reason_other_detail", UnicodeText
543 )
545 diagnosis_no_active_mental_health_problem = BoolColumn(
546 "diagnosis_no_active_mental_health_problem",
547 constraint_name="ck_cpft_lps_discharge_nomhprob",
548 )
549 diagnosis_psych_1_icd10code = Column(
550 "diagnosis_psych_1_icd10code", DiagnosticCodeColType
551 )
552 diagnosis_psych_1_description = CamcopsColumn(
553 "diagnosis_psych_1_description",
554 UnicodeText,
555 exempt_from_anonymisation=True,
556 )
557 diagnosis_psych_2_icd10code = Column(
558 "diagnosis_psych_2_icd10code", DiagnosticCodeColType
559 )
560 diagnosis_psych_2_description = CamcopsColumn(
561 "diagnosis_psych_2_description",
562 UnicodeText,
563 exempt_from_anonymisation=True,
564 )
565 diagnosis_psych_3_icd10code = Column(
566 "diagnosis_psych_3_icd10code", DiagnosticCodeColType
567 )
568 diagnosis_psych_3_description = CamcopsColumn(
569 "diagnosis_psych_3_description",
570 UnicodeText,
571 exempt_from_anonymisation=True,
572 )
573 diagnosis_psych_4_icd10code = Column(
574 "diagnosis_psych_4_icd10code", DiagnosticCodeColType
575 )
576 diagnosis_psych_4_description = CamcopsColumn(
577 "diagnosis_psych_4_description",
578 UnicodeText,
579 exempt_from_anonymisation=True,
580 )
581 diagnosis_medical_1 = Column("diagnosis_medical_1", UnicodeText)
582 diagnosis_medical_2 = Column("diagnosis_medical_2", UnicodeText)
583 diagnosis_medical_3 = Column("diagnosis_medical_3", UnicodeText)
584 diagnosis_medical_4 = Column("diagnosis_medical_4", UnicodeText)
586 management_assessment_diagnostic = BoolColumn(
587 "management_assessment_diagnostic",
588 constraint_name="ck_cpft_lps_discharge_mx_ass_diag",
589 )
590 management_medication = BoolColumn("management_medication")
591 management_specialling_behavioural_disturbance = BoolColumn(
592 "management_specialling_behavioural_disturbance",
593 # Constraint name too long for MySQL unless we do this:
594 constraint_name="ck_cpft_lps_discharge_msbd",
595 )
596 management_supportive_patient = BoolColumn("management_supportive_patient")
597 management_supportive_carers = BoolColumn("management_supportive_carers")
598 management_supportive_staff = BoolColumn("management_supportive_staff")
599 management_nursing_management = BoolColumn("management_nursing_management")
600 management_therapy_cbt = BoolColumn("management_therapy_cbt")
601 management_therapy_cat = BoolColumn("management_therapy_cat")
602 management_therapy_other = BoolColumn("management_therapy_other")
603 management_treatment_adherence = BoolColumn(
604 "management_treatment_adherence",
605 constraint_name="ck_cpft_lps_discharge_mx_rx_adhere",
606 )
607 management_capacity = BoolColumn("management_capacity")
608 management_education_patient = BoolColumn("management_education_patient")
609 management_education_carers = BoolColumn("management_education_carers")
610 management_education_staff = BoolColumn("management_education_staff")
611 management_accommodation_placement = BoolColumn(
612 "management_accommodation_placement",
613 constraint_name="ck_cpft_lps_discharge_accom",
614 )
615 management_signposting_external_referral = BoolColumn(
616 "management_signposting_external_referral",
617 constraint_name="ck_cpft_lps_discharge_mx_signpostrefer",
618 )
619 management_mha_s136 = BoolColumn("management_mha_s136")
620 management_mha_s5_2 = BoolColumn("management_mha_s5_2")
621 management_mha_s2 = BoolColumn("management_mha_s2")
622 management_mha_s3 = BoolColumn("management_mha_s3")
623 management_complex_case_conference = BoolColumn(
624 "management_complex_case_conference",
625 constraint_name="ck_cpft_lps_discharge_caseconf",
626 )
627 management_other = BoolColumn("management_other")
628 management_other_detail = Column("management_other_detail", UnicodeText)
630 outcome = CamcopsColumn(
631 "outcome", UnicodeText, exempt_from_anonymisation=True
632 )
633 outcome_hospital_transfer_detail = Column(
634 "outcome_hospital_transfer_detail", UnicodeText
635 )
636 outcome_other_detail = Column("outcome_other_detail", UnicodeText)
638 @staticmethod
639 def longname(req: "CamcopsRequest") -> str:
640 _ = req.gettext
641 return _("CPFT LPS – discharge")
643 def is_complete(self) -> bool:
644 return bool(
645 self.discharge_date
646 and self.discharge_reason_code
647 and
648 # self.outcome and # v2.0.0
649 self.field_contents_valid()
650 )
652 def get_discharge_reason(self, req: CamcopsRequest) -> Optional[str]:
653 if self.discharge_reason_code == "F":
654 return self.wxstring(req, "reason_code_F")
655 elif self.discharge_reason_code == "A":
656 return self.wxstring(req, "reason_code_A")
657 elif self.discharge_reason_code == "O":
658 return self.wxstring(req, "reason_code_O")
659 elif self.discharge_reason_code == "C":
660 return self.wxstring(req, "reason_code_C")
661 else:
662 return None
664 def get_referral_reasons(self, req: CamcopsRequest) -> List[str]:
665 potential_referral_reasons = [
666 "referral_reason_self_harm_overdose",
667 "referral_reason_self_harm_other",
668 "referral_reason_suicidal_ideas",
669 "referral_reason_behavioural_disturbance",
670 "referral_reason_low_mood",
671 "referral_reason_elevated_mood",
672 "referral_reason_psychosis",
673 "referral_reason_pre_transplant",
674 "referral_reason_post_transplant",
675 "referral_reason_delirium",
676 "referral_reason_anxiety",
677 "referral_reason_somatoform_mus",
678 "referral_reason_motivation_adherence",
679 "referral_reason_capacity",
680 "referral_reason_eating_disorder",
681 "referral_reason_safeguarding",
682 "referral_reason_discharge_placement",
683 "referral_reason_cognitive_problem",
684 "referral_reason_substance_alcohol",
685 "referral_reason_substance_other",
686 "referral_reason_other",
687 ]
688 referral_reasons = []
689 for r in potential_referral_reasons:
690 if getattr(self, r):
691 referral_reasons.append(self.wxstring(req, "" + r))
692 return referral_reasons
694 def get_managements(self, req: CamcopsRequest) -> List[str]:
695 potential_managements = [
696 "management_assessment_diagnostic",
697 "management_medication",
698 "management_specialling_behavioural_disturbance",
699 "management_supportive_patient",
700 "management_supportive_carers",
701 "management_supportive_staff",
702 "management_nursing_management",
703 "management_therapy_cbt",
704 "management_therapy_cat",
705 "management_therapy_other",
706 "management_treatment_adherence",
707 "management_capacity",
708 "management_education_patient",
709 "management_education_carers",
710 "management_education_staff",
711 "management_accommodation_placement",
712 "management_signposting_external_referral",
713 "management_mha_s136",
714 "management_mha_s5_2",
715 "management_mha_s2",
716 "management_mha_s3",
717 "management_complex_case_conference",
718 "management_other",
719 ]
720 managements = []
721 for r in potential_managements:
722 if getattr(self, r):
723 managements.append(self.wxstring(req, "" + r))
724 return managements
726 def get_psychiatric_diagnoses(self, req: CamcopsRequest) -> List[str]:
727 psychiatric_diagnoses = (
728 [self.wxstring(req, "diagnosis_no_active_mental_health_problem")]
729 if self.diagnosis_no_active_mental_health_problem
730 else []
731 )
732 for i in range(1, 4 + 1): # magic number
733 if getattr(self, "diagnosis_psych_" + str(i) + "_icd10code"):
734 psychiatric_diagnoses.append(
735 ws.webify(
736 getattr(
737 self, "diagnosis_psych_" + str(i) + "_icd10code"
738 )
739 )
740 + " – "
741 + ws.webify(
742 getattr(
743 self, "diagnosis_psych_" + str(i) + "_description"
744 )
745 )
746 )
747 return psychiatric_diagnoses
749 def get_medical_diagnoses(self) -> List[str]:
750 medical_diagnoses = []
751 for i in range(1, 4 + 1): # magic number
752 if getattr(self, "diagnosis_medical_" + str(i)):
753 medical_diagnoses.append(
754 ws.webify(getattr(self, "diagnosis_medical_" + str(i)))
755 )
756 return medical_diagnoses
758 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
759 diagnoses = (
760 self.get_psychiatric_diagnoses(req) + self.get_medical_diagnoses()
761 )
762 return [
763 CtvInfo(
764 heading=ws.webify(self.wxstring(req, "discharge_reason")),
765 content=self.get_discharge_reason(req),
766 ),
767 CtvInfo(
768 heading=ws.webify(self.wxstring(req, "referral_reason_t")),
769 content=", ".join(self.get_referral_reasons(req)),
770 ),
771 CtvInfo(
772 heading=ws.webify(self.wxstring(req, "diagnoses_t")),
773 content=", ".join(diagnoses),
774 ),
775 CtvInfo(
776 heading=ws.webify(self.wxstring(req, "management_t")),
777 content=", ".join(self.get_managements(req)),
778 ),
779 CtvInfo(
780 heading=ws.webify(self.wxstring(req, "outcome_t")),
781 content=self.outcome,
782 ),
783 ]
785 def get_task_html(self, req: CamcopsRequest) -> str:
786 h = f"""
787 <div class="{CssClass.SUMMARY}">
788 <table class="{CssClass.SUMMARY}">
789 {self.get_is_complete_tr(req)}
790 </table>
791 </div>
792 <table class="{CssClass.TASKDETAIL}">
793 <col width="40%">
794 <col width="60%">
795 """
796 h += tr_qa(
797 self.wxstring(req, "discharge_date"),
798 format_datetime(
799 self.discharge_date,
800 DateFormat.LONG_DATE_WITH_DAY,
801 default=None,
802 ),
803 "",
804 )
805 h += tr_qa(
806 self.wxstring(req, "discharge_reason"),
807 self.get_discharge_reason(req),
808 "",
809 )
810 h += tr_qa(
811 self.wxstring(req, "leaflet_or_discharge_card_given"),
812 get_yes_no_none(req, self.leaflet_or_discharge_card_given),
813 "",
814 )
815 h += tr_qa(
816 self.wxstring(req, "frequent_attender"),
817 get_yes_no_none(req, self.frequent_attender),
818 "",
819 )
820 h += tr_qa(
821 self.wxstring(req, "patient_wanted_copy_of_letter"),
822 self.patient_wanted_copy_of_letter,
823 "",
824 )
825 h += tr_qa(
826 self.wxstring(req, "gaf_at_first_assessment"),
827 self.gaf_at_first_assessment,
828 "",
829 )
830 h += tr_qa(
831 self.wxstring(req, "gaf_at_discharge"), self.gaf_at_discharge, ""
832 )
834 h += subheading_spanning_two_columns(
835 self.wxstring(req, "referral_reason_t")
836 )
837 h += tr_span_col(
838 answer(", ".join(self.get_referral_reasons(req))), cols=2
839 )
840 h += tr_qa(
841 self.wxstring(req, "referral_reason_transplant_organ"),
842 self.referral_reason_transplant_organ,
843 "",
844 )
845 h += tr_qa(
846 self.wxstring(req, "referral_reason_other_detail"),
847 self.referral_reason_other_detail,
848 "",
849 )
851 h += subheading_spanning_two_columns(self.wxstring(req, "diagnoses_t"))
852 h += tr_qa(
853 self.wxstring(req, "psychiatric_t"),
854 "\n".join(self.get_psychiatric_diagnoses(req)),
855 "",
856 )
857 h += tr_qa(
858 self.wxstring(req, "medical_t"),
859 "\n".join(self.get_medical_diagnoses()),
860 "",
861 )
863 h += subheading_spanning_two_columns(
864 self.wxstring(req, "management_t")
865 )
866 h += tr_span_col(answer(", ".join(self.get_managements(req))), cols=2)
867 h += tr_qa(
868 self.wxstring(req, "management_other_detail"),
869 self.management_other_detail,
870 "",
871 )
873 h += subheading_spanning_two_columns(self.wxstring(req, "outcome_t"))
874 h += tr_qa(self.wxstring(req, "outcome_t"), self.outcome, "")
875 h += tr_qa(
876 self.wxstring(req, "outcome_hospital_transfer_detail"),
877 self.outcome_hospital_transfer_detail,
878 "",
879 )
880 h += tr_qa(
881 self.wxstring(req, "outcome_other_detail"),
882 self.outcome_other_detail,
883 "",
884 )
886 h += """
887 </table>
888 """
889 return h
892# =============================================================================
893# Reports
894# =============================================================================
897class LPSReportSchema(ReportParamSchema):
898 which_idnum = LinkingIdNumSelector() # must match ViewParam.WHICH_IDNUM
901class LPSReportReferredNotDischarged(Report):
902 # noinspection PyMethodParameters
903 @classproperty
904 def report_id(cls) -> str:
905 return "cpft_lps_referred_not_subsequently_discharged"
907 @classmethod
908 def title(cls, req: "CamcopsRequest") -> str:
909 _ = req.gettext
910 return _("CPFT LPS – referred but not yet discharged")
912 # noinspection PyMethodParameters
913 @classproperty
914 def superuser_only(cls) -> bool:
915 return False
917 @staticmethod
918 def get_paramform_schema_class() -> Type[ReportParamSchema]:
919 return LPSReportSchema
921 # noinspection PyProtectedMember,PyUnresolvedReferences
922 def get_query(self, req: CamcopsRequest) -> SelectBase:
923 which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1)
924 if which_idnum is None:
925 raise exc.HTTPBadRequest(f"{ViewParam.WHICH_IDNUM} not specified")
927 group_ids = req.user.ids_of_groups_user_may_report_on
929 # Step 1: link referral and patient
930 p1 = Patient.__table__.alias("p1")
931 i1 = PatientIdNum.__table__.alias("i1")
932 desc = req.get_id_shortdesc(which_idnum)
933 select_fields = [
934 CPFTLPSReferral.lps_division,
935 CPFTLPSReferral.referral_date_time,
936 CPFTLPSReferral.referral_priority,
937 p1.c.surname,
938 p1.c.forename,
939 p1.c.dob,
940 i1.c.idnum_value.label(desc),
941 CPFTLPSReferral.patient_location,
942 ]
943 select_from = p1.join(
944 CPFTLPSReferral.__table__,
945 and_(
946 p1.c._current == True, # noqa: E712
947 CPFTLPSReferral.patient_id == p1.c.id,
948 CPFTLPSReferral._device_id == p1.c._device_id,
949 CPFTLPSReferral._era == p1.c._era,
950 CPFTLPSReferral._current == True, # noqa: E712
951 ),
952 )
953 select_from = select_from.join(
954 i1,
955 and_(
956 i1.c.patient_id == p1.c.id,
957 i1.c._device_id == p1.c._device_id,
958 i1.c._era == p1.c._era,
959 i1.c._current == True, # noqa: E712
960 ),
961 )
962 wheres = [i1.c.which_idnum == which_idnum]
963 if not req.user.superuser:
964 # Restrict to accessible groups
965 wheres.append(CPFTLPSReferral._group_id.in_(group_ids))
967 # Step 2: not yet discharged
968 p2 = Patient.__table__.alias("p2")
969 i2 = PatientIdNum.__table__.alias("i2")
970 discharge = (
971 select(["*"])
972 .select_from(
973 p2.join(
974 CPFTLPSDischarge.__table__,
975 and_(
976 p2.c._current == True, # noqa: E712
977 CPFTLPSDischarge.patient_id == p2.c.id,
978 CPFTLPSDischarge._device_id == p2.c._device_id,
979 CPFTLPSDischarge._era == p2.c._era,
980 CPFTLPSDischarge._current == True, # noqa: E712
981 ),
982 ).join(
983 i2,
984 and_(
985 i2.c.patient_id == p2.c.id,
986 i2.c._device_id == p2.c._device_id,
987 i2.c._era == p2.c._era,
988 i2.c._current == True, # noqa: E712
989 ),
990 )
991 )
992 .where(
993 and_(
994 # Link on ID to main query: same patient
995 i2.c.which_idnum == which_idnum,
996 i2.c.idnum_value == i1.c.idnum_value,
997 # Discharge later than referral
998 (
999 CPFTLPSDischarge.discharge_date
1000 >= CPFTLPSReferral.referral_date_time
1001 ),
1002 )
1003 )
1004 ) # nopep8
1005 if not req.user.superuser:
1006 # Restrict to accessible groups
1007 discharge = discharge.where(
1008 CPFTLPSDischarge._group_id.in_(group_ids)
1009 )
1011 wheres.append(~exists(discharge))
1013 # Finish up
1014 order_by = [
1015 CPFTLPSReferral.lps_division,
1016 CPFTLPSReferral.referral_date_time,
1017 CPFTLPSReferral.referral_priority,
1018 ]
1019 query = (
1020 select(select_fields)
1021 .select_from(select_from)
1022 .where(and_(*wheres))
1023 .order_by(*order_by)
1024 )
1025 return query
1028class LPSReportReferredNotClerkedOrDischarged(Report):
1029 # noinspection PyMethodParameters
1030 @classproperty
1031 def report_id(cls) -> str:
1032 return "cpft_lps_referred_not_subsequently_clerked_or_discharged"
1034 @classmethod
1035 def title(cls, req: "CamcopsRequest") -> str:
1036 _ = req.gettext
1037 return _(
1038 "CPFT LPS – referred but not yet fully assessed or discharged"
1039 )
1041 # noinspection PyMethodParameters
1042 @classproperty
1043 def superuser_only(cls) -> bool:
1044 return False
1046 @staticmethod
1047 def get_paramform_schema_class() -> Type[ReportParamSchema]:
1048 return LPSReportSchema
1050 # noinspection PyProtectedMember
1051 def get_query(self, req: CamcopsRequest) -> SelectBase:
1052 which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1)
1053 if which_idnum is None:
1054 raise exc.HTTPBadRequest(f"{ViewParam.WHICH_IDNUM} not specified")
1056 group_ids = req.user.ids_of_groups_user_may_report_on
1058 # Step 1: link referral and patient
1059 # noinspection PyUnresolvedReferences
1060 p1 = Patient.__table__.alias("p1")
1061 # noinspection PyUnresolvedReferences
1062 i1 = PatientIdNum.__table__.alias("i1")
1063 desc = req.get_id_shortdesc(which_idnum)
1064 select_fields = [
1065 CPFTLPSReferral.lps_division,
1066 CPFTLPSReferral.referral_date_time,
1067 CPFTLPSReferral.referral_priority,
1068 p1.c.surname,
1069 p1.c.forename,
1070 p1.c.dob,
1071 i1.c.idnum_value.label(desc),
1072 CPFTLPSReferral.patient_location,
1073 ]
1074 # noinspection PyUnresolvedReferences
1075 select_from = p1.join(
1076 CPFTLPSReferral.__table__,
1077 and_(
1078 p1.c._current == True, # noqa: E712
1079 CPFTLPSReferral.patient_id == p1.c.id,
1080 CPFTLPSReferral._device_id == p1.c._device_id,
1081 CPFTLPSReferral._era == p1.c._era,
1082 CPFTLPSReferral._current == True, # noqa: E712
1083 ),
1084 )
1085 select_from = select_from.join(
1086 i1,
1087 and_(
1088 i1.c.patient_id == p1.c.id,
1089 i1.c._device_id == p1.c._device_id,
1090 i1.c._era == p1.c._era,
1091 i1.c._current == True, # noqa: E712
1092 ),
1093 ) # nopep8
1094 wheres = [i1.c.which_idnum == which_idnum]
1095 if not req.user.superuser:
1096 # Restrict to accessible groups
1097 wheres.append(CPFTLPSReferral._group_id.in_(group_ids))
1099 # Step 2: not yet discharged
1100 # noinspection PyUnresolvedReferences
1101 p2 = Patient.__table__.alias("p2")
1102 # noinspection PyUnresolvedReferences
1103 i2 = PatientIdNum.__table__.alias("i2")
1104 # noinspection PyUnresolvedReferences
1105 discharge = (
1106 select(["*"])
1107 .select_from(
1108 p2.join(
1109 CPFTLPSDischarge.__table__,
1110 and_(
1111 p2.c._current == True, # noqa: E712
1112 CPFTLPSDischarge.patient_id == p2.c.id,
1113 CPFTLPSDischarge._device_id == p2.c._device_id,
1114 CPFTLPSDischarge._era == p2.c._era,
1115 CPFTLPSDischarge._current == True, # noqa: E712
1116 ),
1117 ).join(
1118 i2,
1119 and_(
1120 i2.c.patient_id == p2.c.id,
1121 i2.c._device_id == p2.c._device_id,
1122 i2.c._era == p2.c._era,
1123 i2.c._current == True, # noqa: E712
1124 ),
1125 )
1126 )
1127 .where(
1128 and_(
1129 # Link on ID to main query: same patient
1130 i2.c.which_idnum == which_idnum,
1131 i2.c.idnum_value == i1.c.idnum_value,
1132 # Discharge later than referral
1133 (
1134 CPFTLPSDischarge.discharge_date
1135 >= CPFTLPSReferral.referral_date_time
1136 ),
1137 )
1138 )
1139 ) # nopep8
1140 if not req.user.superuser:
1141 # Restrict to accessible groups
1142 discharge = discharge.where(
1143 CPFTLPSDischarge._group_id.in_(group_ids)
1144 )
1145 wheres.append(~exists(discharge))
1147 # Step 3: not yet clerked
1148 # noinspection PyUnresolvedReferences
1149 p3 = Patient.__table__.alias("p3")
1150 # noinspection PyUnresolvedReferences
1151 i3 = PatientIdNum.__table__.alias("i3")
1152 # noinspection PyUnresolvedReferences
1153 clerking = (
1154 select(["*"])
1155 .select_from(
1156 p3.join(
1157 PsychiatricClerking.__table__,
1158 and_(
1159 p3.c._current == True, # noqa: E712
1160 PsychiatricClerking.patient_id == p3.c.id,
1161 PsychiatricClerking._device_id == p3.c._device_id,
1162 PsychiatricClerking._era == p3.c._era,
1163 PsychiatricClerking._current == True, # noqa: E712
1164 ),
1165 ).join(
1166 i3,
1167 and_(
1168 i3.c.patient_id == p3.c.id,
1169 i3.c._device_id == p3.c._device_id,
1170 i3.c._era == p3.c._era,
1171 i3.c._current == True, # noqa: E712
1172 ),
1173 )
1174 )
1175 .where(
1176 and_(
1177 # Link on ID to main query: same patient
1178 i3.c.which_idnum == which_idnum,
1179 i3.c.idnum_value == i1.c.idnum_value,
1180 # Discharge later than referral
1181 (
1182 PsychiatricClerking.when_created
1183 >= CPFTLPSReferral.referral_date_time
1184 ),
1185 )
1186 )
1187 ) # nopep8
1188 if not req.user.superuser:
1189 # Restrict to accessible groups
1190 clerking = clerking.where(
1191 PsychiatricClerking._group_id.in_(group_ids)
1192 )
1193 wheres.append(~exists(clerking))
1195 # Finish up
1196 order_by = [
1197 CPFTLPSReferral.lps_division,
1198 CPFTLPSReferral.referral_date_time,
1199 CPFTLPSReferral.referral_priority,
1200 ]
1201 query = (
1202 select(select_fields)
1203 .select_from(select_from)
1204 .where(and_(*wheres))
1205 .order_by(*order_by)
1206 )
1207 return query