Coverage for tasks/aims.py: 61%
61 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/aims.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, PV
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 (
40 answer,
41 get_yes_no_none,
42 tr,
43 tr_qa,
44)
45from camcops_server.cc_modules.cc_request import CamcopsRequest
46from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup
47from camcops_server.cc_modules.cc_summaryelement import SummaryElement
48from camcops_server.cc_modules.cc_task import (
49 get_from_dict,
50 Task,
51 TaskHasClinicianMixin,
52 TaskHasPatientMixin,
53)
54from camcops_server.cc_modules.cc_text import SS
55from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo
58# =============================================================================
59# AIMS
60# =============================================================================
63class AimsMetaclass(DeclarativeMeta):
64 # noinspection PyInitNewSignature
65 def __init__(
66 cls: Type["Aims"],
67 name: str,
68 bases: Tuple[Type, ...],
69 classdict: Dict[str, Any],
70 ) -> None:
71 add_multiple_columns(
72 cls,
73 "q",
74 1,
75 cls.NSCOREDQUESTIONS,
76 minimum=0,
77 maximum=4,
78 comment_fmt="Q{n}, {s} (0 none - 4 severe)",
79 comment_strings=[
80 "facial_expression",
81 "lips",
82 "jaw",
83 "tongue",
84 "upper_limbs",
85 "lower_limbs",
86 "trunk",
87 "global",
88 "incapacitation",
89 "awareness",
90 ],
91 )
92 add_multiple_columns(
93 cls,
94 "q",
95 cls.NSCOREDQUESTIONS + 1,
96 cls.NQUESTIONS,
97 pv=PV.BIT,
98 comment_fmt="Q{n}, {s} (not scored) (0 no, 1 yes)",
99 comment_strings=[
100 "problems_teeth_dentures",
101 "usually_wears_dentures",
102 ],
103 )
105 super().__init__(name, bases, classdict)
108class Aims(
109 TaskHasPatientMixin, TaskHasClinicianMixin, Task, metaclass=AimsMetaclass
110):
111 """
112 Server implementation of the AIMS task.
113 """
115 __tablename__ = "aims"
116 shortname = "AIMS"
117 provides_trackers = True
119 NQUESTIONS = 12
120 NSCOREDQUESTIONS = 10
121 TASK_FIELDS = strseq("q", 1, NQUESTIONS)
122 SCORED_FIELDS = strseq("q", 1, NSCOREDQUESTIONS)
124 @staticmethod
125 def longname(req: "CamcopsRequest") -> str:
126 _ = req.gettext
127 return _("Abnormal Involuntary Movement Scale")
129 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:
130 return [
131 TrackerInfo(
132 value=self.total_score(),
133 plot_label="AIMS total score",
134 axis_label="Total score (out of 40)",
135 axis_min=-0.5,
136 axis_max=40.5,
137 )
138 ]
140 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:
141 if not self.is_complete():
142 return CTV_INCOMPLETE
143 return [CtvInfo(content=f"AIMS total score {self.total_score()}/40")]
145 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:
146 return self.standard_task_summary_fields() + [
147 SummaryElement(
148 name="total",
149 coltype=Integer(),
150 value=self.total_score(),
151 comment="Total score (/40)",
152 )
153 ]
155 def is_complete(self) -> bool:
156 return (
157 self.all_fields_not_none(Aims.TASK_FIELDS)
158 and self.field_contents_valid()
159 )
161 def total_score(self) -> int:
162 return self.sum_fields(self.SCORED_FIELDS)
164 # noinspection PyUnresolvedReferences
165 def get_task_html(self, req: CamcopsRequest) -> str:
166 score = self.total_score()
167 main_dict = {None: None}
168 q10_dict = {None: None}
169 for option in range(0, 5):
170 main_dict[option] = (
171 str(option)
172 + " — "
173 + self.wxstring(req, "main_option" + str(option))
174 )
175 q10_dict[option] = (
176 str(option)
177 + " — "
178 + self.wxstring(req, "q10_option" + str(option))
179 )
181 q_a = ""
182 for q in range(1, 10):
183 q_a += tr_qa(
184 self.wxstring(req, "q" + str(q) + "_s"),
185 get_from_dict(main_dict, getattr(self, "q" + str(q))),
186 )
187 q_a += (
188 tr_qa(
189 self.wxstring(req, "q10_s"), get_from_dict(q10_dict, self.q10)
190 )
191 + tr_qa(
192 self.wxstring(req, "q11_s"), get_yes_no_none(req, self.q11)
193 )
194 + tr_qa(
195 self.wxstring(req, "q12_s"), get_yes_no_none(req, self.q12)
196 )
197 )
199 return f"""
200 <div class="{CssClass.SUMMARY}">
201 <table class="{CssClass.SUMMARY}">
202 {self.get_is_complete_tr(req)}
203 {tr(req.sstring(SS.TOTAL_SCORE) + " <sup>[1]</sup>",
204 answer(score) + " / 40")}
205 </table>
206 </div>
207 <table class="{CssClass.TASKDETAIL}">
208 <tr>
209 <th width="50%">Question</th>
210 <th width="50%">Answer</th>
211 </tr>
212 {q_a}
213 </table>
214 <div class="{CssClass.FOOTNOTES}">
215 [1] Only Q1–10 are scored.
216 </div>
217 """
219 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
220 codes = [
221 SnomedExpression(
222 req.snomed(SnomedLookup.AIMS_PROCEDURE_ASSESSMENT)
223 )
224 ]
225 if self.is_complete():
226 codes.append(
227 SnomedExpression(
228 req.snomed(SnomedLookup.AIMS_SCALE),
229 {
230 req.snomed(
231 SnomedLookup.AIMS_TOTAL_SCORE
232 ): self.total_score() # noqa
233 },
234 )
235 )
236 return codes