Coverage for tasks/khandaker_mojo_medicationtherapy.py: 62%

93 statements  

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

1""" 

2camcops_server/tasks/khandaker_mojo_medicationtherapy.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 

28from typing import List, Optional, Type, TYPE_CHECKING 

29 

30from sqlalchemy.orm import Mapped, mapped_column 

31from sqlalchemy.sql.sqltypes import UnicodeText 

32 

33from camcops_server.cc_modules.cc_constants import CssClass 

34from camcops_server.cc_modules.cc_db import ( 

35 ancillary_relationship, 

36 GenericTabletRecordMixin, 

37 TaskDescendant, 

38) 

39from camcops_server.cc_modules.cc_html import answer, tr_qa 

40from camcops_server.cc_modules.cc_sqlalchemy import Base 

41from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin 

42 

43if TYPE_CHECKING: 

44 from camcops_server.cc_modules.cc_request import CamcopsRequest 

45 

46 

47class KhandakerMojoTableItem(GenericTabletRecordMixin, TaskDescendant, Base): 

48 __abstract__ = True 

49 

50 def any_fields_none(self) -> bool: 

51 for f in self.mandatory_fields(): 

52 if getattr(self, f) is None: 

53 return True 

54 return False 

55 

56 @classmethod 

57 def mandatory_fields(cls) -> List[str]: 

58 raise NotImplementedError 

59 

60 def get_response_option(self, req: "CamcopsRequest") -> Optional[str]: 

61 # Reads "self.response" from derived class. 

62 # noinspection PyUnresolvedReferences 

63 response = self.response # type: ignore[attr-defined] 

64 if response is None: 

65 return None 

66 return self.task_ancestor().xstring(req, f"response_{response}") 

67 

68 # ------------------------------------------------------------------------- 

69 # TaskDescendant overrides 

70 # ------------------------------------------------------------------------- 

71 

72 @classmethod 

73 def task_ancestor_class(cls) -> Optional[Type["Task"]]: 

74 return KhandakerMojoMedicationTherapy 

75 

76 def task_ancestor(self) -> Optional["KhandakerMojoMedicationTherapy"]: 

77 # Reads "self.medicationtable_id" from derived class. 

78 # noinspection PyUnresolvedReferences 

79 return KhandakerMojoMedicationTherapy.get_linked( # type: ignore[return-value] # noqa: E501 

80 self.medicationtable_id, self # type: ignore[attr-defined] 

81 ) 

82 

83 

84class KhandakerMojoMedicationItem(KhandakerMojoTableItem): 

85 __tablename__ = "khandaker_mojo_medication_item" 

86 

87 medicationtable_id: Mapped[int] = mapped_column( 

88 comment="FK to medicationtable", 

89 ) 

90 seqnum: Mapped[int] = mapped_column( 

91 comment="Sequence number of this medication", 

92 ) 

93 brand_name: Mapped[Optional[str]] = mapped_column( 

94 UnicodeText, comment="Brand name" 

95 ) 

96 chemical_name: Mapped[Optional[str]] = mapped_column( 

97 UnicodeText, comment="Chemical name for study team" 

98 ) 

99 dose: Mapped[Optional[str]] = mapped_column(UnicodeText, comment="Dose") 

100 frequency: Mapped[Optional[str]] = mapped_column( 

101 UnicodeText, comment="Frequency" 

102 ) 

103 duration_months: Mapped[Optional[float]] = mapped_column( 

104 comment="Duration (months)" 

105 ) 

106 indication: Mapped[Optional[str]] = mapped_column( 

107 UnicodeText, 

108 comment="Indication (what is the medication used for?)", 

109 ) 

110 response: Mapped[Optional[int]] = mapped_column( 

111 comment=( 

112 "1 = treats all symptoms, " 

113 "2 = most symptoms, " 

114 "3 = some symptoms, " 

115 "4 = no symptoms)" 

116 ), 

117 ) 

118 

119 @classmethod 

120 def mandatory_fields(cls) -> List[str]: 

121 return [ 

122 "brand_name", 

123 "chemical_name", 

124 "dose", 

125 "frequency", 

126 "duration_months", 

127 "indication", 

128 "response", 

129 ] 

130 

131 def get_html_table_row(self, req: "CamcopsRequest") -> str: 

132 return f""" 

133 <tr> 

134 <td>{answer(self.chemical_name)}</td> 

135 <td>{answer(self.brand_name)}</td> 

136 <td>{answer(self.dose)}</td> 

137 <td>{answer(self.frequency)}</td> 

138 <td>{answer(self.duration_months)}</td> 

139 <td>{answer(self.indication)}</td> 

140 <td>{answer(self.get_response_option(req))}</td> 

141 </tr> 

142 """ 

143 

144 

145class KhandakerMojoTherapyItem(KhandakerMojoTableItem): 

146 __tablename__ = "khandaker_mojo_therapy_item" 

147 

148 medicationtable_id: Mapped[int] = mapped_column( 

149 comment="FK to medicationtable", 

150 ) 

151 seqnum: Mapped[int] = mapped_column( 

152 comment="Sequence number of this therapy", 

153 ) 

154 therapy: Mapped[Optional[str]] = mapped_column( 

155 UnicodeText, comment="Therapy" 

156 ) 

157 frequency: Mapped[Optional[str]] = mapped_column( 

158 UnicodeText, comment="Frequency" 

159 ) 

160 sessions_completed: Mapped[Optional[int]] = mapped_column( 

161 comment="Sessions completed" 

162 ) 

163 sessions_planned: Mapped[Optional[int]] = mapped_column( 

164 comment="Sessions planned" 

165 ) 

166 indication: Mapped[Optional[str]] = mapped_column( 

167 UnicodeText, 

168 comment="Indication (what is the medication used for?)", 

169 ) 

170 response: Mapped[Optional[int]] = mapped_column( 

171 comment=( 

172 "1 = treats all symptoms, " 

173 "2 = most symptoms, " 

174 "3 = some symptoms, " 

175 "4 = no symptoms)" 

176 ), 

177 ) 

178 

179 @classmethod 

180 def mandatory_fields(cls) -> List[str]: 

181 return [ 

182 "therapy", 

183 "frequency", 

184 "sessions_completed", 

185 "sessions_planned", 

186 "indication", 

187 "response", 

188 ] 

189 

190 def get_html_table_row(self, req: "CamcopsRequest") -> str: 

191 return f""" 

192 <tr> 

193 <td>{answer(self.therapy)}</td> 

194 <td>{answer(self.frequency)}</td> 

195 <td>{answer(self.sessions_completed)}</td> 

196 <td>{answer(self.sessions_planned)}</td> 

197 <td>{answer(self.indication)}</td> 

198 <td>{answer(self.get_response_option(req))}</td> 

199 </tr> 

200 """ 

201 

202 

203class KhandakerMojoMedicationTherapy(TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501 

204 """ 

205 Server implementation of the KhandakerMojoMedicationTherapy task 

206 """ 

207 

208 __tablename__ = "khandaker_mojo_medicationtherapy" 

209 shortname = "Khandaker_MOJO_MedicationTherapy" 

210 info_filename_stem = "khandaker_mojo" 

211 provides_trackers = False 

212 

213 medication_items = ancillary_relationship( # type: ignore[assignment] 

214 parent_class_name="KhandakerMojoMedicationTherapy", 

215 ancillary_class_name="KhandakerMojoMedicationItem", 

216 ancillary_fk_to_parent_attr_name="medicationtable_id", 

217 ancillary_order_by_attr_name="seqnum", 

218 ) # type: List[KhandakerMojoMedicationItem] 

219 

220 therapy_items = ancillary_relationship( # type: ignore[assignment] 

221 parent_class_name="KhandakerMojoMedicationTherapy", 

222 ancillary_class_name="KhandakerMojoTherapyItem", 

223 ancillary_fk_to_parent_attr_name="medicationtable_id", 

224 ancillary_order_by_attr_name="seqnum", 

225 ) # type: List[KhandakerMojoTherapyItem] 

226 

227 @staticmethod 

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

229 _ = req.gettext 

230 return _("Khandaker GM — MOJO — Medications and therapies") 

231 

232 def is_complete(self) -> bool: 

233 # Whilst it's almost certain that anyone completing this task would be 

234 # on some kind of medication, we have no way of knowing when all 

235 # medication has been added to the table 

236 for item in self.medication_items: 

237 if item.any_fields_none(): 

238 return False 

239 

240 for item in self.therapy_items: 

241 if item.any_fields_none(): 

242 return False 

243 

244 return True 

245 

246 def get_num_medication_items(self) -> int: 

247 return len(self.medication_items) 

248 

249 def get_num_therapy_items(self) -> int: 

250 return len(self.therapy_items) 

251 

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

253 html = f""" 

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

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

256 {self.get_is_complete_tr(req)} 

257 {tr_qa("Number of medications", 

258 self.get_num_medication_items())} 

259 {tr_qa("Number of therapies", 

260 self.get_num_therapy_items())} 

261 </table> 

262 </div> 

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

264 <tr> 

265 <th>{self.xstring(req, "chemical_name")}</th> 

266 <th>{self.xstring(req, "brand_name")}</th> 

267 <th>{self.xstring(req, "dose")}</th> 

268 <th>{self.xstring(req, "frequency")}</th> 

269 <th>{self.xstring(req, "duration_months")}</th> 

270 <th>{self.xstring(req, "indication")}</th> 

271 <th>{self.xstring(req, "response")}</th> 

272 </tr> 

273 """ 

274 for item in self.medication_items: 

275 html += item.get_html_table_row(req) 

276 

277 html += f""" 

278 </table> 

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

280 <tr> 

281 <th>{self.xstring(req, "therapy")}</th> 

282 <th>{self.xstring(req, "frequency")}</th> 

283 <th>{self.xstring(req, "sessions_completed")}</th> 

284 <th>{self.xstring(req, "sessions_planned")}</th> 

285 <th>{self.xstring(req, "indication")}</th> 

286 <th>{self.xstring(req, "response")}</th> 

287 </tr> 

288 """ 

289 

290 for item in self.therapy_items: 

291 html += item.get_html_table_row(req) 

292 

293 html += """ 

294 </table> 

295 """ 

296 

297 return html