Coverage for tasks/icd10schizotypal.py: 60%

65 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/tasks/icd10schizotypal.py 

5 

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

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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

25 

26=============================================================================== 

27 

28""" 

29 

30from typing import Any, Dict, List, Optional, Tuple, Type 

31 

32from cardinal_pythonlib.datetimefunc import format_datetime 

33import cardinal_pythonlib.rnc_web as ws 

34from cardinal_pythonlib.stringfunc import strseq 

35from sqlalchemy.ext.declarative import DeclarativeMeta 

36from sqlalchemy.sql.schema import Column 

37from sqlalchemy.sql.sqltypes import Boolean, Date, UnicodeText 

38 

39from camcops_server.cc_modules.cc_constants import ( 

40 CssClass, 

41 DateFormat, 

42 ICD10_COPYRIGHT_DIV, 

43 PV, 

44) 

45from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo 

46from camcops_server.cc_modules.cc_db import add_multiple_columns 

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

48from camcops_server.cc_modules.cc_request import CamcopsRequest 

49from camcops_server.cc_modules.cc_sqla_coltypes import ( 

50 BIT_CHECKER, 

51 CamcopsColumn, 

52) 

53from camcops_server.cc_modules.cc_string import AS 

54from camcops_server.cc_modules.cc_summaryelement import SummaryElement 

55from camcops_server.cc_modules.cc_task import ( 

56 Task, 

57 TaskHasClinicianMixin, 

58 TaskHasPatientMixin, 

59) 

60from camcops_server.cc_modules.cc_text import SS 

61 

62 

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

64# Icd10Schizotypal 

65# ============================================================================= 

66 

67 

68class Icd10SchizotypalMetaclass(DeclarativeMeta): 

69 # noinspection PyInitNewSignature 

70 def __init__( 

71 cls: Type["Icd10Schizotypal"], 

72 name: str, 

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

74 classdict: Dict[str, Any], 

75 ) -> None: 

76 add_multiple_columns( 

77 cls, 

78 "a", 

79 1, 

80 cls.N_A, 

81 Boolean, 

82 pv=PV.BIT, 

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

84 comment_strings=[ 

85 "inappropriate/constricted affect", 

86 "odd/eccentric/peculiar", 

87 "poor rapport/social withdrawal", 

88 "odd beliefs/magical thinking", 

89 "suspiciousness/paranoid ideas", 

90 "ruminations without inner resistance", 

91 "unusual perceptual experiences", 

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

93 "occasional transient quasi-psychotic episodes", 

94 ], 

95 ) 

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

97 

98 

99class Icd10Schizotypal( 

100 TaskHasClinicianMixin, 

101 TaskHasPatientMixin, 

102 Task, 

103 metaclass=Icd10SchizotypalMetaclass, 

104): 

105 """ 

106 Server implementation of the ICD10-SZTYP task. 

107 """ 

108 

109 __tablename__ = "icd10schizotypal" 

110 shortname = "ICD10-SZTYP" 

111 info_filename_stem = "icd" 

112 

113 date_pertains_to = Column( 

114 "date_pertains_to", Date, comment="Date the assessment pertains to" 

115 ) 

116 comments = Column("comments", UnicodeText, comment="Clinician's comments") 

117 b = CamcopsColumn( 

118 "b", 

119 Boolean, 

120 permitted_value_checker=BIT_CHECKER, 

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

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

123 ) 

124 

125 N_A = 9 

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

127 

128 @staticmethod 

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

130 _ = req.gettext 

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

132 

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

134 if not self.is_complete(): 

135 return CTV_INCOMPLETE 

136 c = self.meets_criteria() 

137 if c is None: 

138 category = "Unknown if met or not met" 

139 elif c: 

140 category = "Met" 

141 else: 

142 category = "Not met" 

143 infolist = [ 

144 CtvInfo( 

145 content=( 

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

147 "disorder: {}.".format( 

148 format_datetime( 

149 self.date_pertains_to, DateFormat.LONG_DATE 

150 ), 

151 category, 

152 ) 

153 ) 

154 ) 

155 ] 

156 if self.comments: 

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

158 return infolist 

159 

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

161 return self.standard_task_summary_fields() + [ 

162 SummaryElement( 

163 name="meets_criteria", 

164 coltype=Boolean(), 

165 value=self.meets_criteria(), 

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

167 ) 

168 ] 

169 

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

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

172 if not self.is_complete(): 

173 return None 

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

175 

176 def is_complete(self) -> bool: 

177 return ( 

178 self.date_pertains_to is not None 

179 and self.all_fields_not_none(self.A_FIELDS) 

180 and self.b is not None 

181 and self.field_contents_valid() 

182 ) 

183 

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

185 return tr( 

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

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

188 literal=True, 

189 ) 

190 

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

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

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

194 q_a += self.get_twocol_bool_row_true_false( 

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

196 ) 

197 q_a += self.get_twocol_bool_row_true_false( 

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

199 ) 

200 h = """ 

201 {clinician_comments} 

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

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

204 {tr_is_complete} 

205 {date_pertains_to} 

206 {meets_criteria} 

207 </table> 

208 </div> 

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

210 <tr> 

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

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

213 </tr> 

214 {q_a} 

215 </table> 

216 {ICD10_COPYRIGHT_DIV} 

217 """.format( 

218 clinician_comments=self.get_standard_clinician_comments_block( 

219 req, self.comments 

220 ), 

221 CssClass=CssClass, 

222 tr_is_complete=self.get_is_complete_tr(req), 

223 date_pertains_to=tr_qa( 

224 req.wappstring(AS.DATE_PERTAINS_TO), 

225 format_datetime( 

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

227 ), 

228 ), 

229 meets_criteria=tr_qa( 

230 req.sstring(SS.MEETS_CRITERIA), 

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

232 ), 

233 q_a=q_a, 

234 ICD10_COPYRIGHT_DIV=ICD10_COPYRIGHT_DIV, 

235 ) 

236 return h