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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/cpft_lps.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 

30import logging 

31from typing import Any, List, Optional, Type 

32 

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 

42 

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 

86 

87log = BraceStyleAdapter(logging.getLogger(__name__)) 

88 

89 

90# ============================================================================= 

91# CPFT_LPS_Referral 

92# ============================================================================= 

93 

94 

95class CPFTLPSReferral(TaskHasPatientMixin, Task): 

96 """ 

97 Server implementation of the CPFT_LPS_Referral task. 

98 """ 

99 

100 __tablename__ = "cpft_lps_referral" 

101 shortname = "CPFT_LPS_Referral" 

102 info_filename_stem = "clinical" 

103 

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) 

165 

166 @staticmethod 

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

168 _ = req.gettext 

169 return _("CPFT LPS – referral") 

170 

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 ) 

177 

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 ] 

185 

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 """ 

196 

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 """ 

205 

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 

221 

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 

230 

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)) 

245 

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 

373 

374 

375# ============================================================================= 

376# CPFT_LPS_ResetResponseClock 

377# ============================================================================= 

378 

379 

380class CPFTLPSResetResponseClock( 

381 TaskHasPatientMixin, TaskHasClinicianMixin, Task 

382): 

383 """ 

384 Server implementation of the CPFT_LPS_ResetResponseClock task. 

385 """ 

386 

387 __tablename__ = "cpft_lps_resetresponseclock" 

388 shortname = "CPFT_LPS_ResetResponseClock" 

389 info_filename_stem = "clinical" 

390 

391 reset_start_time_to = Column( 

392 "reset_start_time_to", PendulumDateTimeAsIsoTextColType 

393 ) 

394 reason = Column("reason", UnicodeText) 

395 

396 @staticmethod 

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

398 _ = req.gettext 

399 return _("CPFT LPS – reset response clock") 

400 

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 ) 

407 

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

409 return [CtvInfo(content=self.reason)] 

410 

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 

435 

436 

437# ============================================================================= 

438# CPFT_LPS_Discharge 

439# ============================================================================= 

440 

441 

442class CPFTLPSDischarge(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

443 """ 

444 Server implementation of the CPFT_LPS_Discharge task. 

445 """ 

446 

447 __tablename__ = "cpft_lps_discharge" 

448 shortname = "CPFT_LPS_Discharge" 

449 info_filename_stem = "clinical" 

450 

451 discharge_date = Column("discharge_date", Date) 

452 discharge_reason_code = CamcopsColumn( 

453 "discharge_reason_code", UnicodeText, exempt_from_anonymisation=True 

454 ) 

455 

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 ) 

475 

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 ) 

544 

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) 

585 

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) 

629 

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) 

637 

638 @staticmethod 

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

640 _ = req.gettext 

641 return _("CPFT LPS – discharge") 

642 

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 ) 

651 

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 

663 

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 

693 

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 

725 

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 

748 

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 

757 

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 ] 

784 

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 ) 

833 

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 ) 

850 

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 ) 

862 

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 ) 

872 

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 ) 

885 

886 h += """ 

887 </table> 

888 """ 

889 return h 

890 

891 

892# ============================================================================= 

893# Reports 

894# ============================================================================= 

895 

896 

897class LPSReportSchema(ReportParamSchema): 

898 which_idnum = LinkingIdNumSelector() # must match ViewParam.WHICH_IDNUM 

899 

900 

901class LPSReportReferredNotDischarged(Report): 

902 # noinspection PyMethodParameters 

903 @classproperty 

904 def report_id(cls) -> str: 

905 return "cpft_lps_referred_not_subsequently_discharged" 

906 

907 @classmethod 

908 def title(cls, req: "CamcopsRequest") -> str: 

909 _ = req.gettext 

910 return _("CPFT LPS – referred but not yet discharged") 

911 

912 # noinspection PyMethodParameters 

913 @classproperty 

914 def superuser_only(cls) -> bool: 

915 return False 

916 

917 @staticmethod 

918 def get_paramform_schema_class() -> Type[ReportParamSchema]: 

919 return LPSReportSchema 

920 

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") 

926 

927 group_ids = req.user.ids_of_groups_user_may_report_on 

928 

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)) 

966 

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 ) 

1010 

1011 wheres.append(~exists(discharge)) 

1012 

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 

1026 

1027 

1028class LPSReportReferredNotClerkedOrDischarged(Report): 

1029 # noinspection PyMethodParameters 

1030 @classproperty 

1031 def report_id(cls) -> str: 

1032 return "cpft_lps_referred_not_subsequently_clerked_or_discharged" 

1033 

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 ) 

1040 

1041 # noinspection PyMethodParameters 

1042 @classproperty 

1043 def superuser_only(cls) -> bool: 

1044 return False 

1045 

1046 @staticmethod 

1047 def get_paramform_schema_class() -> Type[ReportParamSchema]: 

1048 return LPSReportSchema 

1049 

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") 

1055 

1056 group_ids = req.user.ids_of_groups_user_may_report_on 

1057 

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)) 

1098 

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)) 

1146 

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)) 

1194 

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