Coverage for tasks/gds.py : 59%

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