Coverage for tasks/icd10schizotypal.py: 59%
63 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/icd10schizotypal.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28import datetime
29from typing import Any, List, Optional, Type
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
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
61# =============================================================================
62# Icd10Schizotypal
63# =============================================================================
66class Icd10Schizotypal( # type: ignore[misc]
67 TaskHasClinicianMixin,
68 TaskHasPatientMixin,
69 Task,
70):
71 """
72 Server implementation of the ICD10-SZTYP task.
73 """
75 __tablename__ = "icd10schizotypal"
76 shortname = "ICD10-SZTYP"
77 info_filename_stem = "icd"
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 )
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 )
114 N_A = 9
115 A_FIELDS = strseq("a", 1, N_A)
117 @staticmethod
118 def longname(req: "CamcopsRequest") -> str:
119 _ = req.gettext
120 return "ICD-10 criteria for schizotypal disorder (F21)"
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
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 ]
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
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 )
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 )
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