Coverage for tasks/zbi.py: 62%

48 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/zbi.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 

28from typing import Any, cast, List, Optional, Type 

29 

30from cardinal_pythonlib.stringfunc import strseq 

31from sqlalchemy.sql.sqltypes import Integer 

32 

33from camcops_server.cc_modules.cc_constants import ( 

34 CssClass, 

35 DATA_COLLECTION_UNLESS_UPGRADED_DIV, 

36) 

37from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

38from camcops_server.cc_modules.cc_db import add_multiple_columns 

39from camcops_server.cc_modules.cc_html import answer, tr 

40from camcops_server.cc_modules.cc_request import CamcopsRequest 

41from camcops_server.cc_modules.cc_string import AS 

42from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

43from camcops_server.cc_modules.cc_task import ( 

44 get_from_dict, 

45 Task, 

46 TaskHasPatientMixin, 

47 TaskHasRespondentMixin, 

48) 

49 

50 

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

52# ZBI 

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

54 

55 

56class Zbi12( # type: ignore[misc] 

57 TaskHasRespondentMixin, 

58 TaskHasPatientMixin, 

59 Task, 

60): 

61 """ 

62 Server implementation of the ZBI-12 task. 

63 """ 

64 

65 __tablename__ = "zbi12" 

66 shortname = "ZBI-12" 

67 info_filename_stem = "zbi" 

68 

69 MIN_PER_Q = 0 

70 MAX_PER_Q = 4 

71 NQUESTIONS = 12 

72 

73 @classmethod 

74 def extend_columns(cls: Type["Zbi12"], **kwargs: Any) -> None: 

75 add_multiple_columns( 

76 cls, 

77 "q", 

78 1, 

79 cls.NQUESTIONS, 

80 minimum=cls.MIN_PER_Q, 

81 maximum=cls.MAX_PER_Q, 

82 comment_fmt="Q{n}, {s} (0-4, higher worse)", 

83 comment_strings=[ 

84 "insufficient time for self", # 1 

85 "stressed with other responsibilities", 

86 "angry", 

87 "other relationships affected", 

88 "strained", # 5 

89 "health suffered", 

90 "insufficient privacy", 

91 "social life suffered", 

92 "lost control", 

93 "uncertain", # 10 

94 "should do more", 

95 "could care better", 

96 ], 

97 ) 

98 

99 TASK_FIELDS = strseq("q", 1, NQUESTIONS) 

100 MAX_TOTAL = MAX_PER_Q * NQUESTIONS 

101 

102 @staticmethod 

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

104 _ = req.gettext 

105 return _("Zarit Burden Interview-12") 

106 

107 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

108 return self.standard_task_summary_fields() + [ 

109 SummaryElement( 

110 name="total_score", 

111 coltype=Integer(), 

112 value=self.total_score(), 

113 comment=f"Total score (/ {self.MAX_TOTAL})", 

114 ) 

115 ] 

116 

117 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

118 if not self.is_complete(): 

119 return CTV_INCOMPLETE 

120 return [ 

121 CtvInfo( 

122 content=f"ZBI-12 total score " 

123 f"{self.total_score()}/{self.MAX_TOTAL}" 

124 ) 

125 ] 

126 

127 def total_score(self) -> int: 

128 return cast(int, self.sum_fields(self.TASK_FIELDS)) 

129 

130 def is_complete(self) -> bool: 

131 return ( 

132 self.field_contents_valid() 

133 and self.is_respondent_complete() 

134 and self.all_fields_not_none(self.TASK_FIELDS) 

135 ) 

136 

137 def get_task_html(self, req: CamcopsRequest) -> str: 

138 option_dict: dict[Optional[int], Optional[str]] = {None: None} 

139 for a in range(self.MIN_PER_Q, self.MAX_PER_Q + 1): 

140 option_dict[a] = req.wappstring(AS.ZBI_A_PREFIX + str(a)) 

141 h = f""" 

142 <div class="{CssClass.SUMMARY}"> 

143 <table class="{CssClass.SUMMARY}"> 

144 {self.get_is_complete_tr(req)} 

145 <tr> 

146 <td>Total score (/ {self.MAX_TOTAL})</td> 

147 <td>{answer(self.total_score())}</td> 

148 </td> 

149 </table> 

150 </div> 

151 <table class="{CssClass.TASKDETAIL}"> 

152 <tr> 

153 <th width="75%">Question</th> 

154 <th width="25%">Answer ({self.MIN_PER_Q}–{self.MAX_PER_Q}) 

155 </th> 

156 </tr> 

157 """ 

158 for q in range(1, self.NQUESTIONS + 1): 

159 a = getattr(self, "q" + str(q)) 

160 fa = ( 

161 f"{a}: {get_from_dict(option_dict, a)}" 

162 if a is not None 

163 else None 

164 ) 

165 h += tr(self.wxstring(req, "q" + str(q)), answer(fa)) 

166 h += ( 

167 """ 

168 </table> 

169 """ 

170 + DATA_COLLECTION_UNLESS_UPGRADED_DIV 

171 ) 

172 return h