Coverage for tasks/qolsg.py: 60%

67 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/qolsg.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import List 

31 

32import cardinal_pythonlib.rnc_web as ws 

33from sqlalchemy.sql.schema import Column 

34from sqlalchemy.sql.sqltypes import Float, Integer, String 

35 

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 

49 

50 

51# ============================================================================= 

52# QoL-SG 

53# ============================================================================= 

54 

55DP = 3 

56 

57 

58class QolSG(TaskHasPatientMixin, Task): 

59 """ 

60 Server implementation of the QoL-SG task. 

61 """ 

62 

63 __tablename__ = "qolsg" 

64 shortname = "QoL-SG" 

65 info_filename_stem = "qol" 

66 provides_trackers = True 

67 

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") 

142 

143 @staticmethod 

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

145 _ = req.gettext 

146 return _("Quality of Life: Standard Gamble") 

147 

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 ] 

158 

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 ] 

167 

168 def is_complete(self) -> bool: 

169 return self.utility is not None and self.field_contents_valid() 

170 

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 &lt;0 and &gt;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 

228 

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))]