Coverage for tasks/cpft_lps.py: 51%

367 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/cpft_lps.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

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

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

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

14 (at your option) any later version. 

15 

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

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

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

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

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

23 

24=============================================================================== 

25 

26""" 

27 

28import datetime 

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 

36from pendulum import DateTime as Pendulum 

37import pyramid.httpexceptions as exc 

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

39from sqlalchemy.sql.selectable import SelectBase 

40from sqlalchemy.orm import Mapped, mapped_column 

41from sqlalchemy.sql.sqltypes import 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 mapped_bool_column, 

74 mapped_camcops_column, 

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): # type: ignore[misc] 

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: Mapped[Optional[Pendulum]] = mapped_column( 

105 PendulumDateTimeAsIsoTextColType 

106 ) 

107 lps_division: Mapped[Optional[str]] = mapped_camcops_column( 

108 UnicodeText, exempt_from_anonymisation=True 

109 ) 

110 referral_priority: Mapped[Optional[str]] = mapped_camcops_column( 

111 UnicodeText, exempt_from_anonymisation=True 

112 ) 

113 referral_method: Mapped[Optional[str]] = mapped_camcops_column( 

114 UnicodeText, exempt_from_anonymisation=True 

115 ) 

116 referrer_name: Mapped[Optional[str]] = mapped_column(UnicodeText) 

117 referrer_contact_details: Mapped[Optional[str]] = mapped_column( 

118 UnicodeText 

119 ) 

120 referring_consultant: Mapped[Optional[str]] = mapped_column(UnicodeText) 

121 referring_specialty: Mapped[Optional[str]] = mapped_camcops_column( 

122 UnicodeText, exempt_from_anonymisation=True 

123 ) 

124 referring_specialty_other: Mapped[Optional[str]] = mapped_column( 

125 UnicodeText 

126 ) 

127 patient_location: Mapped[Optional[str]] = mapped_column(UnicodeText) 

128 admission_date: Mapped[Optional[datetime.date]] = mapped_column() 

129 estimated_discharge_date: Mapped[Optional[datetime.date]] = mapped_column() 

130 patient_aware_of_referral: Mapped[Optional[bool]] = mapped_bool_column( 

131 "patient_aware_of_referral" 

132 ) 

133 interpreter_required: Mapped[Optional[bool]] = mapped_bool_column( 

134 "interpreter_required" 

135 ) 

136 sensory_impairment: Mapped[Optional[bool]] = mapped_bool_column( 

137 "sensory_impairment" 

138 ) 

139 marital_status_code: Mapped[Optional[str]] = mapped_camcops_column( 

140 CharColType, 

141 permitted_value_checker=PermittedValueChecker( 

142 permitted_values=PV_NHS_MARITAL_STATUS 

143 ), 

144 ) 

145 ethnic_category_code: Mapped[Optional[str]] = mapped_camcops_column( 

146 CharColType, 

147 permitted_value_checker=PermittedValueChecker( 

148 permitted_values=PV_NHS_ETHNIC_CATEGORY 

149 ), 

150 ) 

151 admission_reason_overdose: Mapped[Optional[bool]] = mapped_bool_column( 

152 "admission_reason_overdose" 

153 ) 

154 admission_reason_self_harm_not_overdose: Mapped[Optional[bool]] = ( 

155 mapped_bool_column( 

156 "admission_reason_self_harm_not_overdose", 

157 constraint_name="ck_cpft_lps_referral_arshno", 

158 ) 

159 ) 

160 admission_reason_confusion: Mapped[Optional[bool]] = mapped_bool_column( 

161 "admission_reason_confusion" 

162 ) 

163 admission_reason_trauma: Mapped[Optional[bool]] = mapped_bool_column( 

164 "admission_reason_trauma" 

165 ) 

166 admission_reason_falls: Mapped[Optional[bool]] = mapped_bool_column( 

167 "admission_reason_falls" 

168 ) 

169 admission_reason_infection: Mapped[Optional[bool]] = mapped_bool_column( 

170 "admission_reason_infection" 

171 ) 

172 admission_reason_poor_adherence: Mapped[Optional[bool]] = ( 

173 mapped_bool_column( 

174 "admission_reason_poor_adherence", 

175 constraint_name="ck_cpft_lps_referral_adpa", 

176 ) 

177 ) 

178 admission_reason_other: Mapped[Optional[bool]] = mapped_bool_column( 

179 "admission_reason_other" 

180 ) 

181 existing_psychiatric_teams: Mapped[Optional[str]] = mapped_column( 

182 UnicodeText 

183 ) 

184 care_coordinator: Mapped[Optional[str]] = mapped_column(UnicodeText) 

185 other_contact_details: Mapped[Optional[str]] = mapped_column(UnicodeText) 

186 referral_reason: Mapped[Optional[str]] = mapped_column(UnicodeText) 

187 

188 @staticmethod 

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

190 _ = req.gettext 

191 return _("CPFT LPS – referral") 

192 

193 def is_complete(self) -> bool: 

194 return bool( 

195 self.patient_location 

196 and self.referral_reason 

197 and self.field_contents_valid() 

198 ) 

199 

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

201 return [ 

202 CtvInfo( 

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

204 content=self.referral_reason, 

205 ) 

206 ] 

207 

208 @staticmethod 

209 def four_column_row( 

210 q1: str, a1: Any, q2: str, a2: Any, default: str = "" 

211 ) -> str: 

212 return f""" 

213 <tr> 

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

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

216 </tr> 

217 """ 

218 

219 @staticmethod 

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

221 return f""" 

222 <tr> 

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

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

225 </tr> 

226 """ 

227 

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

229 person_marital_status = get_nhs_dd_person_marital_status(req) 

230 ethnic_category_code = get_nhs_dd_ethnic_category_code(req) 

231 if self.lps_division == "G": 

232 banner_class = CssClass.BANNER_REFERRAL_GENERAL_ADULT 

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

234 elif self.lps_division == "O": 

235 banner_class = CssClass.BANNER_REFERRAL_OLD_AGE 

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

237 elif self.lps_division == "S": 

238 banner_class = CssClass.BANNER_REFERRAL_SUBSTANCE_MISUSE 

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

240 else: 

241 banner_class = "" 

242 division_name = None 

243 

244 if self.referral_priority == "R": 

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

246 elif self.referral_priority == "U": 

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

248 elif self.referral_priority == "E": 

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

250 else: 

251 priority_name = None 

252 

253 potential_admission_reasons = [ 

254 "admission_reason_overdose", 

255 "admission_reason_self_harm_not_overdose", 

256 "admission_reason_confusion", 

257 "admission_reason_trauma", 

258 "admission_reason_falls", 

259 "admission_reason_infection", 

260 "admission_reason_poor_adherence", 

261 "admission_reason_other", 

262 ] 

263 admission_reasons = [] 

264 for r in potential_admission_reasons: 

265 if getattr(self, r): 

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

267 

268 h = f""" 

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

270 {answer(division_name, default_for_blank_strings=True)} 

271 referral at { 

272 answer(format_datetime( 

273 self.referral_date_time, 

274 DateFormat.SHORT_DATETIME_WITH_DAY_NO_TZ, 

275 default=None))} 

276 </div> 

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

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

279 {self.get_is_complete_tr(req)} 

280 </table> 

281 </div> 

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

283 <col width="25%"> 

284 <col width="25%"> 

285 <col width="25%"> 

286 <col width="25%"> 

287 """ 

288 h += subheading_spanning_four_columns( 

289 self.wxstring(req, "t_about_referral") 

290 ) 

291 h += """ 

292 <tr> 

293 <td>{q_method}</td> 

294 <td>{a_method}</td> 

295 <td>{q_priority}</td> 

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

297 </tr> 

298 """.format( 

299 CssClass=CssClass, 

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

301 a_method=answer(self.referral_method), 

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

303 a_priority=( 

304 answer(self.referral_priority, default_for_blank_strings=True) 

305 + ": " 

306 + answer(priority_name) 

307 ), 

308 ) 

309 h += self.four_column_row( 

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

311 self.referrer_name, 

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

313 self.referring_specialty, 

314 ) 

315 h += self.four_column_row( 

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

317 self.referrer_contact_details, 

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

319 self.referring_specialty_other, 

320 ) 

321 h += self.four_column_row( 

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

323 self.referring_consultant, 

324 "", 

325 "", 

326 ) 

327 h += subheading_spanning_four_columns(self.wxstring(req, "t_patient")) 

328 h += """ 

329 <tr> 

330 <td>{q_when}</td> 

331 <td>{a_when}</td> 

332 <td>{q_where}</td> 

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

334 </tr> 

335 """.format( 

336 CssClass=CssClass, 

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

338 a_when=answer( 

339 format_datetime( 

340 self.admission_date, DateFormat.LONG_DATE, default=None 

341 ), 

342 "", 

343 ), 

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

345 a_where=answer(self.patient_location), 

346 ) 

347 h += self.four_column_row( 

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

349 format_datetime( 

350 self.estimated_discharge_date, DateFormat.LONG_DATE, "" 

351 ), 

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

353 get_yes_no_none(req, self.patient_aware_of_referral), 

354 ) 

355 h += self.four_column_row( 

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

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

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

359 get_yes_no_none(req, self.interpreter_required), 

360 ) 

361 h += self.four_column_row( 

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

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

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

365 get_yes_no_none(req, self.sensory_impairment), 

366 ) 

367 h += subheading_spanning_four_columns( 

368 self.wxstring(req, "t_admission_reason") 

369 ) 

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

371 h += subheading_spanning_four_columns( 

372 self.wxstring(req, "t_other_people") 

373 ) 

374 h += self.tr_qa( 

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

376 self.existing_psychiatric_teams, 

377 "", 

378 ) 

379 h += self.tr_qa( 

380 self.wxstring(req, "f_care_coordinator"), self.care_coordinator, "" 

381 ) 

382 h += self.tr_qa( 

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

384 self.other_contact_details, 

385 "", 

386 ) 

387 h += subheading_spanning_four_columns( 

388 self.wxstring(req, "t_referral_reason") 

389 ) 

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

391 h += """ 

392 </table> 

393 """ 

394 return h 

395 

396 

397# ============================================================================= 

398# CPFT_LPS_ResetResponseClock 

399# ============================================================================= 

400 

401 

402class CPFTLPSResetResponseClock( # type: ignore[misc] 

403 TaskHasPatientMixin, TaskHasClinicianMixin, Task 

404): 

405 """ 

406 Server implementation of the CPFT_LPS_ResetResponseClock task. 

407 """ 

408 

409 __tablename__ = "cpft_lps_resetresponseclock" 

410 shortname = "CPFT_LPS_ResetResponseClock" 

411 info_filename_stem = "clinical" 

412 

413 reset_start_time_to: Mapped[Optional[Pendulum]] = mapped_column( 

414 PendulumDateTimeAsIsoTextColType 

415 ) 

416 reason: Mapped[Optional[str]] = mapped_column(UnicodeText) 

417 

418 @staticmethod 

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

420 _ = req.gettext 

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

422 

423 def is_complete(self) -> bool: 

424 return bool( 

425 self.reset_start_time_to 

426 and self.reason 

427 and self.field_contents_valid() 

428 ) 

429 

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

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

432 

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

434 h = f""" 

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

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

437 {self.get_is_complete_tr(req)} 

438 </table> 

439 </div> 

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

441 <col width="25%"> 

442 <col width="75%"> 

443 """ 

444 h += tr_qa( 

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

446 format_datetime( 

447 self.reset_start_time_to, 

448 DateFormat.LONG_DATETIME_WITH_DAY, 

449 default=None, 

450 ), 

451 ) 

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

453 h += """ 

454 </table> 

455 """ 

456 return h 

457 

458 

459# ============================================================================= 

460# CPFT_LPS_Discharge 

461# ============================================================================= 

462 

463 

464class CPFTLPSDischarge(TaskHasPatientMixin, TaskHasClinicianMixin, Task): # type: ignore[misc] # noqa: E501 

465 """ 

466 Server implementation of the CPFT_LPS_Discharge task. 

467 """ 

468 

469 __tablename__ = "cpft_lps_discharge" 

470 shortname = "CPFT_LPS_Discharge" 

471 info_filename_stem = "clinical" 

472 

473 discharge_date: Mapped[Optional[datetime.date]] = mapped_column() 

474 discharge_reason_code: Mapped[Optional[str]] = mapped_camcops_column( 

475 UnicodeText, exempt_from_anonymisation=True 

476 ) 

477 

478 leaflet_or_discharge_card_given: Mapped[Optional[bool]] = ( 

479 mapped_bool_column( 

480 "leaflet_or_discharge_card_given", 

481 constraint_name="ck_cpft_lps_discharge_lodcg", 

482 ) 

483 ) 

484 frequent_attender: Mapped[Optional[bool]] = mapped_bool_column( 

485 "frequent_attender" 

486 ) 

487 patient_wanted_copy_of_letter: Mapped[Optional[bool]] = mapped_bool_column( 

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

489 "patient_wanted_copy_of_letter" 

490 ) 

491 gaf_at_first_assessment: Mapped[Optional[int]] = mapped_camcops_column( 

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

493 ) 

494 gaf_at_discharge: Mapped[Optional[int]] = mapped_camcops_column( 

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

496 ) 

497 

498 referral_reason_self_harm_overdose: Mapped[Optional[bool]] = ( 

499 mapped_bool_column( 

500 "referral_reason_self_harm_overdose", 

501 constraint_name="ck_cpft_lps_discharge_rrshoverdose", 

502 ) 

503 ) 

504 referral_reason_self_harm_other: Mapped[Optional[bool]] = ( 

505 mapped_bool_column( 

506 "referral_reason_self_harm_other", 

507 constraint_name="ck_cpft_lps_discharge_rrshother", 

508 ) 

509 ) 

510 referral_reason_suicidal_ideas: Mapped[Optional[bool]] = ( 

511 mapped_bool_column( 

512 "referral_reason_suicidal_ideas", 

513 constraint_name="ck_cpft_lps_discharge_rrsuicidal", 

514 ) 

515 ) 

516 referral_reason_behavioural_disturbance: Mapped[Optional[bool]] = ( 

517 mapped_bool_column( 

518 "referral_reason_behavioural_disturbance", 

519 constraint_name="ck_cpft_lps_discharge_behavdisturb", 

520 ) 

521 ) 

522 referral_reason_low_mood: Mapped[Optional[bool]] = mapped_bool_column( 

523 "referral_reason_low_mood" 

524 ) 

525 referral_reason_elevated_mood: Mapped[Optional[bool]] = mapped_bool_column( 

526 "referral_reason_elevated_mood" 

527 ) 

528 referral_reason_psychosis: Mapped[Optional[bool]] = mapped_bool_column( 

529 "referral_reason_psychosis" 

530 ) 

531 referral_reason_pre_transplant: Mapped[Optional[bool]] = ( 

532 mapped_bool_column( 

533 "referral_reason_pre_transplant", 

534 constraint_name="ck_cpft_lps_discharge_pretransplant", 

535 ) 

536 ) 

537 referral_reason_post_transplant: Mapped[Optional[bool]] = ( 

538 mapped_bool_column( 

539 "referral_reason_post_transplant", 

540 constraint_name="ck_cpft_lps_discharge_posttransplant", 

541 ) 

542 ) 

543 referral_reason_delirium: Mapped[Optional[bool]] = mapped_bool_column( 

544 "referral_reason_delirium" 

545 ) 

546 referral_reason_anxiety: Mapped[Optional[bool]] = mapped_bool_column( 

547 "referral_reason_anxiety" 

548 ) 

549 referral_reason_somatoform_mus: Mapped[Optional[bool]] = ( 

550 mapped_bool_column( 

551 "referral_reason_somatoform_mus", 

552 constraint_name="ck_cpft_lps_discharge_mus", 

553 ) 

554 ) 

555 referral_reason_motivation_adherence: Mapped[Optional[bool]] = ( 

556 mapped_bool_column( 

557 "referral_reason_motivation_adherence", 

558 constraint_name="ck_cpft_lps_discharge_motivadherence", 

559 ) 

560 ) 

561 referral_reason_capacity: Mapped[Optional[bool]] = mapped_bool_column( 

562 "referral_reason_capacity" 

563 ) 

564 referral_reason_eating_disorder: Mapped[Optional[bool]] = ( 

565 mapped_bool_column( 

566 "referral_reason_eating_disorder", 

567 constraint_name="ck_cpft_lps_discharge_eatingdis", 

568 ) 

569 ) 

570 referral_reason_safeguarding: Mapped[Optional[bool]] = mapped_bool_column( 

571 "referral_reason_safeguarding" 

572 ) 

573 referral_reason_discharge_placement: Mapped[Optional[bool]] = ( 

574 mapped_bool_column( 

575 "referral_reason_discharge_placement", 

576 constraint_name="ck_cpft_lps_discharge_dcplacement", 

577 ) 

578 ) 

579 referral_reason_cognitive_problem: Mapped[Optional[bool]] = ( 

580 mapped_bool_column( 

581 "referral_reason_cognitive_problem", 

582 constraint_name="ck_cpft_lps_discharge_cognitiveprob", 

583 ) 

584 ) 

585 referral_reason_substance_alcohol: Mapped[Optional[bool]] = ( 

586 mapped_bool_column( 

587 "referral_reason_substance_alcohol", 

588 constraint_name="ck_cpft_lps_discharge_alcohol", 

589 ) 

590 ) 

591 referral_reason_substance_other: Mapped[Optional[bool]] = ( 

592 mapped_bool_column( 

593 "referral_reason_substance_other", 

594 constraint_name="ck_cpft_lps_discharge_substanceother", 

595 ) 

596 ) 

597 referral_reason_other: Mapped[Optional[bool]] = mapped_bool_column( 

598 "referral_reason_other" 

599 ) 

600 referral_reason_transplant_organ: Mapped[Optional[str]] = ( 

601 mapped_camcops_column( 

602 UnicodeText, 

603 exempt_from_anonymisation=True, 

604 ) 

605 ) 

606 referral_reason_other_detail: Mapped[Optional[str]] = mapped_column( 

607 UnicodeText 

608 ) 

609 

610 diagnosis_no_active_mental_health_problem: Mapped[Optional[bool]] = ( 

611 mapped_bool_column( 

612 "diagnosis_no_active_mental_health_problem", 

613 constraint_name="ck_cpft_lps_discharge_nomhprob", 

614 ) 

615 ) 

616 diagnosis_psych_1_icd10code: Mapped[Optional[str]] = mapped_column( 

617 DiagnosticCodeColType 

618 ) 

619 diagnosis_psych_1_description: Mapped[Optional[str]] = ( 

620 mapped_camcops_column( 

621 UnicodeText, 

622 exempt_from_anonymisation=True, 

623 ) 

624 ) 

625 diagnosis_psych_2_icd10code: Mapped[Optional[str]] = mapped_column( 

626 DiagnosticCodeColType 

627 ) 

628 diagnosis_psych_2_description: Mapped[Optional[str]] = ( 

629 mapped_camcops_column( 

630 UnicodeText, 

631 exempt_from_anonymisation=True, 

632 ) 

633 ) 

634 diagnosis_psych_3_icd10code: Mapped[Optional[str]] = mapped_column( 

635 DiagnosticCodeColType 

636 ) 

637 diagnosis_psych_3_description: Mapped[Optional[str]] = ( 

638 mapped_camcops_column( 

639 UnicodeText, 

640 exempt_from_anonymisation=True, 

641 ) 

642 ) 

643 diagnosis_psych_4_icd10code: Mapped[Optional[str]] = mapped_column( 

644 DiagnosticCodeColType 

645 ) 

646 diagnosis_psych_4_description: Mapped[Optional[str]] = ( 

647 mapped_camcops_column( 

648 UnicodeText, 

649 exempt_from_anonymisation=True, 

650 ) 

651 ) 

652 diagnosis_medical_1: Mapped[Optional[str]] = mapped_column(UnicodeText) 

653 diagnosis_medical_2: Mapped[Optional[str]] = mapped_column(UnicodeText) 

654 diagnosis_medical_3: Mapped[Optional[str]] = mapped_column(UnicodeText) 

655 diagnosis_medical_4: Mapped[Optional[str]] = mapped_column(UnicodeText) 

656 

657 management_assessment_diagnostic: Mapped[Optional[bool]] = ( 

658 mapped_bool_column( 

659 "management_assessment_diagnostic", 

660 constraint_name="ck_cpft_lps_discharge_mx_ass_diag", 

661 ) 

662 ) 

663 management_medication: Mapped[Optional[bool]] = mapped_bool_column( 

664 "management_medication" 

665 ) 

666 management_specialling_behavioural_disturbance: Mapped[Optional[bool]] = ( 

667 mapped_bool_column( 

668 "management_specialling_behavioural_disturbance", 

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

670 constraint_name="ck_cpft_lps_discharge_msbd", 

671 ) 

672 ) 

673 management_supportive_patient: Mapped[Optional[bool]] = mapped_bool_column( 

674 "management_supportive_patient" 

675 ) 

676 management_supportive_carers: Mapped[Optional[bool]] = mapped_bool_column( 

677 "management_supportive_carers" 

678 ) 

679 management_supportive_staff: Mapped[Optional[bool]] = mapped_bool_column( 

680 "management_supportive_staff" 

681 ) 

682 management_nursing_management: Mapped[Optional[bool]] = mapped_bool_column( 

683 "management_nursing_management" 

684 ) 

685 management_therapy_cbt: Mapped[Optional[bool]] = mapped_bool_column( 

686 "management_therapy_cbt" 

687 ) 

688 management_therapy_cat: Mapped[Optional[bool]] = mapped_bool_column( 

689 "management_therapy_cat" 

690 ) 

691 management_therapy_other: Mapped[Optional[bool]] = mapped_bool_column( 

692 "management_therapy_other" 

693 ) 

694 management_treatment_adherence: Mapped[Optional[bool]] = ( 

695 mapped_bool_column( 

696 "management_treatment_adherence", 

697 constraint_name="ck_cpft_lps_discharge_mx_rx_adhere", 

698 ) 

699 ) 

700 management_capacity: Mapped[Optional[bool]] = mapped_bool_column( 

701 "management_capacity" 

702 ) 

703 management_education_patient: Mapped[Optional[bool]] = mapped_bool_column( 

704 "management_education_patient" 

705 ) 

706 management_education_carers: Mapped[Optional[bool]] = mapped_bool_column( 

707 "management_education_carers" 

708 ) 

709 management_education_staff: Mapped[Optional[bool]] = mapped_bool_column( 

710 "management_education_staff" 

711 ) 

712 management_accommodation_placement: Mapped[Optional[bool]] = ( 

713 mapped_bool_column( 

714 "management_accommodation_placement", 

715 constraint_name="ck_cpft_lps_discharge_accom", 

716 ) 

717 ) 

718 management_signposting_external_referral: Mapped[Optional[bool]] = ( 

719 mapped_bool_column( 

720 "management_signposting_external_referral", 

721 constraint_name="ck_cpft_lps_discharge_mx_signpostrefer", 

722 ) 

723 ) 

724 management_mha_s136: Mapped[Optional[bool]] = mapped_bool_column( 

725 "management_mha_s136" 

726 ) 

727 management_mha_s5_2: Mapped[Optional[bool]] = mapped_bool_column( 

728 "management_mha_s5_2" 

729 ) 

730 management_mha_s2: Mapped[Optional[bool]] = mapped_bool_column( 

731 "management_mha_s2" 

732 ) 

733 management_mha_s3: Mapped[Optional[bool]] = mapped_bool_column( 

734 "management_mha_s3" 

735 ) 

736 management_complex_case_conference: Mapped[Optional[bool]] = ( 

737 mapped_bool_column( 

738 "management_complex_case_conference", 

739 constraint_name="ck_cpft_lps_discharge_caseconf", 

740 ) 

741 ) 

742 management_other: Mapped[Optional[bool]] = mapped_bool_column( 

743 "management_other" 

744 ) 

745 management_other_detail: Mapped[Optional[str]] = mapped_column(UnicodeText) 

746 

747 outcome: Mapped[Optional[str]] = mapped_camcops_column( 

748 UnicodeText, exempt_from_anonymisation=True 

749 ) 

750 outcome_hospital_transfer_detail: Mapped[Optional[str]] = mapped_column( 

751 UnicodeText 

752 ) 

753 outcome_other_detail: Mapped[Optional[str]] = mapped_column(UnicodeText) 

754 

755 @staticmethod 

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

757 _ = req.gettext 

758 return _("CPFT LPS – discharge") 

759 

760 def is_complete(self) -> bool: 

761 return bool( 

762 self.discharge_date 

763 and self.discharge_reason_code 

764 and 

765 # self.outcome and # v2.0.0 

766 self.field_contents_valid() 

767 ) 

768 

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

770 if self.discharge_reason_code == "F": 

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

772 elif self.discharge_reason_code == "A": 

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

774 elif self.discharge_reason_code == "O": 

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

776 elif self.discharge_reason_code == "C": 

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

778 else: 

779 return None 

780 

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

782 potential_referral_reasons = [ 

783 "referral_reason_self_harm_overdose", 

784 "referral_reason_self_harm_other", 

785 "referral_reason_suicidal_ideas", 

786 "referral_reason_behavioural_disturbance", 

787 "referral_reason_low_mood", 

788 "referral_reason_elevated_mood", 

789 "referral_reason_psychosis", 

790 "referral_reason_pre_transplant", 

791 "referral_reason_post_transplant", 

792 "referral_reason_delirium", 

793 "referral_reason_anxiety", 

794 "referral_reason_somatoform_mus", 

795 "referral_reason_motivation_adherence", 

796 "referral_reason_capacity", 

797 "referral_reason_eating_disorder", 

798 "referral_reason_safeguarding", 

799 "referral_reason_discharge_placement", 

800 "referral_reason_cognitive_problem", 

801 "referral_reason_substance_alcohol", 

802 "referral_reason_substance_other", 

803 "referral_reason_other", 

804 ] 

805 referral_reasons = [] 

806 for r in potential_referral_reasons: 

807 if getattr(self, r): 

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

809 return referral_reasons 

810 

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

812 potential_managements = [ 

813 "management_assessment_diagnostic", 

814 "management_medication", 

815 "management_specialling_behavioural_disturbance", 

816 "management_supportive_patient", 

817 "management_supportive_carers", 

818 "management_supportive_staff", 

819 "management_nursing_management", 

820 "management_therapy_cbt", 

821 "management_therapy_cat", 

822 "management_therapy_other", 

823 "management_treatment_adherence", 

824 "management_capacity", 

825 "management_education_patient", 

826 "management_education_carers", 

827 "management_education_staff", 

828 "management_accommodation_placement", 

829 "management_signposting_external_referral", 

830 "management_mha_s136", 

831 "management_mha_s5_2", 

832 "management_mha_s2", 

833 "management_mha_s3", 

834 "management_complex_case_conference", 

835 "management_other", 

836 ] 

837 managements = [] 

838 for r in potential_managements: 

839 if getattr(self, r): 

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

841 return managements 

842 

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

844 psychiatric_diagnoses = ( 

845 [self.wxstring(req, "diagnosis_no_active_mental_health_problem")] 

846 if self.diagnosis_no_active_mental_health_problem 

847 else [] 

848 ) 

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

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

851 psychiatric_diagnoses.append( 

852 ws.webify( 

853 getattr( 

854 self, "diagnosis_psych_" + str(i) + "_icd10code" 

855 ) 

856 ) 

857 + " – " 

858 + ws.webify( 

859 getattr( 

860 self, "diagnosis_psych_" + str(i) + "_description" 

861 ) 

862 ) 

863 ) 

864 return psychiatric_diagnoses 

865 

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

867 medical_diagnoses = [] 

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

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

870 medical_diagnoses.append( 

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

872 ) 

873 return medical_diagnoses 

874 

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

876 diagnoses = ( 

877 self.get_psychiatric_diagnoses(req) + self.get_medical_diagnoses() 

878 ) 

879 return [ 

880 CtvInfo( 

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

882 content=self.get_discharge_reason(req), 

883 ), 

884 CtvInfo( 

885 heading=ws.webify(self.wxstring(req, "referral_reason_t")), 

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

887 ), 

888 CtvInfo( 

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

890 content=", ".join(diagnoses), 

891 ), 

892 CtvInfo( 

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

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

895 ), 

896 CtvInfo( 

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

898 content=self.outcome, 

899 ), 

900 ] 

901 

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

903 h = f""" 

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

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

906 {self.get_is_complete_tr(req)} 

907 </table> 

908 </div> 

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

910 <col width="40%"> 

911 <col width="60%"> 

912 """ 

913 h += tr_qa( 

914 self.wxstring(req, "discharge_date"), 

915 format_datetime( 

916 self.discharge_date, 

917 DateFormat.LONG_DATE_WITH_DAY, 

918 default=None, 

919 ), 

920 "", 

921 ) 

922 h += tr_qa( 

923 self.wxstring(req, "discharge_reason"), 

924 self.get_discharge_reason(req), 

925 "", 

926 ) 

927 h += tr_qa( 

928 self.wxstring(req, "leaflet_or_discharge_card_given"), 

929 get_yes_no_none(req, self.leaflet_or_discharge_card_given), 

930 "", 

931 ) 

932 h += tr_qa( 

933 self.wxstring(req, "frequent_attender"), 

934 get_yes_no_none(req, self.frequent_attender), 

935 "", 

936 ) 

937 h += tr_qa( 

938 self.wxstring(req, "patient_wanted_copy_of_letter"), 

939 self.patient_wanted_copy_of_letter, 

940 "", 

941 ) 

942 h += tr_qa( 

943 self.wxstring(req, "gaf_at_first_assessment"), 

944 self.gaf_at_first_assessment, 

945 "", 

946 ) 

947 h += tr_qa( 

948 self.wxstring(req, "gaf_at_discharge"), self.gaf_at_discharge, "" 

949 ) 

950 

951 h += subheading_spanning_two_columns( 

952 self.wxstring(req, "referral_reason_t") 

953 ) 

954 h += tr_span_col( 

955 answer(", ".join(self.get_referral_reasons(req))), cols=2 

956 ) 

957 h += tr_qa( 

958 self.wxstring(req, "referral_reason_transplant_organ"), 

959 self.referral_reason_transplant_organ, 

960 "", 

961 ) 

962 h += tr_qa( 

963 self.wxstring(req, "referral_reason_other_detail"), 

964 self.referral_reason_other_detail, 

965 "", 

966 ) 

967 

968 h += subheading_spanning_two_columns(self.wxstring(req, "diagnoses_t")) 

969 h += tr_qa( 

970 self.wxstring(req, "psychiatric_t"), 

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

972 "", 

973 ) 

974 h += tr_qa( 

975 self.wxstring(req, "medical_t"), 

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

977 "", 

978 ) 

979 

980 h += subheading_spanning_two_columns( 

981 self.wxstring(req, "management_t") 

982 ) 

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

984 h += tr_qa( 

985 self.wxstring(req, "management_other_detail"), 

986 self.management_other_detail, 

987 "", 

988 ) 

989 

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

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

992 h += tr_qa( 

993 self.wxstring(req, "outcome_hospital_transfer_detail"), 

994 self.outcome_hospital_transfer_detail, 

995 "", 

996 ) 

997 h += tr_qa( 

998 self.wxstring(req, "outcome_other_detail"), 

999 self.outcome_other_detail, 

1000 "", 

1001 ) 

1002 

1003 h += """ 

1004 </table> 

1005 """ 

1006 return h 

1007 

1008 

1009# ============================================================================= 

1010# Reports 

1011# ============================================================================= 

1012 

1013 

1014class LPSReportSchema(ReportParamSchema): 

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

1016 

1017 

1018class LPSReportReferredNotDischarged(Report): 

1019 # noinspection PyMethodParameters 

1020 @classproperty 

1021 def report_id(cls) -> str: 

1022 return "cpft_lps_referred_not_subsequently_discharged" 

1023 

1024 @classmethod 

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

1026 _ = req.gettext 

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

1028 

1029 # noinspection PyMethodParameters 

1030 @classproperty 

1031 def superuser_only(cls) -> bool: 

1032 return False 

1033 

1034 @staticmethod 

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

1036 return LPSReportSchema 

1037 

1038 # noinspection PyProtectedMember,PyUnresolvedReferences 

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

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

1041 if which_idnum is None: 

1042 raise exc.HTTPBadRequest(f"{ViewParam.WHICH_IDNUM} not specified") 

1043 

1044 group_ids = req.user.ids_of_groups_user_may_report_on 

1045 

1046 # Step 1: link referral and patient 

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

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

1049 desc = req.get_id_shortdesc(which_idnum) 

1050 select_fields = [ 

1051 CPFTLPSReferral.lps_division, 

1052 CPFTLPSReferral.referral_date_time, 

1053 CPFTLPSReferral.referral_priority, 

1054 p1.c.surname, 

1055 p1.c.forename, 

1056 p1.c.dob, 

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

1058 CPFTLPSReferral.patient_location, 

1059 ] 

1060 select_from = p1.join( 

1061 CPFTLPSReferral.__table__, 

1062 and_( 

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

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

1065 CPFTLPSReferral._device_id == p1.c._device_id, 

1066 CPFTLPSReferral._era == p1.c._era, 

1067 CPFTLPSReferral._current == True, # noqa: E712 

1068 ), 

1069 ) 

1070 select_from = select_from.join( 

1071 i1, 

1072 and_( 

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

1074 i1.c._device_id == p1.c._device_id, 

1075 i1.c._era == p1.c._era, 

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

1077 ), 

1078 ) 

1079 wheres = [i1.c.which_idnum == which_idnum] 

1080 if not req.user.superuser: 

1081 # Restrict to accessible groups 

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

1083 

1084 # Step 2: not yet discharged 

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

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

1087 discharge = ( 

1088 select("*") 

1089 .select_from( 

1090 p2.join( 

1091 CPFTLPSDischarge.__table__, 

1092 and_( 

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

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

1095 CPFTLPSDischarge._device_id == p2.c._device_id, 

1096 CPFTLPSDischarge._era == p2.c._era, 

1097 CPFTLPSDischarge._current == True, # noqa: E712 

1098 ), 

1099 ).join( 

1100 i2, 

1101 and_( 

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

1103 i2.c._device_id == p2.c._device_id, 

1104 i2.c._era == p2.c._era, 

1105 i2.c._current == True, # noqa: E712 

1106 ), 

1107 ) 

1108 ) 

1109 .where( 

1110 and_( 

1111 # Link on ID to main query: same patient 

1112 i2.c.which_idnum == which_idnum, 

1113 i2.c.idnum_value == i1.c.idnum_value, 

1114 # Discharge later than referral 

1115 ( 

1116 CPFTLPSDischarge.discharge_date 

1117 >= CPFTLPSReferral.referral_date_time 

1118 ), 

1119 ) 

1120 ) 

1121 ) # nopep8 

1122 if not req.user.superuser: 

1123 # Restrict to accessible groups 

1124 discharge = discharge.where( 

1125 CPFTLPSDischarge._group_id.in_(group_ids) 

1126 ) 

1127 

1128 wheres.append(~exists(discharge)) 

1129 

1130 # Finish up 

1131 order_by = [ 

1132 CPFTLPSReferral.lps_division, 

1133 CPFTLPSReferral.referral_date_time, 

1134 CPFTLPSReferral.referral_priority, 

1135 ] 

1136 query = ( 

1137 select(*select_fields) 

1138 .select_from(select_from) 

1139 .where(and_(*wheres)) 

1140 .order_by(*order_by) 

1141 ) 

1142 return query 

1143 

1144 

1145class LPSReportReferredNotClerkedOrDischarged(Report): 

1146 # noinspection PyMethodParameters 

1147 @classproperty 

1148 def report_id(cls) -> str: 

1149 return "cpft_lps_referred_not_subsequently_clerked_or_discharged" 

1150 

1151 @classmethod 

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

1153 _ = req.gettext 

1154 return _( 

1155 "CPFT LPS – referred but not yet fully assessed or discharged" 

1156 ) 

1157 

1158 # noinspection PyMethodParameters 

1159 @classproperty 

1160 def superuser_only(cls) -> bool: 

1161 return False 

1162 

1163 @staticmethod 

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

1165 return LPSReportSchema 

1166 

1167 # noinspection PyProtectedMember 

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

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

1170 if which_idnum is None: 

1171 raise exc.HTTPBadRequest(f"{ViewParam.WHICH_IDNUM} not specified") 

1172 

1173 group_ids = req.user.ids_of_groups_user_may_report_on 

1174 

1175 # Step 1: link referral and patient 

1176 # noinspection PyUnresolvedReferences 

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

1178 # noinspection PyUnresolvedReferences 

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

1180 desc = req.get_id_shortdesc(which_idnum) 

1181 select_fields = [ 

1182 CPFTLPSReferral.lps_division, 

1183 CPFTLPSReferral.referral_date_time, 

1184 CPFTLPSReferral.referral_priority, 

1185 p1.c.surname, 

1186 p1.c.forename, 

1187 p1.c.dob, 

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

1189 CPFTLPSReferral.patient_location, 

1190 ] 

1191 # noinspection PyUnresolvedReferences 

1192 select_from = p1.join( 

1193 CPFTLPSReferral.__table__, 

1194 and_( 

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

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

1197 CPFTLPSReferral._device_id == p1.c._device_id, 

1198 CPFTLPSReferral._era == p1.c._era, 

1199 CPFTLPSReferral._current == True, # noqa: E712 

1200 ), 

1201 ) 

1202 select_from = select_from.join( 

1203 i1, 

1204 and_( 

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

1206 i1.c._device_id == p1.c._device_id, 

1207 i1.c._era == p1.c._era, 

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

1209 ), 

1210 ) # nopep8 

1211 wheres = [i1.c.which_idnum == which_idnum] 

1212 if not req.user.superuser: 

1213 # Restrict to accessible groups 

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

1215 

1216 # Step 2: not yet discharged 

1217 # noinspection PyUnresolvedReferences 

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

1219 # noinspection PyUnresolvedReferences 

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

1221 # noinspection PyUnresolvedReferences 

1222 discharge = ( 

1223 select("*") 

1224 .select_from( 

1225 p2.join( 

1226 CPFTLPSDischarge.__table__, 

1227 and_( 

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

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

1230 CPFTLPSDischarge._device_id == p2.c._device_id, 

1231 CPFTLPSDischarge._era == p2.c._era, 

1232 CPFTLPSDischarge._current == True, # noqa: E712 

1233 ), 

1234 ).join( 

1235 i2, 

1236 and_( 

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

1238 i2.c._device_id == p2.c._device_id, 

1239 i2.c._era == p2.c._era, 

1240 i2.c._current == True, # noqa: E712 

1241 ), 

1242 ) 

1243 ) 

1244 .where( 

1245 and_( 

1246 # Link on ID to main query: same patient 

1247 i2.c.which_idnum == which_idnum, 

1248 i2.c.idnum_value == i1.c.idnum_value, 

1249 # Discharge later than referral 

1250 ( 

1251 CPFTLPSDischarge.discharge_date 

1252 >= CPFTLPSReferral.referral_date_time 

1253 ), 

1254 ) 

1255 ) 

1256 ) # nopep8 

1257 if not req.user.superuser: 

1258 # Restrict to accessible groups 

1259 discharge = discharge.where( 

1260 CPFTLPSDischarge._group_id.in_(group_ids) 

1261 ) 

1262 wheres.append(~exists(discharge)) 

1263 

1264 # Step 3: not yet clerked 

1265 # noinspection PyUnresolvedReferences 

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

1267 # noinspection PyUnresolvedReferences 

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

1269 # noinspection PyUnresolvedReferences 

1270 clerking = ( 

1271 select("*") 

1272 .select_from( 

1273 p3.join( 

1274 PsychiatricClerking.__table__, 

1275 and_( 

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

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

1278 PsychiatricClerking._device_id == p3.c._device_id, 

1279 PsychiatricClerking._era == p3.c._era, 

1280 PsychiatricClerking._current == True, # noqa: E712 

1281 ), 

1282 ).join( 

1283 i3, 

1284 and_( 

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

1286 i3.c._device_id == p3.c._device_id, 

1287 i3.c._era == p3.c._era, 

1288 i3.c._current == True, # noqa: E712 

1289 ), 

1290 ) 

1291 ) 

1292 .where( 

1293 and_( 

1294 # Link on ID to main query: same patient 

1295 i3.c.which_idnum == which_idnum, 

1296 i3.c.idnum_value == i1.c.idnum_value, 

1297 # Discharge later than referral 

1298 ( 

1299 PsychiatricClerking.when_created 

1300 >= CPFTLPSReferral.referral_date_time 

1301 ), 

1302 ) 

1303 ) 

1304 ) # nopep8 

1305 if not req.user.superuser: 

1306 # Restrict to accessible groups 

1307 clerking = clerking.where( 

1308 PsychiatricClerking._group_id.in_(group_ids) 

1309 ) 

1310 wheres.append(~exists(clerking)) 

1311 

1312 # Finish up 

1313 order_by = [ 

1314 CPFTLPSReferral.lps_division, 

1315 CPFTLPSReferral.referral_date_time, 

1316 CPFTLPSReferral.referral_priority, 

1317 ] 

1318 query = ( 

1319 select(*select_fields) 

1320 .select_from(select_from) 

1321 .where(and_(*wheres)) 

1322 .order_by(*order_by) 

1323 ) 

1324 return query