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