Coverage for tasks/gds.py: 59%
64 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/gds.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, String
36from camcops_server.cc_modules.cc_constants import CssClass, NO_CHAR, YES_CHAR
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_summaryelement import SummaryElement
43from camcops_server.cc_modules.cc_task import Task, TaskHasPatientMixin
44from camcops_server.cc_modules.cc_text import SS
45from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
48# =============================================================================
49# GDS-15
50# =============================================================================
53class Gds15Metaclass(DeclarativeMeta):
54 # noinspection PyInitNewSignature
55 def __init__(
56 cls: Type["Gds15"],
57 name: str,
58 bases: Tuple[Type, ...],
59 classdict: Dict[str, Any],
60 ) -> None:
61 add_multiple_columns(
62 cls,
63 "q",
64 1,
65 cls.NQUESTIONS,
66 String(length=1),
67 pv=[NO_CHAR, YES_CHAR],
68 comment_fmt="Q{n}, {s} ('Y' or 'N')",
69 comment_strings=[
70 "satisfied",
71 "dropped activities",
72 "life empty",
73 "bored",
74 "good spirits", # 5
75 "afraid",
76 "happy",
77 "helpless",
78 "stay at home",
79 "memory problems", # 10
80 "wonderful to be alive",
81 "worthless",
82 "full of energy",
83 "hopeless",
84 "others better off", # 15
85 ],
86 )
87 super().__init__(name, bases, classdict)
90class Gds15(TaskHasPatientMixin, Task, metaclass=Gds15Metaclass):
91 """
92 Server implementation of the GDS-15 task.
93 """
95 __tablename__ = "gds15"
96 shortname = "GDS-15"
97 info_filename_stem = "gds"
98 provides_trackers = True
100 NQUESTIONS = 15
101 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
102 SCORE_IF_YES = [2, 3, 4, 6, 8, 9, 10, 12, 14, 15]
103 SCORE_IF_NO = [1, 5, 7, 11, 13]
104 MAX_SCORE = 15
106 @staticmethod
107 def longname(req: "CamcopsRequest") -> str:
108 _ = req.gettext
109 return _("Geriatric Depression Scale, 15-item version")
111 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
112 return [
113 TrackerInfo(
114 value=self.total_score(),
115 plot_label="GDS-15 total score",
116 axis_label=f"Total score (out of {self.MAX_SCORE})",
117 axis_min=-0.5,
118 axis_max=self.MAX_SCORE + 0.5,
119 )
120 ]
122 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
123 if not self.is_complete():
124 return CTV_INCOMPLETE
125 return [
126 CtvInfo(
127 content=f"GDS-15 total score "
128 f"{self.total_score()}/{self.MAX_SCORE}"
129 )
130 ]
132 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
133 return self.standard_task_summary_fields() + [
134 SummaryElement(
135 name="total",
136 coltype=Integer(),
137 value=self.total_score(),
138 comment=f"Total score (/{self.MAX_SCORE})",
139 )
140 ]
142 def is_complete(self) -> bool:
143 return (
144 self.all_fields_not_none(self.TASK_FIELDS)
145 and self.field_contents_valid()
146 )
148 def total_score(self) -> int:
149 score = 0
150 for q in self.SCORE_IF_YES:
151 if getattr(self, "q" + str(q)) == YES_CHAR:
152 score += 1
153 for q in self.SCORE_IF_NO:
154 if getattr(self, "q" + str(q)) == NO_CHAR:
155 score += 1
156 return score
158 def get_task_html(self, req: CamcopsRequest) -> str:
159 score = self.total_score()
161 q_a = ""
162 for q in range(1, self.NQUESTIONS + 1):
163 suffix = " †" if q in self.SCORE_IF_YES else " *"
164 q_a += tr_qa(
165 str(q) + ". " + self.wxstring(req, "q" + str(q)) + suffix,
166 getattr(self, "q" + str(q)),
167 )
169 return f"""
170 <div class="{CssClass.SUMMARY}">
171 <table class="{CssClass.SUMMARY}">
172 {self.get_is_complete_tr(req)}
173 {tr(req.sstring(SS.TOTAL_SCORE),
174 answer(score) + f" / {self.MAX_SCORE}")}
175 </table>
176 </div>
177 <div class="{CssClass.EXPLANATION}">
178 Ratings are over the last 1 week.
179 </div>
180 <table class="{CssClass.TASKDETAIL}">
181 <tr>
182 <th width="70%">Question</th>
183 <th width="30%">Answer</th>
184 </tr>
185 {q_a}
186 </table>
187 <div class="{CssClass.FOOTNOTES}">
188 (†) ‘Y’ scores 1; ‘N’ scores 0.
189 (*) ‘Y’ scores 0; ‘N’ scores 1.
190 </div>
191 """
193 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
194 codes = [
195 SnomedExpression(
196 req.snomed(SnomedLookup.GDS15_PROCEDURE_ASSESSMENT)
197 )
198 ]
199 if self.is_complete():
200 codes.append(
201 SnomedExpression(
202 req.snomed(SnomedLookup.GDS15_SCALE),
203 {req.snomed(SnomedLookup.GDS15_SCORE): self.total_score()},
204 )
205 )
206 return codes