Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/cpft_lps.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29import logging 

30from typing import Any, List, Optional, Type 

31 

32from cardinal_pythonlib.classes import classproperty 

33from cardinal_pythonlib.datetimefunc import format_datetime 

34from cardinal_pythonlib.logs import BraceStyleAdapter 

35import cardinal_pythonlib.rnc_web as ws 

36import pyramid.httpexceptions as exc 

37from sqlalchemy.sql.expression import and_, exists, select 

38from sqlalchemy.sql.selectable import SelectBase 

39from sqlalchemy.sql.schema import Column 

40from sqlalchemy.sql.sqltypes import Date, Integer, UnicodeText 

41 

42from camcops_server.cc_modules.cc_constants import ( 

43 CssClass, 

44 DateFormat, 

45 INVALID_VALUE, 

46) 

47from camcops_server.cc_modules.cc_ctvinfo import CtvInfo 

48from camcops_server.cc_modules.cc_forms import ( 

49 LinkingIdNumSelector, 

50 ReportParamSchema, 

51) 

52from camcops_server.cc_modules.cc_html import ( 

53 answer, 

54 get_yes_no_none, 

55 subheading_spanning_four_columns, 

56 subheading_spanning_two_columns, 

57 tr_qa, 

58 tr_span_col, 

59) 

60from camcops_server.cc_modules.cc_nhs import ( 

61 get_nhs_dd_ethnic_category_code, 

62 get_nhs_dd_person_marital_status, 

63 PV_NHS_ETHNIC_CATEGORY, 

64 PV_NHS_MARITAL_STATUS 

65) 

66from camcops_server.cc_modules.cc_patient import Patient 

67from camcops_server.cc_modules.cc_patientidnum import PatientIdNum 

68from camcops_server.cc_modules.cc_pyramid import ViewParam 

69from camcops_server.cc_modules.cc_report import Report 

70from camcops_server.cc_modules.cc_request import CamcopsRequest 

71from camcops_server.cc_modules.cc_sqla_coltypes import ( 

72 BoolColumn, 

73 CamcopsColumn, 

74 CharColType, 

75 PendulumDateTimeAsIsoTextColType, 

76 DiagnosticCodeColType, 

77 PermittedValueChecker, 

78) 

79from camcops_server.cc_modules.cc_task import ( 

80 Task, 

81 TaskHasClinicianMixin, 

82 TaskHasPatientMixin, 

83) 

84from camcops_server.tasks.psychiatricclerking import PsychiatricClerking 

85 

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

87 

88 

89# ============================================================================= 

90# CPFT_LPS_Referral 

91# ============================================================================= 

92 

93class CPFTLPSReferral(TaskHasPatientMixin, Task): 

94 """ 

95 Server implementation of the CPFT_LPS_Referral task. 

96 """ 

97 __tablename__ = "cpft_lps_referral" 

98 shortname = "CPFT_LPS_Referral" 

99 

100 referral_date_time = Column("referral_date_time", 

101 PendulumDateTimeAsIsoTextColType) 

102 lps_division = CamcopsColumn( 

103 "lps_division", UnicodeText, 

104 exempt_from_anonymisation=True) 

105 referral_priority = CamcopsColumn( 

106 "referral_priority", UnicodeText, 

107 exempt_from_anonymisation=True) 

108 referral_method = CamcopsColumn( 

109 "referral_method", UnicodeText, 

110 exempt_from_anonymisation=True) 

111 referrer_name = Column("referrer_name", UnicodeText) 

112 referrer_contact_details = Column("referrer_contact_details", UnicodeText) 

113 referring_consultant = Column("referring_consultant", UnicodeText) 

114 referring_specialty = CamcopsColumn( 

115 "referring_specialty", UnicodeText, 

116 exempt_from_anonymisation=True) 

117 referring_specialty_other = Column("referring_specialty_other", UnicodeText) 

118 patient_location = Column("patient_location", UnicodeText) 

119 admission_date = Column("admission_date", Date) 

120 estimated_discharge_date = Column("estimated_discharge_date", Date) 

121 patient_aware_of_referral = BoolColumn("patient_aware_of_referral") 

122 interpreter_required = BoolColumn("interpreter_required") 

123 sensory_impairment = BoolColumn("sensory_impairment") 

124 marital_status_code = CamcopsColumn( 

125 "marital_status_code", CharColType, 

126 permitted_value_checker=PermittedValueChecker( 

127 permitted_values=PV_NHS_MARITAL_STATUS) 

128 ) 

129 ethnic_category_code = CamcopsColumn( 

130 "ethnic_category_code", CharColType, 

131 permitted_value_checker=PermittedValueChecker( 

132 permitted_values=PV_NHS_ETHNIC_CATEGORY) 

133 ) 

134 admission_reason_overdose = BoolColumn("admission_reason_overdose") 

135 admission_reason_self_harm_not_overdose = BoolColumn( 

136 "admission_reason_self_harm_not_overdose", 

137 constraint_name="ck_cpft_lps_referral_arshno" 

138 ) 

139 admission_reason_confusion = BoolColumn("admission_reason_confusion") 

140 admission_reason_trauma = BoolColumn("admission_reason_trauma") 

141 admission_reason_falls = BoolColumn("admission_reason_falls") 

142 admission_reason_infection = BoolColumn("admission_reason_infection") 

143 admission_reason_poor_adherence = BoolColumn( 

144 "admission_reason_poor_adherence", 

145 constraint_name="ck_cpft_lps_referral_adpa" 

146 ) 

147 admission_reason_other = BoolColumn("admission_reason_other") 

148 existing_psychiatric_teams = Column("existing_psychiatric_teams", 

149 UnicodeText) 

150 care_coordinator = Column("care_coordinator", UnicodeText) 

151 other_contact_details = Column("other_contact_details", UnicodeText) 

152 referral_reason = Column("referral_reason", UnicodeText) 

153 

154 @staticmethod 

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

156 _ = req.gettext 

157 return _("CPFT LPS – referral") 

158 

159 def is_complete(self) -> bool: 

160 return bool( 

161 self.patient_location and 

162 self.referral_reason and 

163 self.field_contents_valid() 

164 ) 

165 

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

167 return [CtvInfo( 

168 heading=ws.webify(self.wxstring(req, "f_referral_reason_t")), 

169 content=self.referral_reason 

170 )] 

171 

172 @staticmethod 

173 def four_column_row(q1: str, a1: Any, 

174 q2: str, a2: Any, 

175 default: str = "") -> str: 

176 return f""" 

177 <tr> 

178 <td>{q1}</td><td>{answer(a1, default=default)}</td> 

179 <td>{q2}</td><td>{answer(a2, default=default)}</td> 

180 </tr> 

181 """ 

182 

183 @staticmethod 

184 def tr_qa(q: str, a: Any, default: str = "") -> str: 

185 return f""" 

186 <tr> 

187 <td colspan="2">{q}</td> 

188 <td colspan="2"><b>{default if a is None else a}</b></td> 

189 </tr> 

190 """ 

191 

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

193 person_marital_status = get_nhs_dd_person_marital_status(req) 

194 ethnic_category_code = get_nhs_dd_ethnic_category_code(req) 

195 if self.lps_division == "G": 

196 banner_class = CssClass.BANNER_REFERRAL_GENERAL_ADULT 

197 division_name = self.wxstring(req, "service_G") 

198 elif self.lps_division == "O": 

199 banner_class = CssClass.BANNER_REFERRAL_OLD_AGE 

200 division_name = self.wxstring(req, "service_O") 

201 elif self.lps_division == "S": 

202 banner_class = CssClass.BANNER_REFERRAL_SUBSTANCE_MISUSE 

203 division_name = self.wxstring(req, "service_S") 

204 else: 

205 banner_class = "" 

206 division_name = None 

207 

208 if self.referral_priority == "R": 

209 priority_name = self.wxstring(req, "priority_R") 

210 elif self.referral_priority == "U": 

211 priority_name = self.wxstring(req, "priority_U") 

212 elif self.referral_priority == "E": 

213 priority_name = self.wxstring(req, "priority_E") 

214 else: 

215 priority_name = None 

216 

217 potential_admission_reasons = [ 

218 "admission_reason_overdose", 

219 "admission_reason_self_harm_not_overdose", 

220 "admission_reason_confusion", 

221 "admission_reason_trauma", 

222 "admission_reason_falls", 

223 "admission_reason_infection", 

224 "admission_reason_poor_adherence", 

225 "admission_reason_other", 

226 ] 

227 admission_reasons = [] 

228 for r in potential_admission_reasons: 

229 if getattr(self, r): 

230 admission_reasons.append(self.wxstring(req, "f_" + r)) 

231 

232 h = f""" 

233 <div class="{CssClass.BANNER} {banner_class}"> 

234 {answer(division_name, default_for_blank_strings=True)} 

235 referral at { 

236 answer(format_datetime( 

237 self.referral_date_time, 

238 DateFormat.SHORT_DATETIME_WITH_DAY_NO_TZ, 

239 default=None))} 

240 </div> 

241 <div class="{CssClass.SUMMARY}"> 

242 <table class="{CssClass.SUMMARY}"> 

243 {self.get_is_complete_tr(req)} 

244 </table> 

245 </div> 

246 <table class="{CssClass.TASKDETAIL}"> 

247 <col width="25%"> 

248 <col width="25%"> 

249 <col width="25%"> 

250 <col width="25%"> 

251 """ 

252 h += subheading_spanning_four_columns( 

253 self.wxstring(req, "t_about_referral")) 

254 h += """ 

255 <tr> 

256 <td>{q_method}</td> 

257 <td>{a_method}</td> 

258 <td>{q_priority}</td> 

259 <td class="{CssClass.HIGHLIGHT}">{a_priority}</td> 

260 </tr> 

261 """.format( 

262 CssClass=CssClass, 

263 q_method=self.wxstring(req, "f_referral_method"), 

264 a_method=answer(self.referral_method), 

265 q_priority=self.wxstring(req, "f_referral_priority"), 

266 a_priority=( 

267 answer(self.referral_priority, default_for_blank_strings=True) + # noqa 

268 ": " + answer(priority_name) 

269 ) 

270 ) 

271 h += self.four_column_row( 

272 self.wxstring(req, "f_referrer_name"), 

273 self.referrer_name, 

274 self.wxstring(req, "f_referring_specialty"), 

275 self.referring_specialty 

276 ) 

277 h += self.four_column_row( 

278 self.wxstring(req, "f_referrer_contact_details"), 

279 self.referrer_contact_details, 

280 self.wxstring(req, "f_referring_specialty_other"), 

281 self.referring_specialty_other 

282 ) 

283 h += self.four_column_row( 

284 self.wxstring(req, "f_referring_consultant"), 

285 self.referring_consultant, 

286 "", 

287 "" 

288 ) 

289 h += subheading_spanning_four_columns( 

290 self.wxstring(req, "t_patient")) 

291 h += """ 

292 <tr> 

293 <td>{q_when}</td> 

294 <td>{a_when}</td> 

295 <td>{q_where}</td> 

296 <td class="{CssClass.HIGHLIGHT}">{a_where}</td> 

297 </tr> 

298 """.format( 

299 CssClass=CssClass, 

300 q_when=self.wxstring(req, "f_admission_date"), 

301 a_when=answer( 

302 format_datetime(self.admission_date, DateFormat.LONG_DATE, 

303 default=None), ""), 

304 q_where=self.wxstring(req, "f_patient_location"), 

305 a_where=answer(self.patient_location), 

306 ) 

307 h += self.four_column_row( 

308 self.wxstring(req, "f_estimated_discharge_date"), 

309 format_datetime(self.estimated_discharge_date, 

310 DateFormat.LONG_DATE, ""), 

311 self.wxstring(req, "f_patient_aware_of_referral"), 

312 get_yes_no_none(req, self.patient_aware_of_referral) 

313 ) 

314 h += self.four_column_row( 

315 self.wxstring(req, "f_marital_status"), 

316 person_marital_status.get(self.marital_status_code, INVALID_VALUE), 

317 self.wxstring(req, "f_interpreter_required"), 

318 get_yes_no_none(req, self.interpreter_required) 

319 ) 

320 h += self.four_column_row( 

321 self.wxstring(req, "f_ethnic_category"), 

322 ethnic_category_code.get(self.ethnic_category_code, INVALID_VALUE), 

323 self.wxstring(req, "f_sensory_impairment"), 

324 get_yes_no_none(req, self.sensory_impairment) 

325 ) 

326 h += subheading_spanning_four_columns( 

327 self.wxstring(req, "t_admission_reason")) 

328 h += tr_span_col(answer(", ".join(admission_reasons), ""), cols=4) 

329 h += subheading_spanning_four_columns( 

330 self.wxstring(req, "t_other_people")) 

331 h += self.tr_qa( 

332 self.wxstring(req, "f_existing_psychiatric_teams"), 

333 self.existing_psychiatric_teams, "") 

334 h += self.tr_qa( 

335 self.wxstring(req, "f_care_coordinator"), 

336 self.care_coordinator, "") 

337 h += self.tr_qa( 

338 self.wxstring(req, "f_other_contact_details"), 

339 self.other_contact_details, "") 

340 h += subheading_spanning_four_columns( 

341 self.wxstring(req, "t_referral_reason")) 

342 h += tr_span_col(answer(self.referral_reason, ""), cols=4) 

343 h += """ 

344 </table> 

345 """ 

346 return h 

347 

348 

349# ============================================================================= 

350# CPFT_LPS_ResetResponseClock 

351# ============================================================================= 

352 

353class CPFTLPSResetResponseClock(TaskHasPatientMixin, TaskHasClinicianMixin, 

354 Task): 

355 """ 

356 Server implementation of the CPFT_LPS_ResetResponseClock task. 

357 """ 

358 __tablename__ = "cpft_lps_resetresponseclock" 

359 shortname = "CPFT_LPS_ResetResponseClock" 

360 

361 reset_start_time_to = Column( 

362 "reset_start_time_to", PendulumDateTimeAsIsoTextColType 

363 ) 

364 reason = Column("reason", UnicodeText) 

365 

366 @staticmethod 

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

368 _ = req.gettext 

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

370 

371 def is_complete(self) -> bool: 

372 return bool( 

373 self.reset_start_time_to and 

374 self.reason and 

375 self.field_contents_valid() 

376 ) 

377 

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

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

380 

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

382 h = f""" 

383 <div class="{CssClass.SUMMARY}"> 

384 <table class="{CssClass.SUMMARY}"> 

385 {self.get_is_complete_tr(req)} 

386 </table> 

387 </div> 

388 <table class="{CssClass.TASKDETAIL}"> 

389 <col width="25%"> 

390 <col width="75%"> 

391 """ 

392 h += tr_qa( 

393 self.wxstring(req, "to"), 

394 format_datetime(self.reset_start_time_to, 

395 DateFormat.LONG_DATETIME_WITH_DAY, 

396 default=None)) 

397 h += tr_qa(self.wxstring(req, "reason"), self.reason) 

398 h += """ 

399 </table> 

400 """ 

401 return h 

402 

403 

404# ============================================================================= 

405# CPFT_LPS_Discharge 

406# ============================================================================= 

407 

408class CPFTLPSDischarge(TaskHasPatientMixin, TaskHasClinicianMixin, Task): 

409 """ 

410 Server implementation of the CPFT_LPS_Discharge task. 

411 """ 

412 __tablename__ = "cpft_lps_discharge" 

413 shortname = "CPFT_LPS_Discharge" 

414 

415 discharge_date = Column("discharge_date", Date) 

416 discharge_reason_code = CamcopsColumn( 

417 "discharge_reason_code", UnicodeText, 

418 exempt_from_anonymisation=True) 

419 

420 leaflet_or_discharge_card_given = BoolColumn( 

421 "leaflet_or_discharge_card_given", 

422 constraint_name="ck_cpft_lps_discharge_lodcg" 

423 ) 

424 frequent_attender = BoolColumn("frequent_attender") 

425 patient_wanted_copy_of_letter = BoolColumn( 

426 # Was previously text! That wasn't right. 

427 "patient_wanted_copy_of_letter", 

428 ) 

429 gaf_at_first_assessment = CamcopsColumn( 

430 "gaf_at_first_assessment", Integer, 

431 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=100) 

432 ) 

433 gaf_at_discharge = CamcopsColumn( 

434 "gaf_at_discharge", Integer, 

435 permitted_value_checker=PermittedValueChecker(minimum=0, maximum=100) 

436 ) 

437 

438 referral_reason_self_harm_overdose = BoolColumn( 

439 "referral_reason_self_harm_overdose", 

440 constraint_name="ck_cpft_lps_discharge_rrshoverdose" 

441 ) 

442 referral_reason_self_harm_other = BoolColumn( 

443 "referral_reason_self_harm_other", 

444 constraint_name="ck_cpft_lps_discharge_rrshother" 

445 ) 

446 referral_reason_suicidal_ideas = BoolColumn( 

447 "referral_reason_suicidal_ideas", 

448 constraint_name="ck_cpft_lps_discharge_rrsuicidal" 

449 ) 

450 referral_reason_behavioural_disturbance = BoolColumn( 

451 "referral_reason_behavioural_disturbance", 

452 constraint_name="ck_cpft_lps_discharge_behavdisturb" 

453 ) 

454 referral_reason_low_mood = BoolColumn("referral_reason_low_mood") 

455 referral_reason_elevated_mood = BoolColumn("referral_reason_elevated_mood") 

456 referral_reason_psychosis = BoolColumn("referral_reason_psychosis") 

457 referral_reason_pre_transplant = BoolColumn( 

458 "referral_reason_pre_transplant", 

459 constraint_name="ck_cpft_lps_discharge_pretransplant" 

460 ) 

461 referral_reason_post_transplant = BoolColumn( 

462 "referral_reason_post_transplant", 

463 constraint_name="ck_cpft_lps_discharge_posttransplant" 

464 ) 

465 referral_reason_delirium = BoolColumn("referral_reason_delirium") 

466 referral_reason_anxiety = BoolColumn("referral_reason_anxiety") 

467 referral_reason_somatoform_mus = BoolColumn( 

468 "referral_reason_somatoform_mus", 

469 constraint_name="ck_cpft_lps_discharge_mus" 

470 ) 

471 referral_reason_motivation_adherence = BoolColumn( 

472 "referral_reason_motivation_adherence", 

473 constraint_name="ck_cpft_lps_discharge_motivadherence" 

474 ) 

475 referral_reason_capacity = BoolColumn("referral_reason_capacity") 

476 referral_reason_eating_disorder = BoolColumn( 

477 "referral_reason_eating_disorder", 

478 constraint_name="ck_cpft_lps_discharge_eatingdis" 

479 ) 

480 referral_reason_safeguarding = BoolColumn("referral_reason_safeguarding") 

481 referral_reason_discharge_placement = BoolColumn( 

482 "referral_reason_discharge_placement", 

483 constraint_name="ck_cpft_lps_discharge_dcplacement" 

484 ) 

485 referral_reason_cognitive_problem = BoolColumn( 

486 "referral_reason_cognitive_problem", 

487 constraint_name="ck_cpft_lps_discharge_cognitiveprob" 

488 ) 

489 referral_reason_substance_alcohol = BoolColumn( 

490 "referral_reason_substance_alcohol", 

491 constraint_name="ck_cpft_lps_discharge_alcohol" 

492 ) 

493 referral_reason_substance_other = BoolColumn( 

494 "referral_reason_substance_other", 

495 constraint_name="ck_cpft_lps_discharge_substanceother" 

496 ) 

497 referral_reason_other = BoolColumn("referral_reason_other") 

498 referral_reason_transplant_organ = CamcopsColumn( 

499 "referral_reason_transplant_organ", UnicodeText, 

500 exempt_from_anonymisation=True 

501 ) 

502 referral_reason_other_detail = Column( 

503 "referral_reason_other_detail", UnicodeText 

504 ) 

505 

506 diagnosis_no_active_mental_health_problem = BoolColumn( 

507 "diagnosis_no_active_mental_health_problem", 

508 constraint_name="ck_cpft_lps_discharge_nomhprob" 

509 ) 

510 diagnosis_psych_1_icd10code = Column( 

511 "diagnosis_psych_1_icd10code", DiagnosticCodeColType 

512 ) 

513 diagnosis_psych_1_description = CamcopsColumn( 

514 "diagnosis_psych_1_description", UnicodeText, 

515 exempt_from_anonymisation=True 

516 ) 

517 diagnosis_psych_2_icd10code = Column( 

518 "diagnosis_psych_2_icd10code", DiagnosticCodeColType 

519 ) 

520 diagnosis_psych_2_description = CamcopsColumn( 

521 "diagnosis_psych_2_description", UnicodeText, 

522 exempt_from_anonymisation=True 

523 ) 

524 diagnosis_psych_3_icd10code = Column( 

525 "diagnosis_psych_3_icd10code", DiagnosticCodeColType 

526 ) 

527 diagnosis_psych_3_description = CamcopsColumn( 

528 "diagnosis_psych_3_description", UnicodeText, 

529 exempt_from_anonymisation=True 

530 ) 

531 diagnosis_psych_4_icd10code = Column( 

532 "diagnosis_psych_4_icd10code", DiagnosticCodeColType 

533 ) 

534 diagnosis_psych_4_description = CamcopsColumn( 

535 "diagnosis_psych_4_description", UnicodeText, 

536 exempt_from_anonymisation=True 

537 ) 

538 diagnosis_medical_1 = Column("diagnosis_medical_1", UnicodeText) 

539 diagnosis_medical_2 = Column("diagnosis_medical_2", UnicodeText) 

540 diagnosis_medical_3 = Column("diagnosis_medical_3", UnicodeText) 

541 diagnosis_medical_4 = Column("diagnosis_medical_4", UnicodeText) 

542 

543 management_assessment_diagnostic = BoolColumn( 

544 "management_assessment_diagnostic", 

545 constraint_name="ck_cpft_lps_discharge_mx_ass_diag" 

546 ) 

547 management_medication = BoolColumn("management_medication") 

548 management_specialling_behavioural_disturbance = BoolColumn( 

549 "management_specialling_behavioural_disturbance", 

550 # Constraint name too long for MySQL unless we do this: 

551 constraint_name="ck_cpft_lps_discharge_msbd" 

552 ) 

553 management_supportive_patient = BoolColumn("management_supportive_patient") 

554 management_supportive_carers = BoolColumn("management_supportive_carers") 

555 management_supportive_staff = BoolColumn("management_supportive_staff") 

556 management_nursing_management = BoolColumn("management_nursing_management") 

557 management_therapy_cbt = BoolColumn("management_therapy_cbt") 

558 management_therapy_cat = BoolColumn("management_therapy_cat") 

559 management_therapy_other = BoolColumn("management_therapy_other") 

560 management_treatment_adherence = BoolColumn( 

561 "management_treatment_adherence", 

562 constraint_name="ck_cpft_lps_discharge_mx_rx_adhere" 

563 ) 

564 management_capacity = BoolColumn("management_capacity") 

565 management_education_patient = BoolColumn("management_education_patient") 

566 management_education_carers = BoolColumn("management_education_carers") 

567 management_education_staff = BoolColumn("management_education_staff") 

568 management_accommodation_placement = BoolColumn( 

569 "management_accommodation_placement", 

570 constraint_name="ck_cpft_lps_discharge_accom" 

571 ) 

572 management_signposting_external_referral = BoolColumn( 

573 "management_signposting_external_referral", 

574 constraint_name="ck_cpft_lps_discharge_mx_signpostrefer" 

575 ) 

576 management_mha_s136 = BoolColumn("management_mha_s136") 

577 management_mha_s5_2 = BoolColumn("management_mha_s5_2") 

578 management_mha_s2 = BoolColumn("management_mha_s2") 

579 management_mha_s3 = BoolColumn("management_mha_s3") 

580 management_complex_case_conference = BoolColumn( 

581 "management_complex_case_conference", 

582 constraint_name="ck_cpft_lps_discharge_caseconf" 

583 ) 

584 management_other = BoolColumn("management_other") 

585 management_other_detail = Column("management_other_detail", UnicodeText) 

586 

587 outcome = CamcopsColumn( 

588 "outcome", UnicodeText, 

589 exempt_from_anonymisation=True 

590 ) 

591 outcome_hospital_transfer_detail = Column( 

592 "outcome_hospital_transfer_detail", UnicodeText 

593 ) 

594 outcome_other_detail = Column("outcome_other_detail", UnicodeText) 

595 

596 @staticmethod 

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

598 _ = req.gettext 

599 return _("CPFT LPS – discharge") 

600 

601 def is_complete(self) -> bool: 

602 return bool( 

603 self.discharge_date and 

604 self.discharge_reason_code and 

605 # self.outcome and # v2.0.0 

606 self.field_contents_valid() 

607 ) 

608 

609 def get_discharge_reason(self, req: CamcopsRequest) -> Optional[str]: 

610 if self.discharge_reason_code == "F": 

611 return self.wxstring(req, "reason_code_F") 

612 elif self.discharge_reason_code == "A": 

613 return self.wxstring(req, "reason_code_A") 

614 elif self.discharge_reason_code == "O": 

615 return self.wxstring(req, "reason_code_O") 

616 elif self.discharge_reason_code == "C": 

617 return self.wxstring(req, "reason_code_C") 

618 else: 

619 return None 

620 

621 def get_referral_reasons(self, req: CamcopsRequest) -> List[str]: 

622 potential_referral_reasons = [ 

623 "referral_reason_self_harm_overdose", 

624 "referral_reason_self_harm_other", 

625 "referral_reason_suicidal_ideas", 

626 "referral_reason_behavioural_disturbance", 

627 "referral_reason_low_mood", 

628 "referral_reason_elevated_mood", 

629 "referral_reason_psychosis", 

630 "referral_reason_pre_transplant", 

631 "referral_reason_post_transplant", 

632 "referral_reason_delirium", 

633 "referral_reason_anxiety", 

634 "referral_reason_somatoform_mus", 

635 "referral_reason_motivation_adherence", 

636 "referral_reason_capacity", 

637 "referral_reason_eating_disorder", 

638 "referral_reason_safeguarding", 

639 "referral_reason_discharge_placement", 

640 "referral_reason_cognitive_problem", 

641 "referral_reason_substance_alcohol", 

642 "referral_reason_substance_other", 

643 "referral_reason_other", 

644 ] 

645 referral_reasons = [] 

646 for r in potential_referral_reasons: 

647 if getattr(self, r): 

648 referral_reasons.append(self.wxstring(req, "" + r)) 

649 return referral_reasons 

650 

651 def get_managements(self, req: CamcopsRequest) -> List[str]: 

652 potential_managements = [ 

653 "management_assessment_diagnostic", 

654 "management_medication", 

655 "management_specialling_behavioural_disturbance", 

656 "management_supportive_patient", 

657 "management_supportive_carers", 

658 "management_supportive_staff", 

659 "management_nursing_management", 

660 "management_therapy_cbt", 

661 "management_therapy_cat", 

662 "management_therapy_other", 

663 "management_treatment_adherence", 

664 "management_capacity", 

665 "management_education_patient", 

666 "management_education_carers", 

667 "management_education_staff", 

668 "management_accommodation_placement", 

669 "management_signposting_external_referral", 

670 "management_mha_s136", 

671 "management_mha_s5_2", 

672 "management_mha_s2", 

673 "management_mha_s3", 

674 "management_complex_case_conference", 

675 "management_other", 

676 ] 

677 managements = [] 

678 for r in potential_managements: 

679 if getattr(self, r): 

680 managements.append(self.wxstring(req, "" + r)) 

681 return managements 

682 

683 def get_psychiatric_diagnoses(self, req: CamcopsRequest) -> List[str]: 

684 psychiatric_diagnoses = [ 

685 self.wxstring(req, "diagnosis_no_active_mental_health_problem") 

686 ] if self.diagnosis_no_active_mental_health_problem else [] 

687 for i in range(1, 4 + 1): # magic number 

688 if getattr(self, "diagnosis_psych_" + str(i) + "_icd10code"): 

689 psychiatric_diagnoses.append( 

690 ws.webify(getattr(self, "diagnosis_psych_" + 

691 str(i) + "_icd10code")) + 

692 " – " + 

693 ws.webify(getattr(self, "diagnosis_psych_" + 

694 str(i) + "_description")) 

695 ) 

696 return psychiatric_diagnoses 

697 

698 def get_medical_diagnoses(self) -> List[str]: 

699 medical_diagnoses = [] 

700 for i in range(1, 4 + 1): # magic number 

701 if getattr(self, "diagnosis_medical_" + str(i)): 

702 medical_diagnoses.append( 

703 ws.webify(getattr(self, "diagnosis_medical_" + str(i)))) 

704 return medical_diagnoses 

705 

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

707 diagnoses = self.get_psychiatric_diagnoses(req) + \ 

708 self.get_medical_diagnoses() 

709 return [ 

710 CtvInfo( 

711 heading=ws.webify(self.wxstring(req, "discharge_reason")), 

712 content=self.get_discharge_reason(req) 

713 ), 

714 CtvInfo( 

715 heading=ws.webify( 

716 self.wxstring(req, "referral_reason_t")), 

717 content=", ".join(self.get_referral_reasons(req)) 

718 ), 

719 CtvInfo( 

720 heading=ws.webify(self.wxstring(req, "diagnoses_t")), 

721 content=", ".join(diagnoses) 

722 ), 

723 CtvInfo( 

724 heading=ws.webify(self.wxstring(req, "management_t")), 

725 content=", ".join(self.get_managements(req)) 

726 ), 

727 CtvInfo( 

728 heading=ws.webify(self.wxstring(req, "outcome_t")), 

729 content=self.outcome 

730 ), 

731 ] 

732 

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

734 h = f""" 

735 <div class="{CssClass.SUMMARY}"> 

736 <table class="{CssClass.SUMMARY}"> 

737 {self.get_is_complete_tr(req)} 

738 </table> 

739 </div> 

740 <table class="{CssClass.TASKDETAIL}"> 

741 <col width="40%"> 

742 <col width="60%"> 

743 """ 

744 h += tr_qa(self.wxstring(req, "discharge_date"), 

745 format_datetime(self.discharge_date, 

746 DateFormat.LONG_DATE_WITH_DAY, 

747 default=None), "") 

748 h += tr_qa(self.wxstring(req, "discharge_reason"), 

749 self.get_discharge_reason(req), "") 

750 h += tr_qa(self.wxstring(req, "leaflet_or_discharge_card_given"), 

751 get_yes_no_none(req, self.leaflet_or_discharge_card_given), 

752 "") 

753 h += tr_qa(self.wxstring(req, "frequent_attender"), 

754 get_yes_no_none(req, self.frequent_attender), "") 

755 h += tr_qa(self.wxstring(req, "patient_wanted_copy_of_letter"), 

756 self.patient_wanted_copy_of_letter, "") 

757 h += tr_qa(self.wxstring(req, "gaf_at_first_assessment"), 

758 self.gaf_at_first_assessment, "") 

759 h += tr_qa(self.wxstring(req, "gaf_at_discharge"), 

760 self.gaf_at_discharge, "") 

761 

762 h += subheading_spanning_two_columns( 

763 self.wxstring(req, "referral_reason_t")) 

764 h += tr_span_col(answer(", ".join(self.get_referral_reasons(req))), 

765 cols=2) 

766 h += tr_qa(self.wxstring(req, "referral_reason_transplant_organ"), 

767 self.referral_reason_transplant_organ, "") 

768 h += tr_qa(self.wxstring(req, "referral_reason_other_detail"), 

769 self.referral_reason_other_detail, "") 

770 

771 h += subheading_spanning_two_columns( 

772 self.wxstring(req, "diagnoses_t")) 

773 h += tr_qa(self.wxstring(req, "psychiatric_t"), 

774 "\n".join(self.get_psychiatric_diagnoses(req)), "") 

775 h += tr_qa(self.wxstring(req, "medical_t"), 

776 "\n".join(self.get_medical_diagnoses()), "") 

777 

778 h += subheading_spanning_two_columns(self.wxstring(req, "management_t")) 

779 h += tr_span_col(answer(", ".join(self.get_managements(req))), cols=2) 

780 h += tr_qa(self.wxstring(req, "management_other_detail"), 

781 self.management_other_detail, "") 

782 

783 h += subheading_spanning_two_columns(self.wxstring(req, "outcome_t")) 

784 h += tr_qa(self.wxstring(req, "outcome_t"), 

785 self.outcome, "") 

786 h += tr_qa(self.wxstring(req, "outcome_hospital_transfer_detail"), 

787 self.outcome_hospital_transfer_detail, "") 

788 h += tr_qa(self.wxstring(req, "outcome_other_detail"), 

789 self.outcome_other_detail, "") 

790 

791 h += """ 

792 </table> 

793 """ 

794 return h 

795 

796 

797# ============================================================================= 

798# Reports 

799# ============================================================================= 

800 

801class LPSReportSchema(ReportParamSchema): 

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

803 

804 

805class LPSReportReferredNotDischarged(Report): 

806 # noinspection PyMethodParameters 

807 @classproperty 

808 def report_id(cls) -> str: 

809 return "cpft_lps_referred_not_subsequently_discharged" 

810 

811 @classmethod 

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

813 _ = req.gettext 

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

815 

816 # noinspection PyMethodParameters 

817 @classproperty 

818 def superuser_only(cls) -> bool: 

819 return False 

820 

821 @staticmethod 

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

823 return LPSReportSchema 

824 

825 # noinspection PyProtectedMember,PyUnresolvedReferences 

826 def get_query(self, req: CamcopsRequest) -> SelectBase: 

827 which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1) 

828 if which_idnum is None: 

829 raise exc.HTTPBadRequest( 

830 f"{ViewParam.WHICH_IDNUM} not specified") 

831 

832 group_ids = req.user.ids_of_groups_user_may_report_on 

833 

834 # Step 1: link referral and patient 

835 p1 = Patient.__table__.alias("p1") 

836 i1 = PatientIdNum.__table__.alias("i1") 

837 desc = req.get_id_shortdesc(which_idnum) 

838 select_fields = [ 

839 CPFTLPSReferral.lps_division, 

840 CPFTLPSReferral.referral_date_time, 

841 CPFTLPSReferral.referral_priority, 

842 p1.c.surname, 

843 p1.c.forename, 

844 p1.c.dob, 

845 i1.c.idnum_value.label(desc), 

846 CPFTLPSReferral.patient_location, 

847 ] 

848 select_from = p1.join(CPFTLPSReferral.__table__, and_( 

849 p1.c._current == True, # noqa: E712 

850 CPFTLPSReferral.patient_id == p1.c.id, 

851 CPFTLPSReferral._device_id == p1.c._device_id, 

852 CPFTLPSReferral._era == p1.c._era, 

853 CPFTLPSReferral._current == True, 

854 )) 

855 select_from = select_from.join(i1, and_( 

856 i1.c.patient_id == p1.c.id, 

857 i1.c._device_id == p1.c._device_id, 

858 i1.c._era == p1.c._era, 

859 i1.c._current == True, # noqa: E712 

860 )) 

861 wheres = [ 

862 i1.c.which_idnum == which_idnum, 

863 ] 

864 if not req.user.superuser: 

865 # Restrict to accessible groups 

866 wheres.append(CPFTLPSReferral._group_id.in_(group_ids)) 

867 

868 # Step 2: not yet discharged 

869 p2 = Patient.__table__.alias("p2") 

870 i2 = PatientIdNum.__table__.alias("i2") 

871 discharge = ( 

872 select(['*']) 

873 .select_from( 

874 p2.join(CPFTLPSDischarge.__table__, and_( 

875 p2.c._current == True, # noqa: E712 

876 CPFTLPSDischarge.patient_id == p2.c.id, 

877 CPFTLPSDischarge._device_id == p2.c._device_id, 

878 CPFTLPSDischarge._era == p2.c._era, 

879 CPFTLPSDischarge._current == True, 

880 )).join(i2, and_( 

881 i2.c.patient_id == p2.c.id, 

882 i2.c._device_id == p2.c._device_id, 

883 i2.c._era == p2.c._era, 

884 i2.c._current == True, 

885 )) 

886 ) 

887 .where(and_( 

888 # Link on ID to main query: same patient 

889 i2.c.which_idnum == which_idnum, 

890 i2.c.idnum_value == i1.c.idnum_value, 

891 # Discharge later than referral 

892 (CPFTLPSDischarge.discharge_date >= 

893 CPFTLPSReferral.referral_date_time), 

894 )) 

895 ) # nopep8 

896 if not req.user.superuser: 

897 # Restrict to accessible groups 

898 discharge = discharge.where( 

899 CPFTLPSDischarge._group_id.in_(group_ids)) 

900 

901 wheres.append(~exists(discharge)) 

902 

903 # Finish up 

904 order_by = [ 

905 CPFTLPSReferral.lps_division, 

906 CPFTLPSReferral.referral_date_time, 

907 CPFTLPSReferral.referral_priority, 

908 ] 

909 query = select(select_fields) \ 

910 .select_from(select_from) \ 

911 .where(and_(*wheres)) \ 

912 .order_by(*order_by) 

913 return query 

914 

915 

916class LPSReportReferredNotClerkedOrDischarged(Report): 

917 # noinspection PyMethodParameters 

918 @classproperty 

919 def report_id(cls) -> str: 

920 return "cpft_lps_referred_not_subsequently_clerked_or_discharged" 

921 

922 @classmethod 

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

924 _ = req.gettext 

925 return _("CPFT LPS – referred but not yet fully assessed or discharged") # noqa 

926 

927 # noinspection PyMethodParameters 

928 @classproperty 

929 def superuser_only(cls) -> bool: 

930 return False 

931 

932 @staticmethod 

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

934 return LPSReportSchema 

935 

936 # noinspection PyProtectedMember 

937 def get_query(self, req: CamcopsRequest) -> SelectBase: 

938 which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1) 

939 if which_idnum is None: 

940 raise exc.HTTPBadRequest( 

941 f"{ViewParam.WHICH_IDNUM} not specified") 

942 

943 group_ids = req.user.ids_of_groups_user_may_report_on 

944 

945 # Step 1: link referral and patient 

946 # noinspection PyUnresolvedReferences 

947 p1 = Patient.__table__.alias("p1") 

948 # noinspection PyUnresolvedReferences 

949 i1 = PatientIdNum.__table__.alias("i1") 

950 desc = req.get_id_shortdesc(which_idnum) 

951 select_fields = [ 

952 CPFTLPSReferral.lps_division, 

953 CPFTLPSReferral.referral_date_time, 

954 CPFTLPSReferral.referral_priority, 

955 p1.c.surname, 

956 p1.c.forename, 

957 p1.c.dob, 

958 i1.c.idnum_value.label(desc), 

959 CPFTLPSReferral.patient_location, 

960 ] 

961 # noinspection PyUnresolvedReferences 

962 select_from = p1.join(CPFTLPSReferral.__table__, and_( 

963 p1.c._current == True, # noqa: E712 

964 CPFTLPSReferral.patient_id == p1.c.id, 

965 CPFTLPSReferral._device_id == p1.c._device_id, 

966 CPFTLPSReferral._era == p1.c._era, 

967 CPFTLPSReferral._current == True, 

968 )) 

969 select_from = select_from.join(i1, and_( 

970 i1.c.patient_id == p1.c.id, 

971 i1.c._device_id == p1.c._device_id, 

972 i1.c._era == p1.c._era, 

973 i1.c._current == True, # noqa: E712 

974 )) # nopep8 

975 wheres = [ 

976 i1.c.which_idnum == which_idnum, 

977 ] 

978 if not req.user.superuser: 

979 # Restrict to accessible groups 

980 wheres.append(CPFTLPSReferral._group_id.in_(group_ids)) 

981 

982 # Step 2: not yet discharged 

983 # noinspection PyUnresolvedReferences 

984 p2 = Patient.__table__.alias("p2") 

985 # noinspection PyUnresolvedReferences 

986 i2 = PatientIdNum.__table__.alias("i2") 

987 # noinspection PyUnresolvedReferences 

988 discharge = ( 

989 select(['*']) 

990 .select_from( 

991 p2.join(CPFTLPSDischarge.__table__, and_( 

992 p2.c._current == True, # noqa: E712 

993 CPFTLPSDischarge.patient_id == p2.c.id, 

994 CPFTLPSDischarge._device_id == p2.c._device_id, 

995 CPFTLPSDischarge._era == p2.c._era, 

996 CPFTLPSDischarge._current == True, 

997 )).join(i2, and_( 

998 i2.c.patient_id == p2.c.id, 

999 i2.c._device_id == p2.c._device_id, 

1000 i2.c._era == p2.c._era, 

1001 i2.c._current == True, 

1002 )) 

1003 ) 

1004 .where(and_( 

1005 # Link on ID to main query: same patient 

1006 i2.c.which_idnum == which_idnum, 

1007 i2.c.idnum_value == i1.c.idnum_value, 

1008 # Discharge later than referral 

1009 (CPFTLPSDischarge.discharge_date >= 

1010 CPFTLPSReferral.referral_date_time), 

1011 )) 

1012 ) # nopep8 

1013 if not req.user.superuser: 

1014 # Restrict to accessible groups 

1015 discharge = discharge.where( 

1016 CPFTLPSDischarge._group_id.in_(group_ids)) 

1017 wheres.append(~exists(discharge)) 

1018 

1019 # Step 3: not yet clerked 

1020 # noinspection PyUnresolvedReferences 

1021 p3 = Patient.__table__.alias("p3") 

1022 # noinspection PyUnresolvedReferences 

1023 i3 = PatientIdNum.__table__.alias("i3") 

1024 # noinspection PyUnresolvedReferences 

1025 clerking = ( 

1026 select(['*']) 

1027 .select_from( 

1028 p3.join(PsychiatricClerking.__table__, and_( 

1029 p3.c._current == True, # noqa: E712 

1030 PsychiatricClerking.patient_id == p3.c.id, 

1031 PsychiatricClerking._device_id == p3.c._device_id, 

1032 PsychiatricClerking._era == p3.c._era, 

1033 PsychiatricClerking._current == True, 

1034 )).join(i3, and_( 

1035 i3.c.patient_id == p3.c.id, 

1036 i3.c._device_id == p3.c._device_id, 

1037 i3.c._era == p3.c._era, 

1038 i3.c._current == True, 

1039 )) 

1040 ) 

1041 .where(and_( 

1042 # Link on ID to main query: same patient 

1043 i3.c.which_idnum == which_idnum, 

1044 i3.c.idnum_value == i1.c.idnum_value, 

1045 # Discharge later than referral 

1046 (PsychiatricClerking.when_created >= 

1047 CPFTLPSReferral.referral_date_time), 

1048 )) 

1049 ) # nopep8 

1050 if not req.user.superuser: 

1051 # Restrict to accessible groups 

1052 clerking = clerking.where( 

1053 PsychiatricClerking._group_id.in_(group_ids)) 

1054 wheres.append(~exists(clerking)) 

1055 

1056 # Finish up 

1057 order_by = [ 

1058 CPFTLPSReferral.lps_division, 

1059 CPFTLPSReferral.referral_date_time, 

1060 CPFTLPSReferral.referral_priority, 

1061 ] 

1062 query = ( 

1063 select(select_fields) 

1064 .select_from(select_from) 

1065 .where(and_(*wheres)) 

1066 .order_by(*order_by) 

1067 ) 

1068 return query