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
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/khandaker_mojo_medicationtherapy.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28from typing import List, Optional, Type, TYPE_CHECKING
30from sqlalchemy.orm import Mapped, mapped_column
31from sqlalchemy.sql.sqltypes import UnicodeText
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
43if TYPE_CHECKING:
44 from camcops_server.cc_modules.cc_request import CamcopsRequest
47class KhandakerMojoTableItem(GenericTabletRecordMixin, TaskDescendant, Base):
48 __abstract__ = True
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
56 @classmethod
57 def mandatory_fields(cls) -> List[str]:
58 raise NotImplementedError
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}")
68 # -------------------------------------------------------------------------
69 # TaskDescendant overrides
70 # -------------------------------------------------------------------------
72 @classmethod
73 def task_ancestor_class(cls) -> Optional[Type["Task"]]:
74 return KhandakerMojoMedicationTherapy
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 )
84class KhandakerMojoMedicationItem(KhandakerMojoTableItem):
85 __tablename__ = "khandaker_mojo_medication_item"
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 )
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 ]
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 """
145class KhandakerMojoTherapyItem(KhandakerMojoTableItem):
146 __tablename__ = "khandaker_mojo_therapy_item"
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 )
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 ]
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 """
203class KhandakerMojoMedicationTherapy(TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501
204 """
205 Server implementation of the KhandakerMojoMedicationTherapy task
206 """
208 __tablename__ = "khandaker_mojo_medicationtherapy"
209 shortname = "Khandaker_MOJO_MedicationTherapy"
210 info_filename_stem = "khandaker_mojo"
211 provides_trackers = False
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]
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]
227 @staticmethod
228 def longname(req: "CamcopsRequest") -> str:
229 _ = req.gettext
230 return _("Khandaker GM — MOJO — Medications and therapies")
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
240 for item in self.therapy_items:
241 if item.any_fields_none():
242 return False
244 return True
246 def get_num_medication_items(self) -> int:
247 return len(self.medication_items)
249 def get_num_therapy_items(self) -> int:
250 return len(self.therapy_items)
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)
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 """
290 for item in self.therapy_items:
291 html += item.get_html_table_row(req)
293 html += """
294 </table>
295 """
297 return html