Coverage for tasks/qolsg.py : 59%

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
3"""
4camcops_server/tasks/qolsg.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
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.
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.
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/>.
25===============================================================================
27"""
29from typing import List
31import cardinal_pythonlib.rnc_web as ws
32from sqlalchemy.sql.schema import Column
33from sqlalchemy.sql.sqltypes import Float, Integer, String
35from camcops_server.cc_modules.cc_constants import CssClass
36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
37from camcops_server.cc_modules.cc_html import get_yes_no_none, tr_qa
38from camcops_server.cc_modules.cc_request import CamcopsRequest
39from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
40from camcops_server.cc_modules.cc_sqla_coltypes import (
41 BIT_CHECKER,
42 CamcopsColumn,
43 PendulumDateTimeAsIsoTextColType,
44 ZERO_TO_ONE_CHECKER,
45)
46from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
47from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
50# =============================================================================
51# QoL-SG
52# =============================================================================
54DP = 3
57class QolSG(TaskHasPatientMixin, Task):
58 """
59 Server implementation of the QoL-SG task.
60 """
61 __tablename__ = "qolsg"
62 shortname = "QoL-SG"
63 provides_trackers = True
65 category_start_time = Column(
66 "category_start_time", PendulumDateTimeAsIsoTextColType,
67 comment="Time categories were offered (ISO-8601)"
68 )
69 category_responded = CamcopsColumn(
70 "category_responded", Integer,
71 permitted_value_checker=BIT_CHECKER,
72 comment="Responded to category choice? (0 no, 1 yes)"
73 )
74 category_response_time = Column(
75 "category_response_time", PendulumDateTimeAsIsoTextColType,
76 comment="Time category was chosen (ISO-8601)"
77 )
78 category_chosen = Column(
79 "category_chosen", String(length=len("medium")),
80 comment="Category chosen: high (QoL > 1) "
81 "medium (0 <= QoL <= 1) low (QoL < 0)"
82 )
83 gamble_fixed_option = Column(
84 "gamble_fixed_option", String(length=len("current")),
85 comment="Fixed option in gamble (current, healthy, dead)"
86 )
87 gamble_lottery_option_p = Column(
88 "gamble_lottery_option_p", String(length=len("current")),
89 comment="Gamble: option corresponding to p "
90 "(current, healthy, dead)"
91 )
92 gamble_lottery_option_q = Column(
93 "gamble_lottery_option_q", String(length=len("current")),
94 comment="Gamble: option corresponding to q "
95 "(current, healthy, dead) (q = 1 - p)"
96 )
97 gamble_lottery_on_left = CamcopsColumn(
98 "gamble_lottery_on_left", Integer,
99 permitted_value_checker=BIT_CHECKER,
100 comment="Gamble: lottery shown on the left (0 no, 1 yes)"
101 )
102 gamble_starting_p = CamcopsColumn(
103 "gamble_starting_p", Float,
104 permitted_value_checker=ZERO_TO_ONE_CHECKER,
105 comment="Gamble: starting value of p"
106 )
107 gamble_start_time = Column(
108 "gamble_start_time", PendulumDateTimeAsIsoTextColType,
109 comment="Time gamble was offered (ISO-8601)"
110 )
111 gamble_responded = CamcopsColumn(
112 "gamble_responded", Integer,
113 permitted_value_checker=BIT_CHECKER,
114 comment="Gamble was responded to? (0 no, 1 yes)"
115 )
116 gamble_response_time = Column(
117 "gamble_response_time", PendulumDateTimeAsIsoTextColType,
118 comment="Time subject responded to gamble (ISO-8601)"
119 )
120 gamble_p = CamcopsColumn(
121 "gamble_p", Float,
122 permitted_value_checker=ZERO_TO_ONE_CHECKER,
123 comment="Final value of p"
124 )
125 utility = Column(
126 "utility", Float,
127 comment="Calculated utility, h"
128 )
130 @staticmethod
131 def longname(req: "CamcopsRequest") -> str:
132 _ = req.gettext
133 return _("Quality of Life: Standard Gamble")
135 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
136 return [TrackerInfo(
137 value=self.utility,
138 plot_label="Quality of life: standard gamble",
139 axis_label="QoL (0-1)",
140 axis_min=0,
141 axis_max=1
142 )]
144 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
145 if not self.is_complete():
146 return CTV_INCOMPLETE
147 return [CtvInfo(
148 content=f"Quality of life: {ws.number_to_dp(self.utility, DP)}"
149 )]
151 def is_complete(self) -> bool:
152 return (
153 self.utility is not None and
154 self.field_contents_valid()
155 )
157 def get_task_html(self, req: CamcopsRequest) -> str:
158 h = f"""
159 <div class="{CssClass.SUMMARY}">
160 <table class="{CssClass.SUMMARY}">
161 {self.get_is_complete_tr(req)}
162 {tr_qa("Utility",
163 ws.number_to_dp(self.utility, DP, default=None))}
164 </table>
165 </div>
166 <div class="{CssClass.EXPLANATION}">
167 Quality of life (QoL) has anchor values of 0 (none) and 1
168 (perfect health). The Standard Gamble offers a trade-off to
169 determine utility (QoL).
170 Values <0 and >1 are possible with some gambles.
171 </div>
172 <table class="{CssClass.TASKDETAIL}">
173 <tr><th width="50%">Measure</th><th width="50%">Value</th></tr>
174 """
175 h += tr_qa("Category choice: start time", self.category_start_time)
176 h += tr_qa("Category choice: responded?",
177 get_yes_no_none(req, self.category_responded))
178 h += tr_qa("Category choice: response time",
179 self.category_response_time)
180 h += tr_qa("Category choice: category chosen", self.category_chosen)
181 h += tr_qa("Gamble: fixed option", self.gamble_fixed_option)
182 h += tr_qa("Gamble: lottery option for <i>p</i>",
183 self.gamble_lottery_option_p)
184 h += tr_qa("Gamble: lottery option for <i>q</i> = 1 – <i>p</i>",
185 self.gamble_lottery_option_q)
186 h += tr_qa("Gamble: lottery on left?",
187 get_yes_no_none(req, self.gamble_lottery_on_left))
188 h += tr_qa("Gamble: starting <i>p</i>", self.gamble_starting_p)
189 h += tr_qa("Gamble: start time", self.gamble_start_time)
190 h += tr_qa("Gamble: responded?",
191 get_yes_no_none(req, self.gamble_responded))
192 h += tr_qa("Gamble: response time", self.gamble_response_time)
193 h += tr_qa("Gamble: <i>p</i>",
194 ws.number_to_dp(self.gamble_p, DP, default=None))
195 h += tr_qa("Calculated utility",
196 ws.number_to_dp(self.utility, DP, default=None))
197 h += """
198 </table>
199 """
200 return h
202 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
203 if not self.is_complete():
204 return []
205 return [SnomedExpression(req.snomed(SnomedLookup.QOL_SCALE))]