Coverage for tasks/icd10schizotypal.py: 59%

63 statements  

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

1""" 

2camcops_server/tasks/icd10schizotypal.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 Any, List, Optional, Type 

30 

31from cardinal_pythonlib.datetimefunc import format_datetime 

32import cardinal_pythonlib.rnc_web as ws 

33from cardinal_pythonlib.stringfunc import strseq 

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 PV, 

42) 

43from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

44from camcops_server.cc_modules.cc_db import add_multiple_columns 

45from camcops_server.cc_modules.cc_html import get_yes_no_none, td, tr, tr_qa 

46from camcops_server.cc_modules.cc_request import CamcopsRequest 

47from camcops_server.cc_modules.cc_sqla_coltypes import ( 

48 BIT_CHECKER, 

49 mapped_camcops_column, 

50) 

51from camcops_server.cc_modules.cc_string import AS 

52from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

53from camcops_server.cc_modules.cc_task import ( 

54 Task, 

55 TaskHasClinicianMixin, 

56 TaskHasPatientMixin, 

57) 

58from camcops_server.cc_modules.cc_text import SS 

59 

60 

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

62# Icd10Schizotypal 

63# ============================================================================= 

64 

65 

66class Icd10Schizotypal( # type: ignore[misc] 

67 TaskHasClinicianMixin, 

68 TaskHasPatientMixin, 

69 Task, 

70): 

71 """ 

72 Server implementation of the ICD10-SZTYP task. 

73 """ 

74 

75 __tablename__ = "icd10schizotypal" 

76 shortname = "ICD10-SZTYP" 

77 info_filename_stem = "icd" 

78 

79 @classmethod 

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

81 add_multiple_columns( 

82 cls, 

83 "a", 

84 1, 

85 cls.N_A, 

86 Boolean, 

87 pv=PV.BIT, 

88 comment_fmt="Criterion A({n}), {s}", 

89 comment_strings=[ 

90 "inappropriate/constricted affect", 

91 "odd/eccentric/peculiar", 

92 "poor rapport/social withdrawal", 

93 "odd beliefs/magical thinking", 

94 "suspiciousness/paranoid ideas", 

95 "ruminations without inner resistance", 

96 "unusual perceptual experiences", 

97 "vague/circumstantial/metaphorical/over-elaborate/stereotyped thinking", # noqa 

98 "occasional transient quasi-psychotic episodes", 

99 ], 

100 ) 

101 

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

103 comment="Date the assessment pertains to" 

104 ) 

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

106 UnicodeText, comment="Clinician's comments" 

107 ) 

108 b: Mapped[Optional[bool]] = mapped_camcops_column( 

109 permitted_value_checker=BIT_CHECKER, 

110 comment="Criterion (B). True if: the subject has never met " 

111 "the criteria for any disorder in F20 (Schizophrenia).", 

112 ) 

113 

114 N_A = 9 

115 A_FIELDS = strseq("a", 1, N_A) 

116 

117 @staticmethod 

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

119 _ = req.gettext 

120 return "ICD-10 criteria for schizotypal disorder (F21)" 

121 

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

123 if not self.is_complete(): 

124 return CTV_INCOMPLETE 

125 c = self.meets_criteria() 

126 if c is None: 

127 category = "Unknown if met or not met" 

128 elif c: 

129 category = "Met" 

130 else: 

131 category = "Not met" 

132 infolist = [ 

133 CtvInfo( 

134 content=( 

135 "Pertains to: {}. Criteria for schizotypal " 

136 "disorder: {}.".format( 

137 format_datetime( 

138 self.date_pertains_to, DateFormat.LONG_DATE 

139 ), 

140 category, 

141 ) 

142 ) 

143 ) 

144 ] 

145 if self.comments: 

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

147 return infolist 

148 

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

150 return self.standard_task_summary_fields() + [ 

151 SummaryElement( 

152 name="meets_criteria", 

153 coltype=Boolean(), 

154 value=self.meets_criteria(), 

155 comment="Meets criteria for schizotypal disorder?", 

156 ) 

157 ] 

158 

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

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

161 if not self.is_complete(): 

162 return None 

163 return self.count_booleans(self.A_FIELDS) >= 4 and self.b 

164 

165 def is_complete(self) -> bool: 

166 return ( 

167 self.date_pertains_to is not None 

168 and self.all_fields_not_none(self.A_FIELDS) 

169 and self.b is not None 

170 and self.field_contents_valid() 

171 ) 

172 

173 def text_row(self, req: CamcopsRequest, wstringname: str) -> str: 

174 return tr( 

175 td(self.wxstring(req, wstringname)), 

176 td("", td_class=CssClass.SUBHEADING), 

177 literal=True, 

178 ) 

179 

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

181 q_a = self.text_row(req, "a") 

182 for i in range(1, self.N_A + 1): 

183 q_a += self.get_twocol_bool_row_true_false( 

184 req, "a" + str(i), self.wxstring(req, "a" + str(i)) 

185 ) 

186 q_a += self.get_twocol_bool_row_true_false( 

187 req, "b", self.wxstring(req, "b") 

188 ) 

189 h = """ 

190 {clinician_comments} 

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

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

193 {tr_is_complete} 

194 {date_pertains_to} 

195 {meets_criteria} 

196 </table> 

197 </div> 

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

199 <tr> 

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

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

202 </tr> 

203 {q_a} 

204 </table> 

205 {ICD10_COPYRIGHT_DIV} 

206 """.format( 

207 clinician_comments=self.get_standard_clinician_comments_block( 

208 req, self.comments 

209 ), 

210 CssClass=CssClass, 

211 tr_is_complete=self.get_is_complete_tr(req), 

212 date_pertains_to=tr_qa( 

213 req.wappstring(AS.DATE_PERTAINS_TO), 

214 format_datetime( 

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

216 ), 

217 ), 

218 meets_criteria=tr_qa( 

219 req.sstring(SS.MEETS_CRITERIA), 

220 get_yes_no_none(req, self.meets_criteria()), 

221 ), 

222 q_a=q_a, 

223 ICD10_COPYRIGHT_DIV=ICD10_COPYRIGHT_DIV, 

224 ) 

225 return h