Hide keyboard shortcuts

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 

2 

3""" 

4camcops_server/tasks/bprse.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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. 

16 

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. 

21 

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/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29from typing import Any, Dict, List, Tuple, Type 

30 

31from cardinal_pythonlib.stringfunc import strseq 

32from sqlalchemy.ext.declarative import DeclarativeMeta 

33from sqlalchemy.sql.sqltypes import Integer 

34 

35from camcops_server.cc_modules.cc_constants import CssClass 

36from camcops_server.cc_modules.cc_ctvinfo import CtvInfo, CTV_INCOMPLETE 

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_summaryelement import SummaryElement 

41from camcops_server.cc_modules.cc_task import ( 

42 get_from_dict, 

43 Task, 

44 TaskHasClinicianMixin, 

45 TaskHasPatientMixin, 

46) 

47from camcops_server.cc_modules.cc_text import SS 

48from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo 

49 

50 

51# ============================================================================= 

52# BPRS-E 

53# ============================================================================= 

54 

55class BprseMetaclass(DeclarativeMeta): 

56 # noinspection PyInitNewSignature 

57 def __init__(cls: Type['Bprse'], 

58 name: str, 

59 bases: Tuple[Type, ...], 

60 classdict: Dict[str, Any]) -> None: 

61 add_multiple_columns( 

62 cls, "q", 1, cls.NQUESTIONS, 

63 minimum=0, maximum=7, 

64 comment_fmt="Q{n}, {s} (1-7, higher worse, or 0 for not assessed)", 

65 comment_strings=[ 

66 "somatic concern", "anxiety", "depression", "suicidality", 

67 "guilt", "hostility", "elevated mood", "grandiosity", 

68 "suspiciousness", "hallucinations", "unusual thought content", 

69 "bizarre behaviour", "self-neglect", "disorientation", 

70 "conceptual disorganisation", "blunted affect", 

71 "emotional withdrawal", "motor retardation", "tension", 

72 "uncooperativeness", "excitement", "distractibility", 

73 "motor hyperactivity", "mannerisms and posturing"] 

74 ) 

75 super().__init__(name, bases, classdict) 

76 

77 

78class Bprse(TaskHasPatientMixin, TaskHasClinicianMixin, Task, 

79 metaclass=BprseMetaclass): 

80 """ 

81 Server implementation of the BPRS-E task. 

82 """ 

83 __tablename__ = "bprse" 

84 shortname = "BPRS-E" 

85 provides_trackers = True 

86 

87 NQUESTIONS = 24 

88 TASK_FIELDS = strseq("q", 1, NQUESTIONS) 

89 MAX_SCORE = 168 

90 

91 @staticmethod 

92 def longname(req: "CamcopsRequest") -> str: 

93 _ = req.gettext 

94 return _("Brief Psychiatric Rating Scale, Expanded") 

95 

96 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]: 

97 return [TrackerInfo( 

98 value=self.total_score(), 

99 plot_label="BPRS-E total score", 

100 axis_label=f"Total score (out of {self.MAX_SCORE})", 

101 axis_min=-0.5, 

102 axis_max=self.MAX_SCORE + 0.5, 

103 )] 

104 

105 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]: 

106 if not self.is_complete(): 

107 return CTV_INCOMPLETE 

108 return [CtvInfo( 

109 content=f"BPRS-E total score {self.total_score()}/{self.MAX_SCORE}" 

110 )] 

111 

112 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]: 

113 return self.standard_task_summary_fields() + [ 

114 SummaryElement(name="total", 

115 coltype=Integer(), 

116 value=self.total_score(), 

117 comment=f"Total score (/{self.MAX_SCORE})"), 

118 ] 

119 

120 def is_complete(self) -> bool: 

121 return ( 

122 self.all_fields_not_none(self.TASK_FIELDS) and 

123 self.field_contents_valid() 

124 ) 

125 

126 def total_score(self) -> int: 

127 return self.sum_fields(self.TASK_FIELDS) 

128 

129 def get_task_html(self, req: CamcopsRequest) -> str: 

130 def bprs_string(x: str) -> str: 

131 return req.wxstring("bprs", x) 

132 

133 main_dict = { 

134 None: None, 

135 0: "0 — " + bprs_string("old_option0"), 

136 1: "1 — " + bprs_string("old_option1"), 

137 2: "2 — " + bprs_string("old_option2"), 

138 3: "3 — " + bprs_string("old_option3"), 

139 4: "4 — " + bprs_string("old_option4"), 

140 5: "5 — " + bprs_string("old_option5"), 

141 6: "6 — " + bprs_string("old_option6"), 

142 7: "7 — " + bprs_string("old_option7") 

143 } 

144 

145 q_a = "" 

146 for i in range(1, self.NQUESTIONS + 1): 

147 q_a += tr_qa( 

148 self.wxstring(req, "q" + str(i) + "_s"), 

149 get_from_dict(main_dict, getattr(self, "q" + str(i))) 

150 ) 

151 

152 total_score = tr( 

153 req.sstring(SS.TOTAL_SCORE) + 

154 f" (0–{self.MAX_SCORE}; 24–{self.MAX_SCORE} if all rated)", 

155 answer(self.total_score()) 

156 ) 

157 return f""" 

158 <div class="{CssClass.SUMMARY}"> 

159 <table class="{CssClass.SUMMARY}"> 

160 {self.get_is_complete_tr(req)} 

161 {total_score} 

162 </table> 

163 </div> 

164 <div class="{CssClass.EXPLANATION}"> 

165 Each question has specific answer definitions (see e.g. tablet 

166 app). 

167 </div> 

168 <table class="{CssClass.TASKDETAIL}"> 

169 <tr> 

170 <th width="60%">Question</th> 

171 <th width="40%">Answer <sup>[1]</sup></th> 

172 </tr> 

173 {q_a} 

174 </table> 

175 <div class="{CssClass.FOOTNOTES}"> 

176 [1] All answers are in the range 1–7, or 0 (not assessed, for 

177 some). 

178 </div> 

179 """