Coverage for tasks/gad7.py: 52%
65 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/gad7.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"""
28from typing import Any, cast, List, Optional, Type
30from cardinal_pythonlib.stringfunc import strseq
31from sqlalchemy.sql.sqltypes import Integer
33from camcops_server.cc_modules.cc_constants import CssClass
34from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
35from camcops_server.cc_modules.cc_db import add_multiple_columns
36from camcops_server.cc_modules.cc_html import answer, tr, tr_qa
37from camcops_server.cc_modules.cc_request import CamcopsRequest
38from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
39from camcops_server.cc_modules.cc_sqla_coltypes import SummaryCategoryColType
40from camcops_server.cc_modules.cc_summaryelement import SummaryElement
41from camcops_server.cc_modules.cc_task import (
42 get_from_dict,
43 Task,
44 TaskHasPatientMixin,
45)
46from camcops_server.cc_modules.cc_text import SS
47from camcops_server.cc_modules.cc_trackerhelpers import (
48 TrackerInfo,
49 TrackerLabel,
50)
53# =============================================================================
54# GAD-7
55# =============================================================================
58class Gad7( # type: ignore[misc]
59 TaskHasPatientMixin,
60 Task,
61):
62 """
63 Server implementation of the GAD-7 task.
64 """
66 __tablename__ = "gad7"
67 shortname = "GAD-7"
68 provides_trackers = True
70 NQUESTIONS = 7
72 @classmethod
73 def extend_columns(cls: Type["Gad7"], **kwargs: Any) -> None:
74 add_multiple_columns(
75 cls,
76 "q",
77 1,
78 cls.NQUESTIONS,
79 minimum=0,
80 maximum=3,
81 comment_fmt="Q{n}, {s} (0 not at all - 3 nearly every day)",
82 comment_strings=[
83 "nervous/anxious/on edge",
84 "can't stop/control worrying",
85 "worrying too much about different things",
86 "trouble relaxing",
87 "restless",
88 "irritable",
89 "afraid",
90 ],
91 )
93 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
94 MAX_SCORE = 21
96 @staticmethod
97 def longname(req: "CamcopsRequest") -> str:
98 _ = req.gettext
99 return _("Generalized Anxiety Disorder Assessment")
101 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
102 return [
103 TrackerInfo(
104 value=self.total_score(),
105 plot_label="GAD-7 total score",
106 axis_label="Total score (out of 21)",
107 axis_min=-0.5,
108 axis_max=self.MAX_SCORE + 0.5,
109 horizontal_lines=[14.5, 9.5, 4.5],
110 horizontal_labels=[
111 TrackerLabel(17, req.sstring(SS.SEVERE)),
112 TrackerLabel(12, req.sstring(SS.MODERATE)),
113 TrackerLabel(7, req.sstring(SS.MILD)),
114 TrackerLabel(2.25, req.sstring(SS.NONE)),
115 ],
116 )
117 ]
119 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
120 if not self.is_complete():
121 return CTV_INCOMPLETE
122 return [
123 CtvInfo(
124 content=(
125 f"GAD-7 total score {self.total_score()}/{self.MAX_SCORE} "
126 f"({self.severity(req)})"
127 )
128 )
129 ]
131 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
132 return self.standard_task_summary_fields() + [
133 SummaryElement(
134 name="total",
135 coltype=Integer(),
136 value=self.total_score(),
137 comment=f"Total score (/{self.MAX_SCORE})",
138 ),
139 SummaryElement(
140 name="severity",
141 coltype=SummaryCategoryColType,
142 value=self.severity(req),
143 comment="Severity",
144 ),
145 ]
147 def is_complete(self) -> bool:
148 return (
149 self.all_fields_not_none(self.TASK_FIELDS)
150 and self.field_contents_valid()
151 )
153 def total_score(self) -> int:
154 return cast(int, self.sum_fields(self.TASK_FIELDS))
156 def severity(self, req: CamcopsRequest) -> str:
157 score = self.total_score()
158 if score >= 15:
159 severity = req.sstring(SS.SEVERE)
160 elif score >= 10:
161 severity = req.sstring(SS.MODERATE)
162 elif score >= 5:
163 severity = req.sstring(SS.MILD)
164 else:
165 severity = req.sstring(SS.NONE)
166 return severity
168 def get_task_html(self, req: CamcopsRequest) -> str:
169 score = self.total_score()
170 severity = self.severity(req)
171 answer_dict: dict[Optional[int], Optional[str]] = {None: None}
172 for option in range(0, 4):
173 answer_dict[option] = (
174 str(option) + " — " + self.wxstring(req, "a" + str(option))
175 )
177 q_a = ""
178 for q in range(1, self.NQUESTIONS + 1):
179 q_a += tr_qa(
180 self.wxstring(req, "q" + str(q)),
181 get_from_dict(answer_dict, getattr(self, "q" + str(q))),
182 )
184 return """
185 <div class="{CssClass.SUMMARY}">
186 <table class="{CssClass.SUMMARY}">
187 {tr_is_complete}
188 {total_score}
189 {anxiety_severity}
190 </table>
191 </div>
192 <div class="{CssClass.EXPLANATION}">
193 Ratings are over the last 2 weeks.
194 </div>
195 <table class="{CssClass.TASKDETAIL}">
196 <tr>
197 <th width="50%">Question</th>
198 <th width="50%">Answer</th>
199 </tr>
200 {q_a}
201 </table>
202 <div class="{CssClass.FOOTNOTES}">
203 [1] ≥15 severe, ≥10 moderate, ≥5 mild.
204 Score ≥10 identifies: generalized anxiety disorder with
205 sensitivity 89%, specificity 82% (Spitzer et al. 2006, PubMed
206 ID 16717171);
207 panic disorder with sensitivity 74%, specificity 81% (Kroenke
208 et al. 2010, PMID 20633738);
209 social anxiety with sensitivity 72%, specificity 80% (Kroenke
210 et al. 2010);
211 post-traumatic stress disorder with sensitivity 66%,
212 specificity 81% (Kroenke et al. 2010).
213 The majority of evidence contributing to these figures comes
214 from primary care screening studies.
215 </div>
216 """.format(
217 CssClass=CssClass,
218 tr_is_complete=self.get_is_complete_tr(req),
219 total_score=tr(
220 req.sstring(SS.TOTAL_SCORE),
221 answer(score) + " / {}".format(self.MAX_SCORE),
222 ),
223 anxiety_severity=tr(
224 self.wxstring(req, "anxiety_severity") + " <sup>[1]</sup>",
225 severity,
226 ),
227 q_a=q_a,
228 )
230 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
231 codes = [
232 SnomedExpression(
233 req.snomed(SnomedLookup.GAD7_PROCEDURE_ASSESSMENT)
234 )
235 ]
236 if self.is_complete():
237 codes.append(
238 SnomedExpression(
239 req.snomed(SnomedLookup.GAD7_SCALE),
240 {req.snomed(SnomedLookup.GAD7_SCORE): self.total_score()},
241 )
242 )
243 return codes