Coverage for tasks/icd10mixed.py: 62%

50 statements  

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

1""" 

2camcops_server/tasks/icd10mixed.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""" 

27 

28import datetime 

29from typing import List, Optional 

30 

31from cardinal_pythonlib.datetimefunc import format_datetime 

32from cardinal_pythonlib.typetests import is_false 

33import cardinal_pythonlib.rnc_web as ws 

34from sqlalchemy.orm import Mapped, mapped_column 

35from sqlalchemy.sql.sqltypes import Boolean, UnicodeText 

36 

37from camcops_server.cc_modules.cc_constants import ( 

38 CssClass, 

39 DateFormat, 

40 ICD10_COPYRIGHT_DIV, 

41) 

42from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

43from camcops_server.cc_modules.cc_html import get_true_false_none, tr_qa 

44from camcops_server.cc_modules.cc_request import CamcopsRequest 

45from camcops_server.cc_modules.cc_sqla_coltypes import ( 

46 BIT_CHECKER, 

47 mapped_camcops_column, 

48) 

49from camcops_server.cc_modules.cc_string import AS 

50from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

51from camcops_server.cc_modules.cc_task import ( 

52 Task, 

53 TaskHasClinicianMixin, 

54 TaskHasPatientMixin, 

55) 

56from camcops_server.cc_modules.cc_text import SS 

57 

58 

59# ============================================================================= 

60# Icd10Mixed 

61# ============================================================================= 

62 

63 

64class Icd10Mixed(TaskHasClinicianMixin, TaskHasPatientMixin, Task): # type: ignore[misc] # noqa: E501 

65 """ 

66 Server implementation of the ICD10-MIXED task. 

67 """ 

68 

69 __tablename__ = "icd10mixed" 

70 shortname = "ICD10-MIXED" 

71 info_filename_stem = "icd" 

72 

73 date_pertains_to: Mapped[Optional[datetime.date]] = mapped_column( 

74 comment="Date the assessment pertains to" 

75 ) 

76 comments: Mapped[Optional[str]] = mapped_column( 

77 UnicodeText, comment="Clinician's comments" 

78 ) 

79 mixture_or_rapid_alternation: Mapped[Optional[bool]] = ( 

80 mapped_camcops_column( 

81 permitted_value_checker=BIT_CHECKER, 

82 comment="The episode is characterized by either a mixture or " 

83 "a rapid alternation (i.e. within a few hours) of hypomanic, " 

84 "manic and depressive symptoms.", 

85 ) 

86 ) 

87 duration_at_least_2_weeks: Mapped[Optional[bool]] = mapped_camcops_column( 

88 permitted_value_checker=BIT_CHECKER, 

89 comment="Both manic and depressive symptoms must be prominent" 

90 " most of the time during a period of at least two weeks.", 

91 ) 

92 

93 @staticmethod 

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

95 _ = req.gettext 

96 return _( 

97 "ICD-10 symptomatic criteria for a mixed affective episode " 

98 "(as in e.g. F06.3, F25, F38.00, F31.6)" 

99 ) 

100 

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

102 if not self.is_complete(): 

103 return CTV_INCOMPLETE 

104 category = ( 

105 "Meets" if self.meets_criteria() else "Does not meet" 

106 ) + " criteria for mixed affective episode" 

107 infolist = [ 

108 CtvInfo( 

109 content="Pertains to: {}. {}.".format( 

110 format_datetime( 

111 self.date_pertains_to, DateFormat.LONG_DATE 

112 ), 

113 category, 

114 ) 

115 ) 

116 ] 

117 if self.comments: 

118 infolist.append(CtvInfo(content=ws.webify(self.comments))) 

119 return infolist 

120 

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

122 return self.standard_task_summary_fields() + [ 

123 SummaryElement( 

124 name="meets_criteria", 

125 coltype=Boolean(), 

126 value=self.meets_criteria(), 

127 comment="Meets criteria for a mixed affective episode?", 

128 ) 

129 ] 

130 

131 # Meets criteria? These also return null for unknown. 

132 def meets_criteria(self) -> Optional[bool]: 

133 if ( 

134 self.mixture_or_rapid_alternation 

135 and self.duration_at_least_2_weeks 

136 ): 

137 return True 

138 if is_false(self.mixture_or_rapid_alternation): 

139 return False 

140 if is_false(self.duration_at_least_2_weeks): 

141 return False 

142 return None 

143 

144 def is_complete(self) -> bool: 

145 return ( 

146 self.meets_criteria() is not None and self.field_contents_valid() 

147 ) 

148 

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

150 return """ 

151 {clinician_comments} 

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

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

154 {tr_is_complete} 

155 {date_pertains_to} 

156 {meets_criteria} 

157 </table> 

158 </div> 

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

160 {icd10_symptomatic_disclaimer} 

161 </div> 

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

163 <tr> 

164 <th width="80%">Question</th> 

165 <th width="20%">Answer</th> 

166 </tr> 

167 {mixture_or_rapid_alternation} 

168 {duration_at_least_2_weeks} 

169 </table> 

170 {ICD10_COPYRIGHT_DIV} 

171 """.format( 

172 clinician_comments=self.get_standard_clinician_comments_block( 

173 req, self.comments 

174 ), 

175 CssClass=CssClass, 

176 tr_is_complete=self.get_is_complete_tr(req), 

177 date_pertains_to=tr_qa( 

178 req.wappstring(AS.DATE_PERTAINS_TO), 

179 format_datetime( 

180 self.date_pertains_to, DateFormat.LONG_DATE, default=None 

181 ), 

182 ), 

183 meets_criteria=tr_qa( 

184 req.sstring(SS.MEETS_CRITERIA), 

185 get_true_false_none(req, self.meets_criteria()), 

186 ), 

187 icd10_symptomatic_disclaimer=req.wappstring( 

188 AS.ICD10_SYMPTOMATIC_DISCLAIMER 

189 ), 

190 mixture_or_rapid_alternation=self.get_twocol_bool_row_true_false( 

191 req, "mixture_or_rapid_alternation", self.wxstring(req, "a") 

192 ), 

193 duration_at_least_2_weeks=self.get_twocol_bool_row_true_false( 

194 req, "duration_at_least_2_weeks", self.wxstring(req, "b") 

195 ), 

196 ICD10_COPYRIGHT_DIV=ICD10_COPYRIGHT_DIV, 

197 )