Coverage for tasks/qolsg.py: 60%
67 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/tasks/qolsg.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28"""
30from typing import List
32import cardinal_pythonlib.rnc_web as ws
33from sqlalchemy.sql.schema import Column
34from sqlalchemy.sql.sqltypes import Float, Integer, String
36from camcops_server.cc_modules.cc_constants import CssClass
37from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
38from camcops_server.cc_modules.cc_html import get_yes_no_none, tr_qa
39from camcops_server.cc_modules.cc_request import CamcopsRequest
40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
41from camcops_server.cc_modules.cc_sqla_coltypes import (
42 BIT_CHECKER,
43 CamcopsColumn,
44 PendulumDateTimeAsIsoTextColType,
45 ZERO_TO_ONE_CHECKER,
46)
47from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
48from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
51# =============================================================================
52# QoL-SG
53# =============================================================================
55DP = 3
58class QolSG(TaskHasPatientMixin, Task):
59 """
60 Server implementation of the QoL-SG task.
61 """
63 __tablename__ = "qolsg"
64 shortname = "QoL-SG"
65 info_filename_stem = "qol"
66 provides_trackers = True
68 category_start_time = Column(
69 "category_start_time",
70 PendulumDateTimeAsIsoTextColType,
71 comment="Time categories were offered (ISO-8601)",
72 )
73 category_responded = CamcopsColumn(
74 "category_responded",
75 Integer,
76 permitted_value_checker=BIT_CHECKER,
77 comment="Responded to category choice? (0 no, 1 yes)",
78 )
79 category_response_time = Column(
80 "category_response_time",
81 PendulumDateTimeAsIsoTextColType,
82 comment="Time category was chosen (ISO-8601)",
83 )
84 category_chosen = Column(
85 "category_chosen",
86 String(length=len("medium")),
87 comment="Category chosen: high (QoL > 1) "
88 "medium (0 <= QoL <= 1) low (QoL < 0)",
89 )
90 gamble_fixed_option = Column(
91 "gamble_fixed_option",
92 String(length=len("current")),
93 comment="Fixed option in gamble (current, healthy, dead)",
94 )
95 gamble_lottery_option_p = Column(
96 "gamble_lottery_option_p",
97 String(length=len("current")),
98 comment="Gamble: option corresponding to p "
99 "(current, healthy, dead)",
100 )
101 gamble_lottery_option_q = Column(
102 "gamble_lottery_option_q",
103 String(length=len("current")),
104 comment="Gamble: option corresponding to q "
105 "(current, healthy, dead) (q = 1 - p)",
106 )
107 gamble_lottery_on_left = CamcopsColumn(
108 "gamble_lottery_on_left",
109 Integer,
110 permitted_value_checker=BIT_CHECKER,
111 comment="Gamble: lottery shown on the left (0 no, 1 yes)",
112 )
113 gamble_starting_p = CamcopsColumn(
114 "gamble_starting_p",
115 Float,
116 permitted_value_checker=ZERO_TO_ONE_CHECKER,
117 comment="Gamble: starting value of p",
118 )
119 gamble_start_time = Column(
120 "gamble_start_time",
121 PendulumDateTimeAsIsoTextColType,
122 comment="Time gamble was offered (ISO-8601)",
123 )
124 gamble_responded = CamcopsColumn(
125 "gamble_responded",
126 Integer,
127 permitted_value_checker=BIT_CHECKER,
128 comment="Gamble was responded to? (0 no, 1 yes)",
129 )
130 gamble_response_time = Column(
131 "gamble_response_time",
132 PendulumDateTimeAsIsoTextColType,
133 comment="Time subject responded to gamble (ISO-8601)",
134 )
135 gamble_p = CamcopsColumn(
136 "gamble_p",
137 Float,
138 permitted_value_checker=ZERO_TO_ONE_CHECKER,
139 comment="Final value of p",
140 )
141 utility = Column("utility", Float, comment="Calculated utility, h")
143 @staticmethod
144 def longname(req: "CamcopsRequest") -> str:
145 _ = req.gettext
146 return _("Quality of Life: Standard Gamble")
148 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
149 return [
150 TrackerInfo(
151 value=self.utility,
152 plot_label="Quality of life: standard gamble",
153 axis_label="QoL (0-1)",
154 axis_min=0,
155 axis_max=1,
156 )
157 ]
159 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
160 if not self.is_complete():
161 return CTV_INCOMPLETE
162 return [
163 CtvInfo(
164 content=f"Quality of life: {ws.number_to_dp(self.utility, DP)}"
165 )
166 ]
168 def is_complete(self) -> bool:
169 return self.utility is not None and self.field_contents_valid()
171 def get_task_html(self, req: CamcopsRequest) -> str:
172 h = f"""
173 <div class="{CssClass.SUMMARY}">
174 <table class="{CssClass.SUMMARY}">
175 {self.get_is_complete_tr(req)}
176 {tr_qa("Utility",
177 ws.number_to_dp(self.utility, DP, default=None))}
178 </table>
179 </div>
180 <div class="{CssClass.EXPLANATION}">
181 Quality of life (QoL) has anchor values of 0 (none) and 1
182 (perfect health). The Standard Gamble offers a trade-off to
183 determine utility (QoL).
184 Values <0 and >1 are possible with some gambles.
185 </div>
186 <table class="{CssClass.TASKDETAIL}">
187 <tr><th width="50%">Measure</th><th width="50%">Value</th></tr>
188 """
189 h += tr_qa("Category choice: start time", self.category_start_time)
190 h += tr_qa(
191 "Category choice: responded?",
192 get_yes_no_none(req, self.category_responded),
193 )
194 h += tr_qa(
195 "Category choice: response time", self.category_response_time
196 )
197 h += tr_qa("Category choice: category chosen", self.category_chosen)
198 h += tr_qa("Gamble: fixed option", self.gamble_fixed_option)
199 h += tr_qa(
200 "Gamble: lottery option for <i>p</i>", self.gamble_lottery_option_p
201 )
202 h += tr_qa(
203 "Gamble: lottery option for <i>q</i> = 1 – <i>p</i>",
204 self.gamble_lottery_option_q,
205 )
206 h += tr_qa(
207 "Gamble: lottery on left?",
208 get_yes_no_none(req, self.gamble_lottery_on_left),
209 )
210 h += tr_qa("Gamble: starting <i>p</i>", self.gamble_starting_p)
211 h += tr_qa("Gamble: start time", self.gamble_start_time)
212 h += tr_qa(
213 "Gamble: responded?", get_yes_no_none(req, self.gamble_responded)
214 )
215 h += tr_qa("Gamble: response time", self.gamble_response_time)
216 h += tr_qa(
217 "Gamble: <i>p</i>",
218 ws.number_to_dp(self.gamble_p, DP, default=None),
219 )
220 h += tr_qa(
221 "Calculated utility",
222 ws.number_to_dp(self.utility, DP, default=None),
223 )
224 h += """
225 </table>
226 """
227 return h
229 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
230 if not self.is_complete():
231 return []
232 return [SnomedExpression(req.snomed(SnomedLookup.QOL_SCALE))]