Coverage for tasks/esspri.py: 62%

48 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/esspri.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

11 CamCOPS is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CamCOPS is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**EULAR Sjögren’s Syndrome Patient Reported Index (ESSPRI) task.** 

27 

28""" 

29 

30from camcops_server.cc_modules.cc_constants import CssClass 

31from camcops_server.cc_modules.cc_html import tr_qa, tr, answer 

32from camcops_server.cc_modules.cc_request import CamcopsRequest 

33from camcops_server.cc_modules.cc_sqla_coltypes import ( 

34 camcops_column, 

35 ZERO_TO_10_CHECKER, 

36) 

37 

38from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

39from camcops_server.cc_modules.cc_task import TaskHasPatientMixin, Task 

40import cardinal_pythonlib.rnc_web as ws 

41from cardinal_pythonlib.stringfunc import strseq 

42from sqlalchemy import Float, Integer 

43from typing import Any, List, Type 

44 

45 

46class Esspri( # type: ignore[misc] 

47 TaskHasPatientMixin, 

48 Task, 

49): 

50 __tablename__ = "esspri" 

51 shortname = "ESSPRI" 

52 

53 N_QUESTIONS = 3 

54 MAX_SCORE = 10 # Mean of 3 scores of 10 

55 

56 @classmethod 

57 def extend_columns(cls: Type["Esspri"], **kwargs: Any) -> None: 

58 

59 comment_strings = ["dryness", "fatigue", "pain"] 

60 

61 for q_index in range(0, cls.N_QUESTIONS): 

62 q_num = q_index + 1 

63 q_field = "q{}".format(q_num) 

64 

65 score_comment = "(0 none - 10 maximum imaginable)" 

66 

67 setattr( 

68 cls, 

69 q_field, 

70 camcops_column( 

71 q_field, 

72 Integer, 

73 permitted_value_checker=ZERO_TO_10_CHECKER, 

74 comment="Q{} ({}) {}".format( 

75 q_num, comment_strings[q_index], score_comment 

76 ), 

77 ), 

78 ) 

79 

80 ALL_QUESTIONS = strseq("q", 1, N_QUESTIONS) 

81 

82 @staticmethod 

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

84 _ = req.gettext 

85 return _("EULAR Sjögren’s Syndrome Patient Reported Index") 

86 

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

88 return self.standard_task_summary_fields() + [ 

89 SummaryElement( 

90 name="overall_score", 

91 coltype=Float(), 

92 value=self.overall_score(), 

93 comment=f"Overall score (/{self.MAX_SCORE})", 

94 ) 

95 ] 

96 

97 def is_complete(self) -> bool: 

98 if self.any_fields_none(self.ALL_QUESTIONS): 

99 return False 

100 if not self.field_contents_valid(): 

101 return False 

102 return True 

103 

104 def overall_score(self) -> float: 

105 return self.mean_fields(self.ALL_QUESTIONS) 

106 

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

108 rows = "" 

109 for q_num in range(1, self.N_QUESTIONS + 1): 

110 q_field = "q" + str(q_num) 

111 question_cell = "{}. {}".format(q_num, self.wxstring(req, q_field)) 

112 

113 score = getattr(self, q_field) 

114 

115 rows += tr_qa(question_cell, score) 

116 

117 formatted_score = ws.number_to_dp(self.overall_score(), 3, default="?") 

118 

119 html = """ 

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

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

122 {tr_is_complete} 

123 {overall_score} 

124 </table> 

125 </div> 

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

127 <tr> 

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

129 <th width="40%">Answer</th> 

130 </tr> 

131 {rows} 

132 </table> 

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

134 [1] Mean of three numerical rating scales, each rated 0-10. 

135 </div> 

136 """.format( 

137 CssClass=CssClass, 

138 tr_is_complete=self.get_is_complete_tr(req), 

139 overall_score=tr( 

140 self.wxstring(req, "overall_score") + " <sup>[1]</sup>", 

141 "{} / {}".format(answer(formatted_score), self.MAX_SCORE), 

142 ), 

143 rows=rows, 

144 ) 

145 return html