Coverage for tasks/chit.py: 62%
52 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 15:51 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 15:51 +0100
1"""
2camcops_server/tasks/chit.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26**Cambridge-Chicago Compulsivity Trait Scale task.**
28"""
30from typing import Any, cast, List, Optional, Type
32from cardinal_pythonlib.classes import classproperty
33from cardinal_pythonlib.stringfunc import strseq
34from semantic_version import Version
35from sqlalchemy import Integer
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
54class Chit( # type: ignore[misc]
55 TaskHasPatientMixin,
56 Task,
57):
58 __tablename__ = "chit"
59 shortname = "CHI-T"
61 N_SCORED_QUESTIONS = 15
62 MIN_ANSWER = 0
63 MAX_ANSWER = 4
64 MAX_SCORE_MAIN = MAX_ANSWER * N_SCORED_QUESTIONS
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 )
95 SCORED_QUESTIONS = strseq("q", 1, N_SCORED_QUESTIONS)
97 @staticmethod
98 def longname(req: "CamcopsRequest") -> str:
99 _ = req.gettext
100 return _("Cambridge–Chicago Compulsivity Trait Scale")
102 # noinspection PyMethodParameters
103 @classproperty
104 def minimum_client_version(cls) -> Version:
105 return Version("2.4.15")
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 ]
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
124 def total_score(self) -> int:
125 return cast(int, self.sum_fields(self.SCORED_QUESTIONS))
127 def get_task_html(self, req: CamcopsRequest) -> str:
128 score_dict: dict[Optional[int], Optional[str]] = {None: None}
130 for i in range(self.MIN_ANSWER, self.MAX_ANSWER + 1):
131 score_dict[i] = f"{i} — " + self.wxstring(req, f"a{i}")
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))
139 rows += tr_qa(question_cell, answer_cell)
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