Coverage for tasks/gad7.py : 54%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
3"""
4camcops_server/tasks/gad7.py
6===============================================================================
8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com).
10 This file is part of CamCOPS.
12 CamCOPS is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 CamCOPS is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
25===============================================================================
27"""
29from typing import Any, Dict, List, Tuple, Type
31from cardinal_pythonlib.stringfunc import strseq
32from sqlalchemy.ext.declarative import DeclarativeMeta
33from sqlalchemy.sql.sqltypes import Integer
35from camcops_server.cc_modules.cc_constants import CssClass
36from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo
37from camcops_server.cc_modules.cc_db import add_multiple_columns
38from camcops_server.cc_modules.cc_html import answer, tr, tr_qa
39from camcops_server.cc_modules.cc_request import CamcopsRequest
40from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
41from camcops_server.cc_modules.cc_sqla_coltypes import SummaryCategoryColType
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)
48from camcops_server.cc_modules.cc_text import SS
49from camcops_server.cc_modules.cc_trackerhelpers import (
50 TrackerInfo,
51 TrackerLabel,
52)
55# =============================================================================
56# GAD-7
57# =============================================================================
59class Gad7Metaclass(DeclarativeMeta):
60 # noinspection PyInitNewSignature
61 def __init__(cls: Type['Gad7'],
62 name: str,
63 bases: Tuple[Type, ...],
64 classdict: Dict[str, Any]) -> None:
65 add_multiple_columns(
66 cls, "q", 1, cls.NQUESTIONS,
67 minimum=0, maximum=3,
68 comment_fmt="Q{n}, {s} (0 not at all - 3 nearly every day)",
69 comment_strings=[
70 "nervous/anxious/on edge",
71 "can't stop/control worrying",
72 "worrying too much about different things",
73 "trouble relaxing",
74 "restless",
75 "irritable",
76 "afraid"
77 ]
78 )
79 super().__init__(name, bases, classdict)
82class Gad7(TaskHasPatientMixin, Task,
83 metaclass=Gad7Metaclass):
84 """
85 Server implementation of the GAD-7 task.
86 """
87 __tablename__ = "gad7"
88 shortname = "GAD-7"
89 provides_trackers = True
91 NQUESTIONS = 7
92 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
93 MAX_SCORE = 21
95 @staticmethod
96 def longname(req: "CamcopsRequest") -> str:
97 _ = req.gettext
98 return _("Generalized Anxiety Disorder Assessment")
100 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
101 return [TrackerInfo(
102 value=self.total_score(),
103 plot_label="GAD-7 total score",
104 axis_label="Total score (out of 21)",
105 axis_min=-0.5,
106 axis_max=self.MAX_SCORE + 0.5,
107 horizontal_lines=[14.5, 9.5, 4.5],
108 horizontal_labels=[
109 TrackerLabel(17, req.sstring(SS.SEVERE)),
110 TrackerLabel(12, req.sstring(SS.MODERATE)),
111 TrackerLabel(7, req.sstring(SS.MILD)),
112 TrackerLabel(2.25, req.sstring(SS.NONE)),
113 ]
114 )]
116 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
117 if not self.is_complete():
118 return CTV_INCOMPLETE
119 return [CtvInfo(content=(
120 f"GAD-7 total score {self.total_score()}/{self.MAX_SCORE} "
121 f"({self.severity(req)})"
122 ))]
124 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
125 return self.standard_task_summary_fields() + [
126 SummaryElement(name="total",
127 coltype=Integer(),
128 value=self.total_score(),
129 comment=f"Total score (/{self.MAX_SCORE})"),
130 SummaryElement(name="severity",
131 coltype=SummaryCategoryColType,
132 value=self.severity(req),
133 comment="Severity"),
134 ]
136 def is_complete(self) -> bool:
137 return (
138 self.all_fields_not_none(self.TASK_FIELDS) and
139 self.field_contents_valid()
140 )
142 def total_score(self) -> int:
143 return self.sum_fields(self.TASK_FIELDS)
145 def severity(self, req: CamcopsRequest) -> str:
146 score = self.total_score()
147 if score >= 15:
148 severity = req.sstring(SS.SEVERE)
149 elif score >= 10:
150 severity = req.sstring(SS.MODERATE)
151 elif score >= 5:
152 severity = req.sstring(SS.MILD)
153 else:
154 severity = req.sstring(SS.NONE)
155 return severity
157 def get_task_html(self, req: CamcopsRequest) -> str:
158 score = self.total_score()
159 severity = self.severity(req)
160 answer_dict = {None: None}
161 for option in range(0, 4):
162 answer_dict[option] = (
163 str(option) + " — " + self.wxstring(req, "a" + str(option))
164 )
166 q_a = ""
167 for q in range(1, self.NQUESTIONS + 1):
168 q_a += tr_qa(
169 self.wxstring(req, "q" + str(q)),
170 get_from_dict(answer_dict, getattr(self, "q" + str(q)))
171 )
173 return """
174 <div class="{CssClass.SUMMARY}">
175 <table class="{CssClass.SUMMARY}">
176 {tr_is_complete}
177 {total_score}
178 {anxiety_severity}
179 </table>
180 </div>
181 <div class="{CssClass.EXPLANATION}">
182 Ratings are over the last 2 weeks.
183 </div>
184 <table class="{CssClass.TASKDETAIL}">
185 <tr>
186 <th width="50%">Question</th>
187 <th width="50%">Answer</th>
188 </tr>
189 {q_a}
190 </table>
191 <div class="{CssClass.FOOTNOTES}">
192 [1] ≥15 severe, ≥10 moderate, ≥5 mild.
193 Score ≥10 identifies: generalized anxiety disorder with
194 sensitivity 89%, specificity 82% (Spitzer et al. 2006, PubMed
195 ID 16717171);
196 panic disorder with sensitivity 74%, specificity 81% (Kroenke
197 et al. 2010, PMID 20633738);
198 social anxiety with sensitivity 72%, specificity 80% (Kroenke
199 et al. 2010);
200 post-traumatic stress disorder with sensitivity 66%,
201 specificity 81% (Kroenke et al. 2010).
202 The majority of evidence contributing to these figures comes
203 from primary care screening studies.
204 </div>
205 """.format(
206 CssClass=CssClass,
207 tr_is_complete=self.get_is_complete_tr(req),
208 total_score=tr(
209 req.sstring(SS.TOTAL_SCORE),
210 answer(score) + " / {}".format(self.MAX_SCORE)
211 ),
212 anxiety_severity=tr(
213 self.wxstring(req, "anxiety_severity") + " <sup>[1]</sup>",
214 severity
215 ),
216 q_a=q_a,
217 )
219 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
220 codes = [SnomedExpression(req.snomed(SnomedLookup.GAD7_PROCEDURE_ASSESSMENT))] # noqa
221 if self.is_complete():
222 codes.append(SnomedExpression(
223 req.snomed(SnomedLookup.GAD7_SCALE),
224 {
225 req.snomed(SnomedLookup.GAD7_SCORE): self.total_score(),
226 }
227 ))
228 return codes