Coverage for tasks/chit.py: 62%

52 statements  

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

1""" 

2camcops_server/tasks/chit.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**Cambridge-Chicago Compulsivity Trait Scale task.** 

27 

28""" 

29 

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

31 

32from cardinal_pythonlib.classes import classproperty 

33from cardinal_pythonlib.stringfunc import strseq 

34from semantic_version import Version 

35from sqlalchemy import Integer 

36 

37from camcops_server.cc_modules.cc_constants import CssClass 

38from camcops_server.cc_modules.cc_db import add_multiple_columns 

39from camcops_server.cc_modules.cc_html import ( 

40 tr_qa, 

41 tr, 

42 answer, 

43) 

44from camcops_server.cc_modules.cc_request import CamcopsRequest 

45from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

46from camcops_server.cc_modules.cc_task import ( 

47 TaskHasPatientMixin, 

48 Task, 

49 get_from_dict, 

50) 

51from camcops_server.cc_modules.cc_text import SS 

52 

53 

54class Chit( # type: ignore[misc] 

55 TaskHasPatientMixin, 

56 Task, 

57): 

58 __tablename__ = "chit" 

59 shortname = "CHI-T" 

60 

61 N_SCORED_QUESTIONS = 15 

62 MIN_ANSWER = 0 

63 MAX_ANSWER = 4 

64 MAX_SCORE_MAIN = MAX_ANSWER * N_SCORED_QUESTIONS 

65 

66 @classmethod 

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

68 add_multiple_columns( 

69 cls, 

70 "q", 

71 1, 

72 cls.N_SCORED_QUESTIONS, 

73 minimum=cls.MIN_ANSWER, 

74 maximum=cls.MAX_ANSWER, 

75 comment_fmt="Q{n} ({s}) (0 strongly disagree - 4 strongly agree)", 

76 comment_strings=[ 

77 "hate unfinished task", 

78 "just right", 

79 "keep doing task", 

80 "get stuck", 

81 "habit", 

82 "addictive", 

83 "stubborn rigid", 

84 "urges", 

85 "rewarding things", 

86 "hard moving", 

87 "higher standards", 

88 "improvement", 

89 "complete", 

90 "avoid situations", 

91 "hobby", 

92 ], 

93 ) 

94 

95 SCORED_QUESTIONS = strseq("q", 1, N_SCORED_QUESTIONS) 

96 

97 @staticmethod 

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

99 _ = req.gettext 

100 return _("Cambridge–Chicago Compulsivity Trait Scale") 

101 

102 # noinspection PyMethodParameters 

103 @classproperty 

104 def minimum_client_version(cls) -> Version: 

105 return Version("2.4.15") 

106 

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

108 return self.standard_task_summary_fields() + [ 

109 SummaryElement( 

110 name="total", 

111 coltype=Integer(), 

112 value=self.total_score(), 

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

114 ) 

115 ] 

116 

117 def is_complete(self) -> bool: 

118 if self.any_fields_none(self.SCORED_QUESTIONS): 

119 return False 

120 if not self.field_contents_valid(): 

121 return False 

122 return True 

123 

124 def total_score(self) -> int: 

125 return cast(int, self.sum_fields(self.SCORED_QUESTIONS)) 

126 

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

128 score_dict: dict[Optional[int], Optional[str]] = {None: None} 

129 

130 for i in range(self.MIN_ANSWER, self.MAX_ANSWER + 1): 

131 score_dict[i] = f"{i} — " + self.wxstring(req, f"a{i}") 

132 

133 rows = "" 

134 for i in range(1, self.N_SCORED_QUESTIONS + 1): 

135 q_field = "q" + str(i) 

136 question_cell = "{}. {}".format(i, self.wxstring(req, q_field)) 

137 answer_cell = get_from_dict(score_dict, getattr(self, q_field)) 

138 

139 rows += tr_qa(question_cell, answer_cell) 

140 

141 html = """ 

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

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

144 {tr_is_complete} 

145 {total_score} 

146 </table> 

147 </div> 

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

149 <tr> 

150 <th width="60%">Question</th> 

151 <th width="40%">Answer <sup>[2]</sup></th> 

152 </tr> 

153 {rows} 

154 </table> 

155 <div class="{CssClass.FOOTNOTES}"> 

156 [1] Sum for questions 1–15. Prior to CamCOPS version 2.4.15 

157 each question scored 0–3 with a maximum possible score of 45. 

158 [2] Prior to CamCOPS version 2.4.15 the responses were: 

159 0 — Strongly disagree, 1 — Disagree, 2 — Agree, 3 — Strongly 

160 agree. 

161 </div> 

162 """.format( 

163 CssClass=CssClass, 

164 tr_is_complete=self.get_is_complete_tr(req), 

165 total_score=tr( 

166 req.sstring(SS.TOTAL_SCORE) + " <sup>[1]</sup>", 

167 answer(self.total_score()) + f" / {self.MAX_SCORE_MAIN}", 

168 ), 

169 rows=rows, 

170 ) 

171 return html