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/gbo.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 

27Goal-Based Outcomes tasks. 

28 

29- By Joe Kearney, Rudolf Cardinal. 

30 

31""" 

32 

33from typing import List 

34 

35from cardinal_pythonlib.datetimefunc import format_datetime 

36from sqlalchemy import Column 

37from sqlalchemy.sql.sqltypes import Boolean, Integer, Date, UnicodeText 

38 

39from camcops_server.cc_modules.cc_constants import CssClass, DateFormat 

40from camcops_server.cc_modules.cc_html import tr_qa, answer 

41from camcops_server.cc_modules.cc_request import CamcopsRequest 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import ( 

44 Task, 

45 TaskHasPatientMixin, 

46) 

47from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

48 

49 

50# ============================================================================= 

51# Common GBO constants 

52# ============================================================================= 

53 

54AGENT_PATIENT = 1 

55AGENT_PARENT_CARER = 2 

56AGENT_CLINICIAN = 3 

57AGENT_OTHER = 4 

58 

59AGENT_STRING_MAP = { 

60 AGENT_PATIENT: "Patient/service user", # in original: "Child/young person" 

61 AGENT_PARENT_CARER: "Parent/carer", 

62 AGENT_CLINICIAN: "Practitioner/clinician", 

63 AGENT_OTHER: "Other: " 

64} 

65UNKNOWN_AGENT = "Unknown" 

66 

67PROGRESS_COMMENT_SUFFIX = " (0 no progress - 10 reached fully)" 

68 

69 

70def agent_description(agent: int, other_detail: str) -> str: 

71 who = AGENT_STRING_MAP.get(agent, UNKNOWN_AGENT) 

72 if agent == AGENT_OTHER: 

73 who += other_detail or "?" 

74 return who 

75 

76 

77# ============================================================================= 

78# GBO-GReS 

79# ============================================================================= 

80 

81class Gbogres(TaskHasPatientMixin, Task): 

82 """ 

83 Server implementation of the GBO - Goal Record Sheet task. 

84 """ 

85 __tablename__ = "gbogres" 

86 shortname = "GBO-GReS" 

87 extrastring_taskname = "gbo" 

88 

89 FN_DATE = "date" # NB SQL keyword too; doesn't matter 

90 FN_GOAL_1_DESC = "goal_1_description" 

91 FN_GOAL_2_DESC = "goal_2_description" 

92 FN_GOAL_3_DESC = "goal_3_description" 

93 FN_GOAL_OTHER = "other_goals" 

94 FN_COMPLETED_BY = "completed_by" 

95 FN_COMPLETED_BY_OTHER = "completed_by_other" 

96 

97 REQUIRED_FIELDS = [FN_DATE, FN_GOAL_1_DESC, FN_COMPLETED_BY] 

98 

99 date = Column(FN_DATE, Date, comment="Date of goal-setting") 

100 goal_1_description = Column( 

101 FN_GOAL_1_DESC, UnicodeText, 

102 comment="Goal 1 description") 

103 goal_2_description = Column( 

104 FN_GOAL_2_DESC, UnicodeText, 

105 comment="Goal 2 description") 

106 goal_3_description = Column( 

107 FN_GOAL_3_DESC, UnicodeText, 

108 comment="Goal 3 description") 

109 other_goals = Column( 

110 FN_GOAL_OTHER, UnicodeText, 

111 comment="Other/additional goal description(s)") 

112 completed_by = Column( 

113 FN_COMPLETED_BY, Integer, 

114 comment="Who completed the form ({})".format( 

115 "; ".join(f"{k} = {v}" 

116 for k, v in AGENT_STRING_MAP.items()) 

117 ) 

118 ) 

119 completed_by_other = Column( 

120 FN_COMPLETED_BY_OTHER, UnicodeText, 

121 comment="If completed by 'other', who?") 

122 

123 @staticmethod 

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

125 _ = req.gettext 

126 return _("Goal-Based Outcomes – 1 – Goal Record Sheet") 

127 

128 def get_n_core_goals(self) -> int: 

129 """ 

130 Returns the number of non-blank core (1-3) goals. 

131 """ 

132 return len(list(filter( 

133 None, 

134 [self.goal_1_description, self.goal_2_description, 

135 self.goal_3_description]))) 

136 

137 def goals_set_tr(self) -> str: 

138 extra = " (additional goals specified)" if self.other_goals else "" 

139 return tr_qa("Number of goals set", 

140 f"{self.get_n_core_goals()}{extra}") 

141 

142 def completed_by_tr(self) -> str: 

143 who = agent_description(self.completed_by, self.completed_by_other) 

144 return tr_qa("Completed by", who) 

145 

146 def get_date_tr(self) -> str: 

147 return tr_qa("Date", format_datetime(self.date, 

148 DateFormat.SHORT_DATE, 

149 default=None)) 

150 

151 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

152 return self.standard_task_summary_fields() 

153 

154 def is_complete(self) -> bool: 

155 if self.any_fields_none(self.REQUIRED_FIELDS): 

156 return False 

157 if self.completed_by == AGENT_OTHER and not self.completed_by_other: 

158 return False 

159 return True 

160 

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

162 return f""" 

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

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

165 {self.get_is_complete_tr(req)} 

166 {self.get_date_tr()} 

167 {self.completed_by_tr()} 

168 {self.goals_set_tr()} 

169 </table> 

170 </div> 

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

172 <tr> 

173 <th width="15%">Goal number</th> 

174 <th width="85%">Goal description</th> 

175 </tr> 

176 <tr><td>1</td><td>{answer(self.goal_1_description, 

177 default="")}</td></tr> 

178 <tr><td>2</td><td>{answer(self.goal_2_description, 

179 default="")}</td></tr> 

180 <tr><td>3</td><td>{answer(self.goal_3_description, 

181 default="")}</td></tr> 

182 <tr><td>Other</td><td>{answer(self.other_goals, 

183 default="")}</td></tr> 

184 </table> 

185 """ 

186 

187 

188# ============================================================================= 

189# GBO-GPC 

190# ============================================================================= 

191 

192class Gbogpc(TaskHasPatientMixin, Task): 

193 """ 

194 Server implementation of the GBO-GPC task. 

195 """ 

196 __tablename__ = "gbogpc" 

197 shortname = "GBO-GPC" 

198 extrastring_taskname = "gbo" 

199 provides_trackers = True 

200 

201 FN_DATE = "date" # NB SQL keyword too; doesn't matter 

202 FN_SESSION = "session" 

203 FN_GOAL_NUMBER = "goal_number" 

204 FN_GOAL_DESCRIPTION = "goal_description" 

205 FN_PROGRESS = "progress" 

206 FN_WHOSE_GOAL = "whose_goal" 

207 FN_WHOSE_GOAL_OTHER = "whose_goal_other" 

208 

209 date = Column(FN_DATE, Date, comment="Session date") 

210 session = Column(FN_SESSION, Integer, comment="Session number") 

211 goal_number = Column(FN_GOAL_NUMBER, Integer, comment="Goal number (1-3)") 

212 goal_text = Column( 

213 FN_GOAL_DESCRIPTION, UnicodeText, 

214 comment="Brief description of the goal") 

215 progress = Column( 

216 FN_PROGRESS, Integer, 

217 comment="Progress towards goal" + PROGRESS_COMMENT_SUFFIX 

218 ) 

219 whose_goal = Column( 

220 FN_WHOSE_GOAL, Integer, 

221 comment="Whose goal is this ({})".format( 

222 "; ".join(f"{k} = {v}" 

223 for k, v in AGENT_STRING_MAP.items()) 

224 ) 

225 ) 

226 whose_goal_other = Column( 

227 FN_WHOSE_GOAL_OTHER, UnicodeText, 

228 comment="If 'whose goal' is 'other', who?") 

229 

230 REQUIRED_FIELDS = [ 

231 FN_DATE, FN_SESSION, FN_GOAL_NUMBER, FN_PROGRESS, FN_WHOSE_GOAL 

232 ] 

233 

234 @staticmethod 

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

236 _ = req.gettext 

237 return _("Goal-Based Outcomes – 2 – Goal Progress Chart") 

238 

239 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

240 return self.standard_task_summary_fields() 

241 

242 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: 

243 axis_min = -0.5 

244 axis_max = 10.5 

245 hlines = [0, 5, 10] 

246 axis_label = "Progress towards goal (0-10)" 

247 title_start = "GBO Goal Progress Chart – Goal " 

248 return [ 

249 TrackerInfo( 

250 value=self.progress if self.goal_number == 1 else None, 

251 plot_label=title_start + "1", 

252 axis_label=axis_label, 

253 axis_min=axis_min, 

254 axis_max=axis_max, 

255 horizontal_lines=hlines 

256 ), 

257 TrackerInfo( 

258 value=self.progress if self.goal_number == 2 else None, 

259 plot_label=title_start + "2", 

260 axis_label=axis_label, 

261 axis_min=axis_min, 

262 axis_max=axis_max, 

263 horizontal_lines=hlines 

264 ), 

265 TrackerInfo( 

266 value=self.progress if self.goal_number == 3 else None, 

267 plot_label=title_start + "3", 

268 axis_label=axis_label, 

269 axis_min=axis_min, 

270 axis_max=axis_max, 

271 horizontal_lines=hlines 

272 ), 

273 ] 

274 

275 def is_complete(self) -> bool: 

276 if self.any_fields_none(self.REQUIRED_FIELDS): 

277 return False 

278 if self.whose_goal == AGENT_OTHER and not self.whose_goal_other: 

279 return False 

280 return True 

281 

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

283 return f""" 

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

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

286 {self.get_is_complete_tr(req)} 

287 </table> 

288 </div> 

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

290 <tr> 

291 <th width="30%">Date</th> 

292 <td width="70%">{ 

293 answer(format_datetime(self.date, DateFormat.SHORT_DATE, 

294 default=None))}</td> 

295 </tr> 

296 <tr> 

297 <th>Session number</th> 

298 <td>{answer(self.session)}</td> 

299 </tr> 

300 <tr> 

301 <th>Goal number</th> 

302 <td>{answer(self.goal_number)}</td> 

303 </tr> 

304 <tr> 

305 <th>Goal description</th> 

306 <td>{answer(self.goal_text)}</td> 

307 </tr> 

308 <tr> 

309 <th>Progress <sup>[1]</sup></th> 

310 <td>{answer(self.progress)}</td> 

311 </tr> 

312 <tr> 

313 <th>Whose goal is this?</th> 

314 <td>{answer(agent_description(self.whose_goal, 

315 self.whose_goal_other))}</td> 

316 </tr> 

317 </table> 

318 <div class="{CssClass.FOOTNOTES}"> 

319 [1] {self.wxstring(req, "progress_explanation")} 

320 </div> 

321 """ 

322 

323 

324# ============================================================================= 

325# GBO-GRaS 

326# ============================================================================= 

327 

328class Gbogras(TaskHasPatientMixin, Task): 

329 """ 

330 Server implementation of the GBO-GRaS task. 

331 """ 

332 __tablename__ = "gbogras" 

333 shortname = "GBO-GRaS" 

334 extrastring_taskname = "gbo" 

335 provides_trackers = True 

336 

337 FN_DATE = "date" # NB SQL keyword too; doesn't matter 

338 FN_RATE_GOAL_1 = "rate_goal_1" 

339 FN_RATE_GOAL_2 = "rate_goal_2" 

340 FN_RATE_GOAL_3 = "rate_goal_3" 

341 FN_GOAL_1_DESC = "goal_1_description" 

342 FN_GOAL_2_DESC = "goal_2_description" 

343 FN_GOAL_3_DESC = "goal_3_description" 

344 FN_GOAL_1_PROGRESS = "goal_1_progress" 

345 FN_GOAL_2_PROGRESS = "goal_2_progress" 

346 FN_GOAL_3_PROGRESS = "goal_3_progress" 

347 FN_COMPLETED_BY = "completed_by" 

348 FN_COMPLETED_BY_OTHER = "completed_by_other" 

349 

350 date = Column(FN_DATE, Date, comment="Date of ratings") 

351 # ... NB SQL keyword too; doesn't matter 

352 rate_goal_1 = Column(FN_RATE_GOAL_1, Boolean, comment="Rate goal 1?") 

353 rate_goal_2 = Column(FN_RATE_GOAL_2, Boolean, comment="Rate goal 2?") 

354 rate_goal_3 = Column(FN_RATE_GOAL_3, Boolean, comment="Rate goal 3?") 

355 goal_1_description = Column( 

356 FN_GOAL_1_DESC, UnicodeText, 

357 comment="Goal 1 description") 

358 goal_2_description = Column( 

359 FN_GOAL_2_DESC, UnicodeText, 

360 comment="Goal 2 description") 

361 goal_3_description = Column( 

362 FN_GOAL_3_DESC, UnicodeText, 

363 comment="Goal 3 description") 

364 goal_1_progress = Column( 

365 FN_GOAL_1_PROGRESS, Integer, 

366 comment="Goal 1 progress" + PROGRESS_COMMENT_SUFFIX) 

367 goal_2_progress = Column( 

368 FN_GOAL_2_PROGRESS, Integer, 

369 comment="Goal 2 progress" + PROGRESS_COMMENT_SUFFIX) 

370 goal_3_progress = Column( 

371 FN_GOAL_3_PROGRESS, Integer, 

372 comment="Goal 3 progress" + PROGRESS_COMMENT_SUFFIX) 

373 completed_by = Column( 

374 FN_COMPLETED_BY, Integer, 

375 comment="Who completed the form ({})".format( 

376 "; ".join(f"{k} = {v}" 

377 for k, v in AGENT_STRING_MAP.items() 

378 if k != AGENT_CLINICIAN) 

379 ) 

380 ) 

381 completed_by_other = Column( 

382 FN_COMPLETED_BY_OTHER, UnicodeText, 

383 comment="If completed by 'other', who?") 

384 

385 REQUIRED_FIELDS = [FN_DATE, FN_COMPLETED_BY] 

386 GOAL_TUPLES = ( 

387 # goalnum, rate it?, goal description, progress 

388 (1, FN_RATE_GOAL_1, FN_GOAL_1_DESC, FN_GOAL_1_PROGRESS), 

389 (2, FN_RATE_GOAL_2, FN_GOAL_2_DESC, FN_GOAL_2_PROGRESS), 

390 (3, FN_RATE_GOAL_3, FN_GOAL_3_DESC, FN_GOAL_3_PROGRESS), 

391 ) 

392 

393 @staticmethod 

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

395 _ = req.gettext 

396 return _("Goal-Based Outcomes – 3 – Goal Rating Sheet") 

397 

398 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

399 return self.standard_task_summary_fields() 

400 

401 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: 

402 axis_min = -0.5 

403 axis_max = 10.5 

404 hlines = [0, 5, 10] 

405 axis_label = "Progress towards goal (0-10)" 

406 title_start = "GBO Goal Rating Sheet – Goal " 

407 return [ 

408 TrackerInfo( 

409 value=self.goal_1_progress if self.rate_goal_1 else None, 

410 plot_label=title_start + "1", 

411 axis_label=axis_label, 

412 axis_min=axis_min, 

413 axis_max=axis_max, 

414 horizontal_lines=hlines 

415 ), 

416 TrackerInfo( 

417 value=self.goal_2_progress if self.rate_goal_2 else None, 

418 plot_label=title_start + "2", 

419 axis_label=axis_label, 

420 axis_min=axis_min, 

421 axis_max=axis_max, 

422 horizontal_lines=hlines 

423 ), 

424 TrackerInfo( 

425 value=self.goal_3_progress if self.rate_goal_3 else None, 

426 plot_label=title_start + "3", 

427 axis_label=axis_label, 

428 axis_min=axis_min, 

429 axis_max=axis_max, 

430 horizontal_lines=hlines 

431 ), 

432 ] 

433 

434 def is_complete(self) -> bool: 

435 if self.any_fields_none(self.REQUIRED_FIELDS): 

436 return False 

437 if self.completed_by == AGENT_OTHER and not self.completed_by_other: 

438 return False 

439 n_goals_completed = 0 

440 for _, rate_attr, desc_attr, prog_attr in self.GOAL_TUPLES: 

441 if getattr(self, rate_attr): 

442 n_goals_completed += 1 

443 if not getattr(self, desc_attr) or not getattr(self, prog_attr): # noqa 

444 return False 

445 return n_goals_completed > 0 

446 

447 def completed_by_tr(self) -> str: 

448 who = agent_description(self.completed_by, self.completed_by_other) 

449 return tr_qa("Completed by", who) 

450 

451 def get_date_tr(self) -> str: 

452 return tr_qa("Date", format_datetime(self.date, 

453 DateFormat.SHORT_DATE, 

454 default=None)) 

455 

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

457 rows = [] # type: List[str] 

458 for goalnum, rate_attr, desc_attr, prog_attr in self.GOAL_TUPLES: 

459 if getattr(self, rate_attr): 

460 rows.append(f""" 

461 <tr> 

462 <td>{answer(goalnum)}</td> 

463 <td>{answer(getattr(self, desc_attr))}</td> 

464 <td>{answer(getattr(self, prog_attr))}</td> 

465 </tr> 

466 """) 

467 newline = "\n" 

468 return f""" 

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

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

471 {self.get_is_complete_tr(req)} 

472 {self.get_date_tr()} 

473 {self.completed_by_tr()} 

474 </table> 

475 </div> 

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

477 <tr> 

478 <th width="15%">Goal number</th> 

479 <th width="70%">Description</th> 

480 <th width="15%">Progress <sup>[1]</sup></th> 

481 </tr> 

482 {newline.join(rows)} 

483 </table> 

484 <div class="{CssClass.FOOTNOTES}"> 

485 [1] {self.wxstring(req, "progress_explanation")} 

486 </div> 

487 """