Coverage for tasks/khandaker_mojo_medicationtherapy.py: 62%

1"""

2 camcops_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

28 from typing import List, Optional, Type, TYPE_CHECKING

29

30 from sqlalchemy.orm import Mapped, mapped_column

31 from sqlalchemy.sql.sqltypes import UnicodeText

32

33 from camcops_server.cc_modules.cc_constants import CssClass

34 from camcops_server.cc_modules.cc_db import (

35 ancillary_relationship,

36 GenericTabletRecordMixin,

37 TaskDescendant,

38)

39 from camcops_server.cc_modules.cc_html import answer, tr_qa

40 from camcops_server.cc_modules.cc_sqlalchemy import Base

41 from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin

42

43 if TYPE_CHECKING:

44 from camcops_server.cc_modules.cc_request import CamcopsRequest

45

46

47 class 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

84 class 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

145 class 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

203 class 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