Coverage for cc_modules/cc_snomed.py: 60%

454 statements  

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

1r""" 

2camcops_server/cc_modules/cc_snomed.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**CamCOPS code to support SNOMED-CT.** 

27 

28Note that the licensing arrangements for SNOMED-CT mean that the actual codes 

29must be separate (and not part of the CamCOPS code). See the documentation. 

30 

31Some tests: 

32 

33.. code-block:: python 

34 

35 import logging 

36 from cardinal_pythonlib.logs import main_only_quicksetup_rootlogger 

37 from camcops_server.cc_modules.cc_request import get_command_line_request 

38 from camcops_server.cc_modules.cc_snomed import * 

39 from camcops_server.tasks.phq9 import Phq9 

40 main_only_quicksetup_rootlogger(level=logging.DEBUG) 

41 req = get_command_line_request() 

42 

43 # --------------------------------------------------------------------- 

44 # Read the XML, etc. 

45 # --------------------------------------------------------------------- 

46 

47 print(VALID_SNOMED_LOOKUPS) 

48 concepts = get_all_snomed_concepts(req.config.snomed_xml_filename) 

49 

50 # --------------------------------------------------------------------- 

51 # Test a task, and loading SNOMED data from XML via the CamCOPS config 

52 # --------------------------------------------------------------------- 

53 

54 phq9 = Phq9() 

55 print("\n".join(str(x) for x in phq9.get_snomed_codes(req))) 

56 phq9.q1 = 2 

57 phq9.q2 = 2 

58 phq9.q3 = 2 

59 phq9.q4 = 2 

60 phq9.q5 = 2 

61 phq9.q6 = 2 

62 phq9.q7 = 2 

63 phq9.q8 = 2 

64 phq9.q9 = 2 

65 phq9.q10 = 2 

66 print("\n".join(repr(x) for x in phq9.get_snomed_codes(req))) 

67 print("\n".join(str(x) for x in phq9.get_snomed_codes(req))) 

68 

69Note diagnostic coding maps: 

70 

71- https://www.nlm.nih.gov/research/umls/mapping_projects/icd9cm_to_snomedct.html 

72- https://www.nlm.nih.gov/research/umls/mapping_projects/snomedct_to_icd10cm.html 

73 

74Other testing: 

75 

76.. code-block:: bash 

77 

78 camcops_server dev_cli --verbose 

79 

80.. code-block:: python 

81 

82 from camcops_server.cc_modules.cc_snomed import * 

83 

84 athena_concepts = get_athena_concepts(config.athena_concept_tsv_filename) 

85 relationships = get_athena_concept_relationships(config.athena_concept_relationship_tsv_filename) 

86 rel_ids = set(r.relationship_id for r in relationships) 

87 icd9, icd10 = get_icd9cm_icd10_snomed_concepts(config.athena_concept_tsv_filename, config.athena_concept_relationship_tsv_filename) 

88 

89 ac = get_athena_concepts(config.athena_concept_tsv_filename, vocabulary_ids=[AthenaVocabularyId.SNOMED], concept_codes=["4303690"]) 

90 

91Regarding temporal coding: 

92 

93- SNOMED CT has some concepts relating to time (e.g. "time aspect", "single 

94 point in time", "date of birth", "date of event", "time (property)", "time 

95 (attribute)"). HOWEVER, note that SNOMED-CT isn't intended for recording 

96 event dates: 

97 

98 - https://confluence.ihtsdotools.org/display/DOCRSG/3.5.+Safely+representing+the+context+of+recorded+codes 

99 

100 "Contextual information that is not represented by SNOMED CT 

101 

102 Clinical statements that contain SNOMED CT Concept representations will 

103 be associated with some information which is not intended to be 

104 represented using SNOMED CT: 

105 

106 - Dates, times of an activity of recording and activity; 

107 - Quantitative information including ranges and durations; 

108 - Identifiers or names of authors, providers of information or other 

109 parties involved in a recorded activity. 

110 - SNOMED CT is not intended to represent this information. Appropriate 

111 constructs in a standardized or proprietary record architecture 

112 should be used to relate this information to SNOMED CT encoded 

113 clinical statements." 

114 

115 - https://confluence.ihtsdotools.org/display/DOCRSG/3.4.+Record+architectures%2C+structures+and+semantics 

116 

117 "Using SNOMED CT in standard architectures 

118 

119 ... 

120 

121 Each health record component has the potential to include: 

122 

123 - Dates and times of actual and planned events. 

124 - Associations with people, organizations, devices and other entities 

125 that participate or are used in relations to a recorded event or 

126 plan. 

127 - Codes or other representations that name or provide the semantic 

128 information container, link, or statement: 

129 

130 - SNOMED CT fulfills this role in a structured health record. 

131 

132 - Additional data including text, numeric values, images and other 

133 digital data." 

134 

135- That explains why the grammar has no explanation of how to represent 

136 dates/times, perhaps! 

137 

138""" # noqa 

139 

140from collections import OrderedDict 

141import csv 

142import logging 

143from typing import Dict, List, Optional, Set, Tuple, Union 

144import xml.etree.cElementTree as ElementTree 

145 

146from cardinal_pythonlib.athena_ohdsi import ( 

147 AthenaConceptRow, 

148 AthenaRelationshipId, 

149 AthenaVocabularyId, 

150 get_athena_concept_relationships, 

151 get_athena_concepts, 

152) 

153from cardinal_pythonlib.logs import BraceStyleAdapter 

154from cardinal_pythonlib.reprfunc import simple_repr 

155 

156# noinspection PyUnresolvedReferences 

157from cardinal_pythonlib.snomed import ( # noqa: F401 

158 SnomedAttribute, 

159 SnomedAttributeGroup, 

160 SnomedAttributeSet, 

161 SnomedConcept as SnomedConceptCardinalPythonlib, 

162 SnomedExpression as SnomedExpressionCardinalPythonlib, 

163 SnomedFocusConcept, 

164 SnomedRefinement, 

165 SnomedValue, 

166) 

167 

168from camcops_server.cc_modules.cc_cache import cache_region_static, fkg 

169from camcops_server.cc_modules.cc_xml import XmlDataTypes, XmlElement 

170 

171log = BraceStyleAdapter(logging.getLogger(__name__)) 

172 

173 

174# ============================================================================= 

175# Constants 

176# ============================================================================= 

177 

178# ----------------------------------------------------------------------------- 

179# Internal 

180# ----------------------------------------------------------------------------- 

181 

182SNOMED_XML_NAME = "snomed_ct_expression" 

183 

184# ----------------------------------------------------------------------------- 

185# ICD-9-CM; from the client: camcops --print_icd9_codes 

186# ----------------------------------------------------------------------------- 

187 

188CLIENT_ICD9CM_CODES = set( 

189 """ 

190290.0 290.1 290.10 290.11 290.12 290.13 290.2 290.20 290.21 290.3 290.4 290.40 

191290.41 290.42 290.43 290.8 290.9 291.0 291.1 291.2 291.3 291.4 291.5 291.8 

192291.81 291.82 291.89 291.9 292.0 292.1 292.11 292.12 292.2 292.8 292.81 292.82 

193292.83 292.84 292.85 292.89 292.9 293.0 293.1 293.8 293.81 293.82 293.83 293.84 

194293.89 293.9 294.0 294.1 294.10 294.11 294.2 294.20 294.21 294.8 294.9 295.0 

195295.00 295.01 295.02 295.03 295.04 295.05 295.1 295.10 295.11 295.12 295.13 

196295.14 295.15 295.2 295.20 295.21 295.22 295.23 295.24 295.25 295.3 295.30 

197295.31 295.32 295.33 295.34 295.35 295.4 295.40 295.41 295.42 295.43 295.44 

198295.45 295.5 295.50 295.51 295.52 295.53 295.54 295.55 295.6 295.60 295.61 

199295.62 295.63 295.64 295.65 295.7 295.70 295.71 295.72 295.73 295.74 295.75 

200295.8 295.80 295.81 295.82 295.83 295.84 295.85 295.9 295.90 295.91 295.92 

201295.93 295.94 295.95 296.0 296.00 296.01 296.02 296.03 296.04 296.05 296.06 

202296.1 296.10 296.11 296.12 296.13 296.14 296.15 296.16 296.2 296.20 296.21 

203296.22 296.23 296.24 296.25 296.26 296.3 296.30 296.31 296.32 296.33 296.34 

204296.35 296.36 296.4 296.40 296.41 296.42 296.43 296.44 296.45 296.46 296.5 

205296.50 296.51 296.52 296.53 296.54 296.55 296.56 296.6 296.60 296.61 296.62 

206296.63 296.64 296.65 296.66 296.7 296.8 296.80 296.81 296.82 296.89 296.9 

207296.90 296.99 297.0 297.1 297.2 297.3 297.8 297.9 298.0 298.1 298.2 298.3 298.4 

208298.8 298.9 299.0 299.00 299.01 299.1 299.10 299.11 299.8 299.80 299.81 299.9 

209299.90 299.91 300.0 300.00 300.01 300.02 300.09 300.1 300.10 300.11 300.12 

210300.13 300.14 300.15 300.16 300.19 300.2 300.20 300.21 300.22 300.23 300.29 

211300.3 300.4 300.5 300.6 300.7 300.8 300.81 300.82 300.89 300.9 301.0 301.1 

212301.10 301.11 301.12 301.13 301.2 301.20 301.21 301.22 301.3 301.4 301.5 301.50 

213301.51 301.59 301.6 301.7 301.8 301.81 301.82 301.83 301.84 301.89 301.9 302.0 

214302.1 302.2 302.3 302.4 302.5 302.50 302.51 302.52 302.53 302.6 302.7 302.70 

215302.71 302.72 302.73 302.74 302.75 302.76 302.79 302.8 302.81 302.82 302.83 

216302.84 302.85 302.89 302.9 303.0 303.00 303.01 303.02 303.03 303.0x 303.9 

217303.90 303.91 303.92 303.93 304.0 304.00 304.01 304.02 304.03 304.1 304.10 

218304.11 304.12 304.13 304.2 304.20 304.21 304.22 304.23 304.3 304.30 304.31 

219304.32 304.33 304.4 304.40 304.41 304.42 304.43 304.5 304.50 304.51 304.52 

220304.53 304.6 304.60 304.61 304.62 304.63 304.7 304.70 304.71 304.72 304.73 

221304.8 304.80 304.81 304.82 304.83 304.9 304.90 304.91 304.92 304.93 305.0 

222305.00 305.01 305.02 305.03 305.0x 305.1 305.10 305.11 305.12 305.13 305.2 

223305.20 305.21 305.22 305.23 305.2x 305.3 305.30 305.31 305.32 305.33 305.3x 

224305.4 305.40 305.41 305.42 305.43 305.4x 305.5 305.50 305.51 305.52 305.53 

225305.5x 305.6 305.60 305.61 305.62 305.63 305.6x 305.7 305.70 305.71 305.72 

226305.73 305.7x 305.8 305.80 305.81 305.82 305.83 305.8x 305.9 305.90 305.91 

227305.92 305.93 305.9x 306.0 306.1 306.2 306.3 306.4 306.50 306.51 306.52 306.53 

228306.59 306.6 306.7 306.8 306.9 307.0 307.1 307.2 307.20 307.21 307.22 307.23 

229307.3 307.4 307.40 307.41 307.42 307.43 307.44 307.45 307.46 307.47 307.48 

230307.49 307.5 307.50 307.51 307.52 307.53 307.54 307.59 307.6 307.7 307.8 307.80 

231307.81 307.89 307.9 308.0 308.1 308.2 308.3 308.4 308.9 309.0 309.1 309.2 

232309.21 309.22 309.23 309.24 309.28 309.29 309.3 309.4 309.8 309.81 309.82 

233309.83 309.89 309.9 310.0 310.1 310.2 310.8 310.81 310.89 310.9 311 312.0 

234312.00 312.01 312.02 312.03 312.1 312.10 312.11 312.12 312.13 312.2 312.20 

235312.21 312.22 312.23 312.3 312.30 312.31 312.32 312.33 312.34 312.35 312.39 

236312.4 312.8 312.81 312.82 312.89 312.9 313.0 313.1 313.2 313.21 313.22 313.23 

237313.3 313.8 313.81 313.82 313.83 313.89 313.9 314.0 314.00 314.01 314.1 314.2 

238314.8 314.9 315.0 315.00 315.01 315.02 315.09 315.1 315.2 315.3 315.31 315.32 

239315.34 315.35 315.39 315.4 315.5 315.8 315.9 316 317 318.0 318.1 318.2 319 

240V71.09 

241""".split() 

242) 

243 

244# ----------------------------------------------------------------------------- 

245# ICD-10; from the client: camcops --print_icd10_codes 

246# ----------------------------------------------------------------------------- 

247 

248CLIENT_ICD10_CODES = set( 

249 """ 

250F00 F00.0 F00.00 F00.000 F00.001 F00.002 F00.01 F00.010 F00.011 F00.012 F00.02 

251F00.020 F00.021 F00.022 F00.03 F00.030 F00.031 F00.032 F00.04 F00.040 F00.041 

252F00.042 F00.1 F00.10 F00.100 F00.101 F00.102 F00.11 F00.110 F00.111 F00.112 

253F00.12 F00.120 F00.121 F00.122 F00.13 F00.130 F00.131 F00.132 F00.14 F00.140 

254F00.141 F00.142 F00.2 F00.20 F00.200 F00.201 F00.202 F00.21 F00.210 F00.211 

255F00.212 F00.22 F00.220 F00.221 F00.222 F00.23 F00.230 F00.231 F00.232 F00.24 

256F00.240 F00.241 F00.242 F00.9 F00.90 F00.900 F00.901 F00.902 F00.91 F00.910 

257F00.911 F00.912 F00.92 F00.920 F00.921 F00.922 F00.93 F00.930 F00.931 F00.932 

258F00.94 F00.940 F00.941 F00.942 F01 F01.0 F01.00 F01.000 F01.001 F01.002 F01.01 

259F01.010 F01.011 F01.012 F01.02 F01.020 F01.021 F01.022 F01.03 F01.030 F01.031 

260F01.032 F01.04 F01.040 F01.041 F01.042 F01.1 F01.10 F01.100 F01.101 F01.102 

261F01.11 F01.110 F01.111 F01.112 F01.12 F01.120 F01.121 F01.122 F01.13 F01.130 

262F01.131 F01.132 F01.14 F01.140 F01.141 F01.142 F01.2 F01.20 F01.200 F01.201 

263F01.202 F01.21 F01.210 F01.211 F01.212 F01.22 F01.220 F01.221 F01.222 F01.23 

264F01.230 F01.231 F01.232 F01.24 F01.240 F01.241 F01.242 F01.3 F01.30 F01.300 

265F01.301 F01.302 F01.31 F01.310 F01.311 F01.312 F01.32 F01.320 F01.321 F01.322 

266F01.33 F01.330 F01.331 F01.332 F01.34 F01.340 F01.341 F01.342 F01.8 F01.80 

267F01.800 F01.801 F01.802 F01.81 F01.810 F01.811 F01.812 F01.82 F01.820 F01.821 

268F01.822 F01.83 F01.830 F01.831 F01.832 F01.84 F01.840 F01.841 F01.842 F01.9 

269F01.90 F01.900 F01.901 F01.902 F01.91 F01.910 F01.911 F01.912 F01.92 F01.920 

270F01.921 F01.922 F01.93 F01.930 F01.931 F01.932 F01.94 F01.940 F01.941 F01.942 

271F02 F02.0 F02.00 F02.000 F02.001 F02.002 F02.01 F02.010 F02.011 F02.012 F02.02 

272F02.020 F02.021 F02.022 F02.03 F02.030 F02.031 F02.032 F02.04 F02.040 F02.041 

273F02.042 F02.1 F02.10 F02.100 F02.101 F02.102 F02.11 F02.110 F02.111 F02.112 

274F02.12 F02.120 F02.121 F02.122 F02.13 F02.130 F02.131 F02.132 F02.14 F02.140 

275F02.141 F02.142 F02.2 F02.20 F02.200 F02.201 F02.202 F02.21 F02.210 F02.211 

276F02.212 F02.22 F02.220 F02.221 F02.222 F02.23 F02.230 F02.231 F02.232 F02.24 

277F02.240 F02.241 F02.242 F02.3 F02.30 F02.300 F02.301 F02.302 F02.31 F02.310 

278F02.311 F02.312 F02.32 F02.320 F02.321 F02.322 F02.33 F02.330 F02.331 F02.332 

279F02.34 F02.340 F02.341 F02.342 F02.4 F02.40 F02.400 F02.401 F02.402 F02.41 

280F02.410 F02.411 F02.412 F02.42 F02.420 F02.421 F02.422 F02.43 F02.430 F02.431 

281F02.432 F02.44 F02.440 F02.441 F02.442 F02.8 F02.80 F02.800 F02.801 F02.802 

282F02.81 F02.810 F02.811 F02.812 F02.82 F02.820 F02.821 F02.822 F02.83 F02.830 

283F02.831 F02.832 F02.84 F02.840 F02.841 F02.842 F03 F03.0 F03.00 F03.000 F03.001 

284F03.002 F03.01 F03.010 F03.011 F03.012 F03.02 F03.020 F03.021 F03.022 F03.03 

285F03.030 F03.031 F03.032 F03.04 F03.040 F03.041 F03.042 F04 F05 F05.0 F05.1 

286F05.8 F05.9 F06 F06.0 F06.1 F06.2 F06.3 F06.4 F06.5 F06.6 F06.7 F06.8 F06.9 F07 

287F07.0 F07.1 F07.2 F07.8 F07.9 F09 F10 F10.0 F10.00 F10.01 F10.02 F10.03 F10.04 

288F10.05 F10.06 F10.07 F10.1 F10.2 F10.20 F10.200 F10.201 F10.202 F10.21 F10.22 

289F10.23 F10.24 F10.241 F10.242 F10.25 F10.26 F10.3 F10.30 F10.31 F10.4 F10.40 

290F10.41 F10.5 F10.50 F10.51 F10.52 F10.53 F10.54 F10.55 F10.56 F10.6 F10.7 

291F10.70 F10.71 F10.72 F10.73 F10.74 F10.75 F10.8 F10.9 F11 F11.0 F11.00 F11.01 

292F11.02 F11.03 F11.04 F11.05 F11.06 F11.1 F11.2 F11.20 F11.200 F11.201 F11.202 

293F11.21 F11.22 F11.23 F11.24 F11.241 F11.242 F11.25 F11.26 F11.3 F11.30 F11.31 

294F11.4 F11.40 F11.41 F11.5 F11.50 F11.51 F11.52 F11.53 F11.54 F11.55 F11.56 

295F11.6 F11.7 F11.70 F11.71 F11.72 F11.73 F11.74 F11.75 F11.8 F11.9 F12 F12.0 

296F12.00 F12.01 F12.02 F12.03 F12.04 F12.05 F12.06 F12.1 F12.2 F12.20 F12.200 

297F12.201 F12.202 F12.21 F12.22 F12.23 F12.24 F12.241 F12.242 F12.25 F12.26 F12.3 

298F12.30 F12.31 F12.4 F12.40 F12.41 F12.5 F12.50 F12.51 F12.52 F12.53 F12.54 

299F12.55 F12.56 F12.6 F12.7 F12.70 F12.71 F12.72 F12.73 F12.74 F12.75 F12.8 F12.9 

300F13 F13.0 F13.00 F13.01 F13.02 F13.03 F13.04 F13.05 F13.06 F13.1 F13.2 F13.20 

301F13.200 F13.201 F13.202 F13.21 F13.22 F13.23 F13.24 F13.241 F13.242 F13.25 

302F13.26 F13.3 F13.30 F13.31 F13.4 F13.40 F13.41 F13.5 F13.50 F13.51 F13.52 

303F13.53 F13.54 F13.55 F13.56 F13.6 F13.7 F13.70 F13.71 F13.72 F13.73 F13.74 

304F13.75 F13.8 F13.9 F14 F14.0 F14.00 F14.01 F14.02 F14.03 F14.04 F14.05 F14.06 

305F14.1 F14.2 F14.20 F14.200 F14.201 F14.202 F14.21 F14.22 F14.23 F14.24 F14.241 

306F14.242 F14.25 F14.26 F14.3 F14.30 F14.31 F14.4 F14.40 F14.41 F14.5 F14.50 

307F14.51 F14.52 F14.53 F14.54 F14.55 F14.56 F14.6 F14.7 F14.70 F14.71 F14.72 

308F14.73 F14.74 F14.75 F14.8 F14.9 F15 F15.0 F15.00 F15.01 F15.02 F15.03 F15.04 

309F15.05 F15.06 F15.1 F15.2 F15.20 F15.200 F15.201 F15.202 F15.21 F15.22 F15.23 

310F15.24 F15.241 F15.242 F15.25 F15.26 F15.3 F15.30 F15.31 F15.4 F15.40 F15.41 

311F15.5 F15.50 F15.51 F15.52 F15.53 F15.54 F15.55 F15.56 F15.6 F15.7 F15.70 

312F15.71 F15.72 F15.73 F15.74 F15.75 F15.8 F15.9 F16 F16.0 F16.00 F16.01 F16.02 

313F16.03 F16.04 F16.05 F16.06 F16.1 F16.2 F16.20 F16.200 F16.201 F16.202 F16.21 

314F16.22 F16.23 F16.24 F16.241 F16.242 F16.25 F16.26 F16.3 F16.30 F16.31 F16.4 

315F16.40 F16.41 F16.5 F16.50 F16.51 F16.52 F16.53 F16.54 F16.55 F16.56 F16.6 

316F16.7 F16.70 F16.71 F16.72 F16.73 F16.74 F16.75 F16.8 F16.9 F17 F17.0 F17.00 

317F17.01 F17.02 F17.03 F17.04 F17.05 F17.06 F17.1 F17.2 F17.20 F17.200 F17.201 

318F17.202 F17.21 F17.22 F17.23 F17.24 F17.241 F17.242 F17.25 F17.26 F17.3 F17.30 

319F17.31 F17.4 F17.40 F17.41 F17.5 F17.50 F17.51 F17.52 F17.53 F17.54 F17.55 

320F17.56 F17.6 F17.7 F17.70 F17.71 F17.72 F17.73 F17.74 F17.75 F17.8 F17.9 F18 

321F18.0 F18.00 F18.01 F18.02 F18.03 F18.04 F18.05 F18.06 F18.1 F18.2 F18.20 

322F18.200 F18.201 F18.202 F18.21 F18.22 F18.23 F18.24 F18.241 F18.242 F18.25 

323F18.26 F18.3 F18.30 F18.31 F18.4 F18.40 F18.41 F18.5 F18.50 F18.51 F18.52 

324F18.53 F18.54 F18.55 F18.56 F18.6 F18.7 F18.70 F18.71 F18.72 F18.73 F18.74 

325F18.75 F18.8 F18.9 F19 F19.0 F19.00 F19.01 F19.02 F19.03 F19.04 F19.05 F19.06 

326F19.1 F19.2 F19.20 F19.200 F19.201 F19.202 F19.21 F19.22 F19.23 F19.24 F19.241 

327F19.242 F19.25 F19.26 F19.3 F19.30 F19.31 F19.4 F19.40 F19.41 F19.5 F19.50 

328F19.51 F19.52 F19.53 F19.54 F19.55 F19.56 F19.6 F19.7 F19.70 F19.71 F19.72 

329F19.73 F19.74 F19.75 F19.8 F19.9 F20 F20.0 F20.1 F20.2 F20.3 F20.4 F20.5 F20.6 

330F20.8 F20.9 F21 F22 F22.0 F22.8 F22.9 F23 F23.0 F23.1 F23.2 F23.3 F23.8 F23.9 

331F23.90 F23.91 F24 F25 F25.0 F25.00 F25.01 F25.1 F25.10 F25.11 F25.2 F25.20 

332F25.21 F25.8 F25.80 F25.81 F25.9 F25.90 F25.91 F28 F29 F30 F30.0 F30.1 F30.2 

333F30.20 F30.21 F30.8 F30.9 F31 F31.0 F31.1 F31.2 F31.20 F31.21 F31.3 F31.30 

334F31.31 F31.4 F31.5 F31.50 F31.51 F31.6 F31.7 F31.8 F31.9 F32 F32.0 F32.00 

335F32.01 F32.1 F32.10 F32.11 F32.2 F32.3 F32.30 F32.31 F32.8 F32.9 F33 F33.0 

336F33.00 F33.01 F33.1 F33.10 F33.11 F33.2 F33.3 F33.30 F33.31 F33.4 F33.8 F33.9 

337F34 F34.0 F34.1 F34.8 F34.9 F38 F38.0 F38.00 F38.1 F38.10 F38.8 F39 F40 F40.0 

338F40.00 F40.01 F40.1 F40.2 F40.8 F40.9 F41 F41.0 F41.00 F41.01 F41.1 F41.2 F41.3 

339F41.8 F41.9 F42 F42.0 F42.1 F42.2 F42.8 F42.9 F43 F43.0 F43.00 F43.01 F43.02 

340F43.1 F43.2 F43.20 F43.21 F43.22 F43.23 F43.24 F43.25 F43.28 F43.8 F43.9 F44 

341F44.0 F44.1 F44.2 F44.3 F44.4 F44.5 F44.6 F44.7 F44.8 F44.80 F44.81 F44.82 

342F44.88 F44.9 F45 F45.0 F45.1 F45.2 F45.3 F45.30 F45.31 F45.32 F45.33 F45.34 

343F45.38 F45.4 F45.8 F45.9 F48 F48.0 F48.1 F48.8 F48.9 F50 F50.0 F50.1 F50.2 

344F50.3 F50.4 F50.5 F50.8 F50.9 F51 F51.0 F51.1 F51.2 F51.3 F51.4 F51.5 F51.8 

345F51.9 F52 F52.0 F52.1 F52.10 F52.11 F52.2 F52.3 F52.4 F52.5 F52.6 F52.7 F52.8 

346F52.9 F53 F53.0 F53.1 F53.8 F53.9 F54 F55 F59 F60 F60.0 F60.1 F60.2 F60.3 

347F60.30 F60.31 F60.4 F60.5 F60.6 F60.7 F60.8 F60.9 F61 F62 F62.0 F62.1 F62.8 

348F62.9 F63 F63.0 F63.1 F63.2 F63.3 F63.8 F63.9 F64 F64.0 F64.1 F64.2 F64.8 F64.9 

349F65 F65.0 F65.1 F65.2 F65.3 F65.4 F65.5 F65.6 F65.8 F65.9 F66 F66.0 F66.1 F66.2 

350F66.8 F66.9 F66.90 F66.91 F66.92 F66.98 F68 F68.0 F68.1 F68.8 F69 F70 F70.0 

351F70.1 F70.8 F70.9 F71 F71.0 F71.1 F71.8 F71.9 F72 F72.0 F72.1 F72.8 F72.9 F73 

352F73.0 F73.1 F73.8 F73.9 F78 F78.0 F78.1 F78.8 F78.9 F79 F79.0 F79.1 F79.8 F79.9 

353F80 F80.0 F80.1 F80.2 F80.3 F80.8 F80.9 F81 F81.0 F81.1 F81.2 F81.3 F81.8 F81.9 

354F82 F83 F84 F84.0 F84.1 F84.10 F84.11 F84.12 F84.2 F84.3 F84.4 F84.5 F84.8 

355F84.9 F88 F89 F90 F90.0 F90.1 F90.8 F90.9 F91 F91.0 F91.1 F91.2 F91.3 F91.8 

356F91.9 F92 F92.0 F92.8 F92.9 F93 F93.0 F93.1 F93.2 F93.3 F93.8 F93.80 F93.9 F94 

357F94.0 F94.1 F94.2 F94.8 F94.9 F95 F95.0 F95.1 F95.2 F95.8 F95.9 F98 F98.0 

358F98.00 F98.01 F98.02 F98.1 F98.10 F98.11 F98.12 F98.2 F98.3 F98.4 F98.40 F98.41 

359F98.42 F98.5 F98.6 F98.8 F98.9 F99 R40 R40.0 R40.1 R40.2 R41 R41.0 R41.1 R41.2 

360R41.3 R41.8 R42 R43 R43.0 R43.1 R43.2 R43.8 R44 R44.0 R44.1 R44.2 R44.3 R44.8 

361R45 R45.0 R45.1 R45.2 R45.3 R45.4 R45.5 R45.6 R45.7 R45.8 R46 R46.0 R46.1 R46.2 

362R46.3 R46.4 R46.5 R46.6 R46.7 R46.8 X60 X60.0 X60.00 X60.01 X60.02 X60.03 

363X60.04 X60.08 X60.09 X60.1 X60.10 X60.11 X60.12 X60.13 X60.14 X60.18 X60.19 

364X60.2 X60.20 X60.21 X60.22 X60.23 X60.24 X60.28 X60.29 X60.3 X60.30 X60.31 

365X60.32 X60.33 X60.34 X60.38 X60.39 X60.4 X60.40 X60.41 X60.42 X60.43 X60.44 

366X60.48 X60.49 X60.5 X60.50 X60.51 X60.52 X60.53 X60.54 X60.58 X60.59 X60.6 

367X60.60 X60.61 X60.62 X60.63 X60.64 X60.68 X60.69 X60.7 X60.70 X60.71 X60.72 

368X60.73 X60.74 X60.78 X60.79 X60.8 X60.80 X60.81 X60.82 X60.83 X60.84 X60.88 

369X60.89 X60.9 X60.90 X60.91 X60.92 X60.93 X60.94 X60.98 X60.99 X61 X61.0 X61.00 

370X61.01 X61.02 X61.03 X61.04 X61.08 X61.09 X61.1 X61.10 X61.11 X61.12 X61.13 

371X61.14 X61.18 X61.19 X61.2 X61.20 X61.21 X61.22 X61.23 X61.24 X61.28 X61.29 

372X61.3 X61.30 X61.31 X61.32 X61.33 X61.34 X61.38 X61.39 X61.4 X61.40 X61.41 

373X61.42 X61.43 X61.44 X61.48 X61.49 X61.5 X61.50 X61.51 X61.52 X61.53 X61.54 

374X61.58 X61.59 X61.6 X61.60 X61.61 X61.62 X61.63 X61.64 X61.68 X61.69 X61.7 

375X61.70 X61.71 X61.72 X61.73 X61.74 X61.78 X61.79 X61.8 X61.80 X61.81 X61.82 

376X61.83 X61.84 X61.88 X61.89 X61.9 X61.90 X61.91 X61.92 X61.93 X61.94 X61.98 

377X61.99 X62 X62.0 X62.00 X62.01 X62.02 X62.03 X62.04 X62.08 X62.09 X62.1 X62.10 

378X62.11 X62.12 X62.13 X62.14 X62.18 X62.19 X62.2 X62.20 X62.21 X62.22 X62.23 

379X62.24 X62.28 X62.29 X62.3 X62.30 X62.31 X62.32 X62.33 X62.34 X62.38 X62.39 

380X62.4 X62.40 X62.41 X62.42 X62.43 X62.44 X62.48 X62.49 X62.5 X62.50 X62.51 

381X62.52 X62.53 X62.54 X62.58 X62.59 X62.6 X62.60 X62.61 X62.62 X62.63 X62.64 

382X62.68 X62.69 X62.7 X62.70 X62.71 X62.72 X62.73 X62.74 X62.78 X62.79 X62.8 

383X62.80 X62.81 X62.82 X62.83 X62.84 X62.88 X62.89 X62.9 X62.90 X62.91 X62.92 

384X62.93 X62.94 X62.98 X62.99 X63 X63.0 X63.00 X63.01 X63.02 X63.03 X63.04 X63.08 

385X63.09 X63.1 X63.10 X63.11 X63.12 X63.13 X63.14 X63.18 X63.19 X63.2 X63.20 

386X63.21 X63.22 X63.23 X63.24 X63.28 X63.29 X63.3 X63.30 X63.31 X63.32 X63.33 

387X63.34 X63.38 X63.39 X63.4 X63.40 X63.41 X63.42 X63.43 X63.44 X63.48 X63.49 

388X63.5 X63.50 X63.51 X63.52 X63.53 X63.54 X63.58 X63.59 X63.6 X63.60 X63.61 

389X63.62 X63.63 X63.64 X63.68 X63.69 X63.7 X63.70 X63.71 X63.72 X63.73 X63.74 

390X63.78 X63.79 X63.8 X63.80 X63.81 X63.82 X63.83 X63.84 X63.88 X63.89 X63.9 

391X63.90 X63.91 X63.92 X63.93 X63.94 X63.98 X63.99 X64 X64.0 X64.00 X64.01 X64.02 

392X64.03 X64.04 X64.08 X64.09 X64.1 X64.10 X64.11 X64.12 X64.13 X64.14 X64.18 

393X64.19 X64.2 X64.20 X64.21 X64.22 X64.23 X64.24 X64.28 X64.29 X64.3 X64.30 

394X64.31 X64.32 X64.33 X64.34 X64.38 X64.39 X64.4 X64.40 X64.41 X64.42 X64.43 

395X64.44 X64.48 X64.49 X64.5 X64.50 X64.51 X64.52 X64.53 X64.54 X64.58 X64.59 

396X64.6 X64.60 X64.61 X64.62 X64.63 X64.64 X64.68 X64.69 X64.7 X64.70 X64.71 

397X64.72 X64.73 X64.74 X64.78 X64.79 X64.8 X64.80 X64.81 X64.82 X64.83 X64.84 

398X64.88 X64.89 X64.9 X64.90 X64.91 X64.92 X64.93 X64.94 X64.98 X64.99 X65 X65.0 

399X65.00 X65.01 X65.02 X65.03 X65.04 X65.08 X65.09 X65.1 X65.10 X65.11 X65.12 

400X65.13 X65.14 X65.18 X65.19 X65.2 X65.20 X65.21 X65.22 X65.23 X65.24 X65.28 

401X65.29 X65.3 X65.30 X65.31 X65.32 X65.33 X65.34 X65.38 X65.39 X65.4 X65.40 

402X65.41 X65.42 X65.43 X65.44 X65.48 X65.49 X65.5 X65.50 X65.51 X65.52 X65.53 

403X65.54 X65.58 X65.59 X65.6 X65.60 X65.61 X65.62 X65.63 X65.64 X65.68 X65.69 

404X65.7 X65.70 X65.71 X65.72 X65.73 X65.74 X65.78 X65.79 X65.8 X65.80 X65.81 

405X65.82 X65.83 X65.84 X65.88 X65.89 X65.9 X65.90 X65.91 X65.92 X65.93 X65.94 

406X65.98 X65.99 X66 X66.0 X66.00 X66.01 X66.02 X66.03 X66.04 X66.08 X66.09 X66.1 

407X66.10 X66.11 X66.12 X66.13 X66.14 X66.18 X66.19 X66.2 X66.20 X66.21 X66.22 

408X66.23 X66.24 X66.28 X66.29 X66.3 X66.30 X66.31 X66.32 X66.33 X66.34 X66.38 

409X66.39 X66.4 X66.40 X66.41 X66.42 X66.43 X66.44 X66.48 X66.49 X66.5 X66.50 

410X66.51 X66.52 X66.53 X66.54 X66.58 X66.59 X66.6 X66.60 X66.61 X66.62 X66.63 

411X66.64 X66.68 X66.69 X66.7 X66.70 X66.71 X66.72 X66.73 X66.74 X66.78 X66.79 

412X66.8 X66.80 X66.81 X66.82 X66.83 X66.84 X66.88 X66.89 X66.9 X66.90 X66.91 

413X66.92 X66.93 X66.94 X66.98 X66.99 X67 X67.0 X67.00 X67.01 X67.02 X67.03 X67.04 

414X67.08 X67.09 X67.1 X67.10 X67.11 X67.12 X67.13 X67.14 X67.18 X67.19 X67.2 

415X67.20 X67.21 X67.22 X67.23 X67.24 X67.28 X67.29 X67.3 X67.30 X67.31 X67.32 

416X67.33 X67.34 X67.38 X67.39 X67.4 X67.40 X67.41 X67.42 X67.43 X67.44 X67.48 

417X67.49 X67.5 X67.50 X67.51 X67.52 X67.53 X67.54 X67.58 X67.59 X67.6 X67.60 

418X67.61 X67.62 X67.63 X67.64 X67.68 X67.69 X67.7 X67.70 X67.71 X67.72 X67.73 

419X67.74 X67.78 X67.79 X67.8 X67.80 X67.81 X67.82 X67.83 X67.84 X67.88 X67.89 

420X67.9 X67.90 X67.91 X67.92 X67.93 X67.94 X67.98 X67.99 X68 X68.0 X68.00 X68.01 

421X68.02 X68.03 X68.04 X68.08 X68.09 X68.1 X68.10 X68.11 X68.12 X68.13 X68.14 

422X68.18 X68.19 X68.2 X68.20 X68.21 X68.22 X68.23 X68.24 X68.28 X68.29 X68.3 

423X68.30 X68.31 X68.32 X68.33 X68.34 X68.38 X68.39 X68.4 X68.40 X68.41 X68.42 

424X68.43 X68.44 X68.48 X68.49 X68.5 X68.50 X68.51 X68.52 X68.53 X68.54 X68.58 

425X68.59 X68.6 X68.60 X68.61 X68.62 X68.63 X68.64 X68.68 X68.69 X68.7 X68.70 

426X68.71 X68.72 X68.73 X68.74 X68.78 X68.79 X68.8 X68.80 X68.81 X68.82 X68.83 

427X68.84 X68.88 X68.89 X68.9 X68.90 X68.91 X68.92 X68.93 X68.94 X68.98 X68.99 X69 

428X69.0 X69.00 X69.01 X69.02 X69.03 X69.04 X69.08 X69.09 X69.1 X69.10 X69.11 

429X69.12 X69.13 X69.14 X69.18 X69.19 X69.2 X69.20 X69.21 X69.22 X69.23 X69.24 

430X69.28 X69.29 X69.3 X69.30 X69.31 X69.32 X69.33 X69.34 X69.38 X69.39 X69.4 

431X69.40 X69.41 X69.42 X69.43 X69.44 X69.48 X69.49 X69.5 X69.50 X69.51 X69.52 

432X69.53 X69.54 X69.58 X69.59 X69.6 X69.60 X69.61 X69.62 X69.63 X69.64 X69.68 

433X69.69 X69.7 X69.70 X69.71 X69.72 X69.73 X69.74 X69.78 X69.79 X69.8 X69.80 

434X69.81 X69.82 X69.83 X69.84 X69.88 X69.89 X69.9 X69.90 X69.91 X69.92 X69.93 

435X69.94 X69.98 X69.99 X70 X70.0 X70.00 X70.01 X70.02 X70.03 X70.04 X70.08 X70.09 

436X70.1 X70.10 X70.11 X70.12 X70.13 X70.14 X70.18 X70.19 X70.2 X70.20 X70.21 

437X70.22 X70.23 X70.24 X70.28 X70.29 X70.3 X70.30 X70.31 X70.32 X70.33 X70.34 

438X70.38 X70.39 X70.4 X70.40 X70.41 X70.42 X70.43 X70.44 X70.48 X70.49 X70.5 

439X70.50 X70.51 X70.52 X70.53 X70.54 X70.58 X70.59 X70.6 X70.60 X70.61 X70.62 

440X70.63 X70.64 X70.68 X70.69 X70.7 X70.70 X70.71 X70.72 X70.73 X70.74 X70.78 

441X70.79 X70.8 X70.80 X70.81 X70.82 X70.83 X70.84 X70.88 X70.89 X70.9 X70.90 

442X70.91 X70.92 X70.93 X70.94 X70.98 X70.99 X71 X71.0 X71.00 X71.01 X71.02 X71.03 

443X71.04 X71.08 X71.09 X71.1 X71.10 X71.11 X71.12 X71.13 X71.14 X71.18 X71.19 

444X71.2 X71.20 X71.21 X71.22 X71.23 X71.24 X71.28 X71.29 X71.3 X71.30 X71.31 

445X71.32 X71.33 X71.34 X71.38 X71.39 X71.4 X71.40 X71.41 X71.42 X71.43 X71.44 

446X71.48 X71.49 X71.5 X71.50 X71.51 X71.52 X71.53 X71.54 X71.58 X71.59 X71.6 

447X71.60 X71.61 X71.62 X71.63 X71.64 X71.68 X71.69 X71.7 X71.70 X71.71 X71.72 

448X71.73 X71.74 X71.78 X71.79 X71.8 X71.80 X71.81 X71.82 X71.83 X71.84 X71.88 

449X71.89 X71.9 X71.90 X71.91 X71.92 X71.93 X71.94 X71.98 X71.99 X72 X72.0 X72.00 

450X72.01 X72.02 X72.03 X72.04 X72.08 X72.09 X72.1 X72.10 X72.11 X72.12 X72.13 

451X72.14 X72.18 X72.19 X72.2 X72.20 X72.21 X72.22 X72.23 X72.24 X72.28 X72.29 

452X72.3 X72.30 X72.31 X72.32 X72.33 X72.34 X72.38 X72.39 X72.4 X72.40 X72.41 

453X72.42 X72.43 X72.44 X72.48 X72.49 X72.5 X72.50 X72.51 X72.52 X72.53 X72.54 

454X72.58 X72.59 X72.6 X72.60 X72.61 X72.62 X72.63 X72.64 X72.68 X72.69 X72.7 

455X72.70 X72.71 X72.72 X72.73 X72.74 X72.78 X72.79 X72.8 X72.80 X72.81 X72.82 

456X72.83 X72.84 X72.88 X72.89 X72.9 X72.90 X72.91 X72.92 X72.93 X72.94 X72.98 

457X72.99 X73 X73.0 X73.00 X73.01 X73.02 X73.03 X73.04 X73.08 X73.09 X73.1 X73.10 

458X73.11 X73.12 X73.13 X73.14 X73.18 X73.19 X73.2 X73.20 X73.21 X73.22 X73.23 

459X73.24 X73.28 X73.29 X73.3 X73.30 X73.31 X73.32 X73.33 X73.34 X73.38 X73.39 

460X73.4 X73.40 X73.41 X73.42 X73.43 X73.44 X73.48 X73.49 X73.5 X73.50 X73.51 

461X73.52 X73.53 X73.54 X73.58 X73.59 X73.6 X73.60 X73.61 X73.62 X73.63 X73.64 

462X73.68 X73.69 X73.7 X73.70 X73.71 X73.72 X73.73 X73.74 X73.78 X73.79 X73.8 

463X73.80 X73.81 X73.82 X73.83 X73.84 X73.88 X73.89 X73.9 X73.90 X73.91 X73.92 

464X73.93 X73.94 X73.98 X73.99 X74 X74.0 X74.00 X74.01 X74.02 X74.03 X74.04 X74.08 

465X74.09 X74.1 X74.10 X74.11 X74.12 X74.13 X74.14 X74.18 X74.19 X74.2 X74.20 

466X74.21 X74.22 X74.23 X74.24 X74.28 X74.29 X74.3 X74.30 X74.31 X74.32 X74.33 

467X74.34 X74.38 X74.39 X74.4 X74.40 X74.41 X74.42 X74.43 X74.44 X74.48 X74.49 

468X74.5 X74.50 X74.51 X74.52 X74.53 X74.54 X74.58 X74.59 X74.6 X74.60 X74.61 

469X74.62 X74.63 X74.64 X74.68 X74.69 X74.7 X74.70 X74.71 X74.72 X74.73 X74.74 

470X74.78 X74.79 X74.8 X74.80 X74.81 X74.82 X74.83 X74.84 X74.88 X74.89 X74.9 

471X74.90 X74.91 X74.92 X74.93 X74.94 X74.98 X74.99 X75 X75.0 X75.00 X75.01 X75.02 

472X75.03 X75.04 X75.08 X75.09 X75.1 X75.10 X75.11 X75.12 X75.13 X75.14 X75.18 

473X75.19 X75.2 X75.20 X75.21 X75.22 X75.23 X75.24 X75.28 X75.29 X75.3 X75.30 

474X75.31 X75.32 X75.33 X75.34 X75.38 X75.39 X75.4 X75.40 X75.41 X75.42 X75.43 

475X75.44 X75.48 X75.49 X75.5 X75.50 X75.51 X75.52 X75.53 X75.54 X75.58 X75.59 

476X75.6 X75.60 X75.61 X75.62 X75.63 X75.64 X75.68 X75.69 X75.7 X75.70 X75.71 

477X75.72 X75.73 X75.74 X75.78 X75.79 X75.8 X75.80 X75.81 X75.82 X75.83 X75.84 

478X75.88 X75.89 X75.9 X75.90 X75.91 X75.92 X75.93 X75.94 X75.98 X75.99 X76 X76.0 

479X76.00 X76.01 X76.02 X76.03 X76.04 X76.08 X76.09 X76.1 X76.10 X76.11 X76.12 

480X76.13 X76.14 X76.18 X76.19 X76.2 X76.20 X76.21 X76.22 X76.23 X76.24 X76.28 

481X76.29 X76.3 X76.30 X76.31 X76.32 X76.33 X76.34 X76.38 X76.39 X76.4 X76.40 

482X76.41 X76.42 X76.43 X76.44 X76.48 X76.49 X76.5 X76.50 X76.51 X76.52 X76.53 

483X76.54 X76.58 X76.59 X76.6 X76.60 X76.61 X76.62 X76.63 X76.64 X76.68 X76.69 

484X76.7 X76.70 X76.71 X76.72 X76.73 X76.74 X76.78 X76.79 X76.8 X76.80 X76.81 

485X76.82 X76.83 X76.84 X76.88 X76.89 X76.9 X76.90 X76.91 X76.92 X76.93 X76.94 

486X76.98 X76.99 X77 X77.0 X77.00 X77.01 X77.02 X77.03 X77.04 X77.08 X77.09 X77.1 

487X77.10 X77.11 X77.12 X77.13 X77.14 X77.18 X77.19 X77.2 X77.20 X77.21 X77.22 

488X77.23 X77.24 X77.28 X77.29 X77.3 X77.30 X77.31 X77.32 X77.33 X77.34 X77.38 

489X77.39 X77.4 X77.40 X77.41 X77.42 X77.43 X77.44 X77.48 X77.49 X77.5 X77.50 

490X77.51 X77.52 X77.53 X77.54 X77.58 X77.59 X77.6 X77.60 X77.61 X77.62 X77.63 

491X77.64 X77.68 X77.69 X77.7 X77.70 X77.71 X77.72 X77.73 X77.74 X77.78 X77.79 

492X77.8 X77.80 X77.81 X77.82 X77.83 X77.84 X77.88 X77.89 X77.9 X77.90 X77.91 

493X77.92 X77.93 X77.94 X77.98 X77.99 X78 X78.0 X78.00 X78.01 X78.02 X78.03 X78.04 

494X78.08 X78.09 X78.1 X78.10 X78.11 X78.12 X78.13 X78.14 X78.18 X78.19 X78.2 

495X78.20 X78.21 X78.22 X78.23 X78.24 X78.28 X78.29 X78.3 X78.30 X78.31 X78.32 

496X78.33 X78.34 X78.38 X78.39 X78.4 X78.40 X78.41 X78.42 X78.43 X78.44 X78.48 

497X78.49 X78.5 X78.50 X78.51 X78.52 X78.53 X78.54 X78.58 X78.59 X78.6 X78.60 

498X78.61 X78.62 X78.63 X78.64 X78.68 X78.69 X78.7 X78.70 X78.71 X78.72 X78.73 

499X78.74 X78.78 X78.79 X78.8 X78.80 X78.81 X78.82 X78.83 X78.84 X78.88 X78.89 

500X78.9 X78.90 X78.91 X78.92 X78.93 X78.94 X78.98 X78.99 X79 X79.0 X79.00 X79.01 

501X79.02 X79.03 X79.04 X79.08 X79.09 X79.1 X79.10 X79.11 X79.12 X79.13 X79.14 

502X79.18 X79.19 X79.2 X79.20 X79.21 X79.22 X79.23 X79.24 X79.28 X79.29 X79.3 

503X79.30 X79.31 X79.32 X79.33 X79.34 X79.38 X79.39 X79.4 X79.40 X79.41 X79.42 

504X79.43 X79.44 X79.48 X79.49 X79.5 X79.50 X79.51 X79.52 X79.53 X79.54 X79.58 

505X79.59 X79.6 X79.60 X79.61 X79.62 X79.63 X79.64 X79.68 X79.69 X79.7 X79.70 

506X79.71 X79.72 X79.73 X79.74 X79.78 X79.79 X79.8 X79.80 X79.81 X79.82 X79.83 

507X79.84 X79.88 X79.89 X79.9 X79.90 X79.91 X79.92 X79.93 X79.94 X79.98 X79.99 X80 

508X80.0 X80.00 X80.01 X80.02 X80.03 X80.04 X80.08 X80.09 X80.1 X80.10 X80.11 

509X80.12 X80.13 X80.14 X80.18 X80.19 X80.2 X80.20 X80.21 X80.22 X80.23 X80.24 

510X80.28 X80.29 X80.3 X80.30 X80.31 X80.32 X80.33 X80.34 X80.38 X80.39 X80.4 

511X80.40 X80.41 X80.42 X80.43 X80.44 X80.48 X80.49 X80.5 X80.50 X80.51 X80.52 

512X80.53 X80.54 X80.58 X80.59 X80.6 X80.60 X80.61 X80.62 X80.63 X80.64 X80.68 

513X80.69 X80.7 X80.70 X80.71 X80.72 X80.73 X80.74 X80.78 X80.79 X80.8 X80.80 

514X80.81 X80.82 X80.83 X80.84 X80.88 X80.89 X80.9 X80.90 X80.91 X80.92 X80.93 

515X80.94 X80.98 X80.99 X81 X81.0 X81.00 X81.01 X81.02 X81.03 X81.04 X81.08 X81.09 

516X81.1 X81.10 X81.11 X81.12 X81.13 X81.14 X81.18 X81.19 X81.2 X81.20 X81.21 

517X81.22 X81.23 X81.24 X81.28 X81.29 X81.3 X81.30 X81.31 X81.32 X81.33 X81.34 

518X81.38 X81.39 X81.4 X81.40 X81.41 X81.42 X81.43 X81.44 X81.48 X81.49 X81.5 

519X81.50 X81.51 X81.52 X81.53 X81.54 X81.58 X81.59 X81.6 X81.60 X81.61 X81.62 

520X81.63 X81.64 X81.68 X81.69 X81.7 X81.70 X81.71 X81.72 X81.73 X81.74 X81.78 

521X81.79 X81.8 X81.80 X81.81 X81.82 X81.83 X81.84 X81.88 X81.89 X81.9 X81.90 

522X81.91 X81.92 X81.93 X81.94 X81.98 X81.99 X82 X82.0 X82.00 X82.01 X82.02 X82.03 

523X82.04 X82.08 X82.09 X82.1 X82.10 X82.11 X82.12 X82.13 X82.14 X82.18 X82.19 

524X82.2 X82.20 X82.21 X82.22 X82.23 X82.24 X82.28 X82.29 X82.3 X82.30 X82.31 

525X82.32 X82.33 X82.34 X82.38 X82.39 X82.4 X82.40 X82.41 X82.42 X82.43 X82.44 

526X82.48 X82.49 X82.5 X82.50 X82.51 X82.52 X82.53 X82.54 X82.58 X82.59 X82.6 

527X82.60 X82.61 X82.62 X82.63 X82.64 X82.68 X82.69 X82.7 X82.70 X82.71 X82.72 

528X82.73 X82.74 X82.78 X82.79 X82.8 X82.80 X82.81 X82.82 X82.83 X82.84 X82.88 

529X82.89 X82.9 X82.90 X82.91 X82.92 X82.93 X82.94 X82.98 X82.99 X83 X83.0 X83.00 

530X83.01 X83.02 X83.03 X83.04 X83.08 X83.09 X83.1 X83.10 X83.11 X83.12 X83.13 

531X83.14 X83.18 X83.19 X83.2 X83.20 X83.21 X83.22 X83.23 X83.24 X83.28 X83.29 

532X83.3 X83.30 X83.31 X83.32 X83.33 X83.34 X83.38 X83.39 X83.4 X83.40 X83.41 

533X83.42 X83.43 X83.44 X83.48 X83.49 X83.5 X83.50 X83.51 X83.52 X83.53 X83.54 

534X83.58 X83.59 X83.6 X83.60 X83.61 X83.62 X83.63 X83.64 X83.68 X83.69 X83.7 

535X83.70 X83.71 X83.72 X83.73 X83.74 X83.78 X83.79 X83.8 X83.80 X83.81 X83.82 

536X83.83 X83.84 X83.88 X83.89 X83.9 X83.90 X83.91 X83.92 X83.93 X83.94 X83.98 

537X83.99 X84 X84.0 X84.00 X84.01 X84.02 X84.03 X84.04 X84.08 X84.09 X84.1 X84.10 

538X84.11 X84.12 X84.13 X84.14 X84.18 X84.19 X84.2 X84.20 X84.21 X84.22 X84.23 

539X84.24 X84.28 X84.29 X84.3 X84.30 X84.31 X84.32 X84.33 X84.34 X84.38 X84.39 

540X84.4 X84.40 X84.41 X84.42 X84.43 X84.44 X84.48 X84.49 X84.5 X84.50 X84.51 

541X84.52 X84.53 X84.54 X84.58 X84.59 X84.6 X84.60 X84.61 X84.62 X84.63 X84.64 

542X84.68 X84.69 X84.7 X84.70 X84.71 X84.72 X84.73 X84.74 X84.78 X84.79 X84.8 

543X84.80 X84.81 X84.82 X84.83 X84.84 X84.88 X84.89 X84.9 X84.90 X84.91 X84.92 

544X84.93 X84.94 X84.98 X84.99 Z00.4 Z03.2 Z71.1 

545""".split() 

546) 

547 

548 

549# ============================================================================= 

550# The SnomedConcept class, amended 

551# ============================================================================= 

552 

553 

554class SnomedConcept(SnomedConceptCardinalPythonlib): 

555 @classmethod 

556 def create( 

557 cls, concept: SnomedConceptCardinalPythonlib 

558 ) -> "SnomedConcept": 

559 """ 

560 Creates a CamCOPS-friendly 

561 :class:`camcops_server.cc_modules.cc_snomed.SnomedConcept` from a 

562 :class:`cardinal_pythonlib.snomed.SnomedConcept`. 

563 

564 The result has extra methods. 

565 """ 

566 return SnomedConcept(identifier=concept.identifier, term=concept.term) 

567 

568 def xml_element(self, longform: bool = True) -> XmlElement: 

569 """ 

570 Returns a :class:`camcops_server.cc_modules.cc_xml.XmlElement` for this 

571 SNOMED-CT object. 

572 

573 Args: 

574 longform: print SNOMED-CT concepts in long form? 

575 """ 

576 return XmlElement( 

577 name=SNOMED_XML_NAME, 

578 value=self.as_string(longform), 

579 datatype=XmlDataTypes.STRING, 

580 ) 

581 

582 

583# ============================================================================= 

584# The SnomedExpression class, amended 

585# ============================================================================= 

586 

587 

588class SnomedExpression(SnomedExpressionCardinalPythonlib): 

589 def xml_element(self, longform: bool = True) -> XmlElement: 

590 """ 

591 Returns a :class:`camcops_server.cc_modules.cc_xml.XmlElement` for this 

592 SNOMED-CT object. 

593 

594 Args: 

595 longform: print SNOMED-CT expressions in long form? 

596 """ 

597 return XmlElement( 

598 name=SNOMED_XML_NAME, 

599 value=self.as_string(longform), 

600 datatype=XmlDataTypes.STRING, 

601 ) 

602 

603 

604# ============================================================================= 

605# The CamCOPS XML file format for SNOMED-CT 

606# ============================================================================= 

607 

608ROOT_TAG = "snomed_concepts" 

609CONCEPT_TAG = "concept" 

610LOOKUP_TAG = "lookup" 

611LOOKUP_NAME_ATTR = "name" 

612LOOKUP_ATTR = "lookup" 

613ID_TAG = "id" 

614TERM_TAG = "term" 

615AUTOGEN_COMMENT = ( 

616 "Autogenerated XML (see camcops_server.cc_modules.cc_snomed.py); do not " 

617 "edit" 

618) 

619 

620 

621def get_snomed_concepts_from_xml( 

622 xml_filename: str, 

623) -> Dict[str, Union[SnomedConcept, List[SnomedConcept]]]: 

624 """ 

625 Reads in all SNOMED-CT concepts from an XML file according to the CamCOPS 

626 format. 

627 

628 Args: 

629 xml_filename: XML filename to read 

630 

631 Returns: 

632 dict: mapping each lookup code found to a list of 

633 :class:`SnomedConcept` objects 

634 

635 """ 

636 log.info("Reading SNOMED-CT XML file: {}", xml_filename) 

637 parser = ElementTree.XMLParser(encoding="UTF-8") 

638 tree = ElementTree.parse(xml_filename, parser=parser) 

639 root = tree.getroot() 

640 all_concepts = {} # type: Dict[str, List[SnomedConcept]] 

641 find_lookup = "./{tag}[@{attr}]".format( 

642 tag=LOOKUP_TAG, attr=LOOKUP_NAME_ATTR 

643 ) 

644 find_concept = "./{tag}".format(tag=CONCEPT_TAG) 

645 find_id_in_concept = "./{tag}".format(tag=ID_TAG) 

646 find_name_in_concept = "./{tag}".format(tag=TERM_TAG) 

647 for lookuproot in root.findall(find_lookup): 

648 # Extract info from the XML 

649 lookupname = lookuproot.attrib.get(LOOKUP_NAME_ATTR) 

650 for conceptroot in lookuproot.findall(find_concept): 

651 id_node = conceptroot.find(find_id_in_concept) 

652 identifier = int(id_node.text) 

653 name_node = conceptroot.find(find_name_in_concept) 

654 name = name_node.text or "" 

655 # Stash it 

656 concept = SnomedConcept(identifier, name) 

657 concepts_for_lookup = all_concepts.setdefault(lookupname, []) 

658 concepts_for_lookup.append(concept) 

659 # Done 

660 return all_concepts # type: ignore[return-value] 

661 

662 

663def write_snomed_concepts_to_xml( 

664 xml_filename: str, 

665 concepts: Dict[str, List[SnomedConcept]], 

666 comment: str = AUTOGEN_COMMENT, 

667) -> None: 

668 """ 

669 Writes SNOMED-CT concepts to an XML file in the CamCOPS format. 

670 

671 Args: 

672 xml_filename: XML filename to write 

673 concepts: dictionary mapping lookup codes to a list of 

674 :class:`SnomedConcept` objects 

675 comment: comment for XML file 

676 """ 

677 # https://stackoverflow.com/questions/3605680/creating-a-simple-xml-file-using-python 

678 root = ElementTree.Element(ROOT_TAG) 

679 comment_element = ElementTree.Comment(comment) 

680 root.insert(0, comment_element) 

681 for lookup, concepts_for_lookup in concepts.items(): 

682 l_el = ElementTree.SubElement( 

683 root, LOOKUP_TAG, {LOOKUP_NAME_ATTR: lookup} 

684 ) 

685 for concept in concepts_for_lookup: 

686 c_el = ElementTree.SubElement(l_el, CONCEPT_TAG) 

687 i_el = ElementTree.SubElement(c_el, ID_TAG) 

688 i_el.text = str(concept.identifier) 

689 n_el = ElementTree.SubElement(c_el, TERM_TAG) 

690 n_el.text = concept.term 

691 tree = ElementTree.ElementTree(root) 

692 log.info("Writing to {!r}", xml_filename) 

693 tree.write(xml_filename) 

694 

695 

696# ============================================================================= 

697# SNOMED-CT concepts for CamCOPS tasks 

698# ============================================================================= 

699 

700# ----------------------------------------------------------------------------- 

701# CamCOPS lookup codes for SNOMED-CT concepts 

702# ----------------------------------------------------------------------------- 

703 

704 

705class SnomedLookup(object): 

706 """ 

707 We're not allowed to embed SNOMED-CT codes in the CamCOPS code. Therefore, 

708 within CamCOPS, we use string constants represented in this class. If the 

709 local institution is allowed (e.g. in the UK, as below), then it can 

710 install additional data. 

711 

712 - UK license: 

713 https://isd.digital.nhs.uk/trud3/user/guest/group/0/pack/26/subpack/101/licences 

714 

715 - To find codes: https://termbrowser.nhs.uk/ 

716 

717 Abbreviations: 

718 

719 - "Finding" is not abbreviated 

720 - "Obs" or "observable" is short for "observable entity" 

721 - "Procedure" is not abbreviated 

722 - "Scale" is short for "assessment scale" 

723 - "Situation" is not abbreviated 

724 

725 Variable names are designed for clear code. Value strings are designed for 

726 clear XML that matches SNOMED-CT, in the format TASK_CONCEPTTYPE_NAME. 

727 

728 """ 

729 

730 # https://snomedbrowser.com/Codes/Details/XXX 

731 

732 # ------------------------------------------------------------------------- 

733 # SNOMED-CT core concepts 

734 # ------------------------------------------------------------------------- 

735 

736 # Base concepts 

737 OBSERVABLE_ENTITY = "observable_entity" 

738 

739 # Abstract physical quantities 

740 MASS = "mass" 

741 LENGTH = "length" 

742 

743 # Saying "units" 

744 UNIT_OF_MEASURE = "unit_of_measure" 

745 

746 # Base physical units 

747 KILOGRAM = "kilogram" 

748 METRE = "metre" 

749 CENTIMETRE = "centimetre" 

750 

751 # Compound physical units 

752 KG_PER_SQ_M = "kilogram_per_square_metre" 

753 

754 # ------------------------------------------------------------------------- 

755 # Scales 

756 # ------------------------------------------------------------------------- 

757 

758 # ACE-R 

759 ACE_R_SCALE = "ace_r_scale" 

760 ACE_R_SUBSCALE_ATTENTION_ORIENTATION = ( 

761 "ace_r_subscale_attention_orientation" 

762 ) 

763 ACE_R_SUBSCALE_FLUENCY = "ace_r_subscale_fluency" 

764 ACE_R_SUBSCALE_LANGUAGE = "ace_r_subscale_language" 

765 ACE_R_SUBSCALE_MEMORY = "ace_r_subscale_memory" 

766 ACE_R_SUBSCALE_VISUOSPATIAL = "ace_r_subscale_visuospatial" 

767 ACE_R_SCORE = "ace_r_observable_score" 

768 ACE_R_SUBSCORE_ATTENTION_ORIENTATION = ( 

769 "ace_r_observable_subscore_attention_orientation" 

770 ) 

771 ACE_R_SUBSCORE_FLUENCY = "ace_r_observable_subscore_fluency" 

772 ACE_R_SUBSCORE_LANGUAGE = "ace_r_observable_subscore_language" 

773 ACE_R_SUBSCORE_MEMORY = "ace_r_observable_subscore_memory" 

774 ACE_R_SUBSCORE_VISUOSPATIAL = "ace_r_observable_subscore_visuospatial" 

775 ACE_R_PROCEDURE_ASSESSMENT = "ace_r_procedure_assessment" 

776 ACE_R_PROCEDURE_ASSESSMENT_SUBSCALE_ATTENTION_ORIENTATION = ( 

777 "ace_r_procedure_assessment_subscale_attention_orientation" 

778 ) 

779 ACE_R_PROCEDURE_ASSESSMENT_SUBSCALE_FLUENCY = ( 

780 "ace_r_procedure_assessment_subscale_fluency" 

781 ) 

782 ACE_R_PROCEDURE_ASSESSMENT_SUBSCALE_LANGUAGE = ( 

783 "ace_r_procedure_assessment_subscale_language" 

784 ) 

785 ACE_R_PROCEDURE_ASSESSMENT_SUBSCALE_MEMORY = ( 

786 "ace_r_procedure_assessment_subscale_memory" 

787 ) 

788 ACE_R_PROCEDURE_ASSESSMENT_SUBSCALE_VISUOSPATIAL = ( 

789 "ace_r_procedure_assessment_subscale_visuospatial" 

790 ) 

791 

792 # AIMS 

793 AIMS_SCALE = "aims_scale" 

794 AIMS_TOTAL_SCORE = "aims_observable_total_score" 

795 AIMS_PROCEDURE_ASSESSMENT = "aims_procedure_assessment" 

796 

797 # AUDIT, AUDIT-C 

798 AUDIT_SCALE = "audit_scale" 

799 AUDIT_SCORE = "audit_observable_score" 

800 AUDIT_PROCEDURE_ASSESSMENT = "audit_procedure_assessment" 

801 AUDITC_SCALE = "auditc_scale" 

802 AUDITC_SCORE = "auditc_observable_score" 

803 AUDITC_PROCEDURE_ASSESSMENT = "auditc_procedure_assessment" 

804 

805 # BADLS 

806 BADLS_SCALE = "badls_scale" 

807 BADLS_SCORE = "badls_observable_score" 

808 BADLS_PROCEDURE_ASSESSMENT = "badls_procedure_assessment" 

809 

810 # BDI 

811 BDI_SCALE = "bdi_scale" 

812 BDI_SCORE = "bdi_observable_score" 

813 BDI_PROCEDURE_ASSESSMENT = "bdi_procedure_assessment" 

814 BDI_II_SCORE = "bdi_ii_observable_score" 

815 BDI_II_PROCEDURE_ASSESSMENT = "bdi_ii_procedure_assessment" 

816 

817 # BMI 

818 BMI_OBSERVABLE = "bmi_observable" 

819 BMI_PROCEDURE_MEASUREMENT = "bmi_procedure_measurement" 

820 BODY_HEIGHT_OBSERVABLE = "body_height_observable" 

821 BODY_WEIGHT_OBSERVABLE = "body_weight_observable" 

822 WAIST_CIRCUMFERENCE_PROCEDURE_MEASUREMENT = ( 

823 "waist_circumference_procedure_measurement" 

824 ) 

825 WAIST_CIRCUMFERENCE_OBSERVABLE = "waist_circumference_observable" 

826 

827 # BPRS, BPRS-E 

828 BPRS1962_SCALE = "bprs1962_scale" 

829 # no observable/procedure 

830 

831 # CAGE 

832 CAGE_SCALE = "cage_scale" 

833 CAGE_SCORE = "cage_observable_score" 

834 CAGE_PROCEDURE_ASSESSMENT = "cage_procedure_assessment" 

835 

836 # CAPE-42: none 

837 # CAPS: none 

838 # Cardinal RN, Expectation-Detection: none 

839 # CBI-R: none 

840 # CECA-Q3: none 

841 # CESD, CESD-R: none 

842 # CGI, CGI-I, CGI-SCH: none 

843 # CIS-R: none 

844 

845 # CIWA-Ar 

846 CIWA_AR_SCALE = "ciwa_ar_scale" 

847 CIWA_AR_SCORE = "ciwa_ar_observable_score" 

848 CIWA_AR_PROCEDURE_ASSESSMENT = "ciwa_ar_procedure_assessment" 

849 

850 # Clinical: progress note 

851 PROGRESS_NOTE_PROCEDURE = "progress_note_procedure" 

852 CLINICAL_NOTE = "clinical_note" 

853 

854 # Clinical: photograph 

855 PHOTOGRAPH_PROCEDURE = "photograph_procedure" 

856 PHOTOGRAPH_PHYSICAL_OBJECT = "photograph_physical_object" 

857 

858 # Clinical: psychiatric clerking 

859 # ... "location": not obvious 

860 # ... "contact type" is an AoMRC heading, but I'm not sure the observable 

861 # entity of "Initial contact type" is right. 

862 # 

863 # Deprecated between v20191001 and v20210929: 

864 # PSYCHIATRIC_ASSESSMENT_PROCEDURE = "psychiatric_assessment_procedure" 

865 DIAGNOSTIC_PSYCHIATRIC_INTERVIEW_PROCEDURE = ( 

866 "diagnostic_psychiatric_interview_procedure" 

867 ) 

868 

869 PSYCLERK_REASON_FOR_REFERRAL = "psyclerk_reason_for_referral" 

870 PSYCLERK_PRESENTING_ISSUE = "psyclerk_presenting_issue" 

871 PSYCLERK_SYSTEMS_REVIEW = "psyclerk_systems_review" 

872 PSYCLERK_COLLATERAL_HISTORY = "psyclerk_collateral_history" 

873 

874 PSYCLERK_PAST_MEDICAL_SURGICAL_MENTAL_HEALTH_HISTORY = ( 

875 "psyclerk_past_medical_surgical_mental_health_history" 

876 ) 

877 PSYCLERK_PROCEDURES = "psyclerk_procedures" 

878 PSYCLERK_ALLERGIES_ADVERSE_REACTIONS = ( 

879 "psyclerk_allergies_adverse_reactions" 

880 ) 

881 PSYCLERK_MEDICATIONS_MEDICAL_DEVICES = ( 

882 "psyclerk_medications_medical_devices" 

883 ) 

884 PSYCLERK_DRUG_SUBSTANCE_USE = "psyclerk_drug_substance_use" 

885 PSYCLERK_FAMILY_HISTORY = "psyclerk_family_history" 

886 PSYCLERK_DEVELOPMENTAL_HISTORY = "psyclerk_developmental_history" 

887 PSYCLERK_SOCIAL_PERSONAL_HISTORY = "psyclerk_social_personal_history" 

888 PSYCLERK_PERSONALITY = "psyclerk_personality" 

889 PSYCLERK_PRISON_RECORD_CRIMINAL_ACTIVITY = ( 

890 "psyclerk_prison_record_criminal_activity" 

891 ) 

892 PSYCLERK_SOCIAL_HISTORY_BASELINE = "psyclerk_social_history_baseline" 

893 

894 PSYCLERK_MSE_APPEARANCE = "psyclerk_mse_appearance" 

895 PSYCLERK_MSE_BEHAVIOUR = "psyclerk_mse_behaviour" 

896 PSYCLERK_MSE_SPEECH = "psyclerk_mse_speech" 

897 PSYCLERK_MSE_MOOD = "psyclerk_mse_mood" 

898 PSYCLERK_MSE_AFFECT = "psyclerk_mse_affect" 

899 PSYCLERK_MSE_THOUGHT = "psyclerk_mse_thought" 

900 PSYCLERK_MSE_PERCEPTION = "psyclerk_mse_perception" 

901 PSYCLERK_MSE_COGNITION = "psyclerk_mse_cognition" 

902 PSYCLERK_MSE_INSIGHT = "psyclerk_mse_insight" 

903 

904 PSYCLERK_PHYSEXAM_GENERAL = "psyclerk_physexam_general" 

905 PSYCLERK_PHYSEXAM_CARDIOVASCULAR = "psyclerk_physexam_cardiovascular" 

906 PSYCLERK_PHYSEXAM_RESPIRATORY = "psyclerk_physexam_respiratory" 

907 PSYCLERK_PHYSEXAM_ABDOMINAL = "psyclerk_physexam_abdominal" 

908 PSYCLERK_PHYSEXAM_NEUROLOGICAL = "psyclerk_physexam_neurological" 

909 

910 PSYCLERK_ASSESSMENT_SCALES = "psyclerk_assessment_scales" 

911 PSYCLERK_INVESTIGATIONS_RESULTS = "psyclerk_investigations_results" 

912 

913 PSYCLERK_SAFETY_ALERTS = "psyclerk_safety_alerts" 

914 PSYCLERK_RISK_ASSESSMENT = "psyclerk_risk_assessment" 

915 PSYCLERK_RELEVANT_LEGAL_INFORMATION = "psyclerk_relevant_legal_information" 

916 

917 PSYCLERK_CURRENT_PROBLEMS = "psyclerk_current_problems" 

918 PSYCLERK_PATIENT_CARER_CONCERNS = "psyclerk_patient_carer_concerns" 

919 PSYCLERK_CLINICAL_NARRATIVE = "psyclerk_clinical_narrative" 

920 PSYCLERK_MANAGEMENT_PLAN = "psyclerk_management_plan" 

921 PSYCLERK_INFORMATION_GIVEN = "psyclerk_information_given" 

922 

923 # CPFT/CUH LPS: none 

924 # COPE: none 

925 

926 # CORE-10 

927 CORE10_SCALE = "core10_scale" 

928 CORE10_SCORE = "core10_observable_score" 

929 CORE10_PROCEDURE_ASSESSMENT = "core10_procedure_assessment" 

930 

931 # DAD: none 

932 

933 # DAST 

934 DAST_SCALE = "dast_scale" 

935 # I think the similarly named codes represent urine screening. 

936 

937 # Deakin JB, antibody: none 

938 # Demo questionnaire: none 

939 # DEMQOL, DEMQOL-Proxy: none 

940 # Distress Thermometer: none 

941 

942 # EQ-5D-5L 

943 EQ5D5L_SCALE = "eq5d5l_scale" 

944 EQ5D5L_INDEX_VALUE = "eq5d5l_observable_index_value" 

945 EQ5D5L_PAIN_DISCOMFORT_SCORE = "eq5d5l_observable_pain_discomfort_score" 

946 EQ5D5L_USUAL_ACTIVITIES_SCORE = "eq5d5l_observable_usual_activities_score" 

947 EQ5D5L_ANXIETY_DEPRESSION_SCORE = ( 

948 "eq5d5l_observable_anxiety_depression_score" 

949 ) 

950 EQ5D5L_MOBILITY_SCORE = "eq5d5l_observable_mobility_score" 

951 EQ5D5L_SELF_CARE_SCORE = "eq5d5l_observable_self_care_score" 

952 EQ5D5L_PROCEDURE_ASSESSMENT = "eq5d5l_procedure_assessment" 

953 

954 # FACT-G: none 

955 

956 # FAST: none 

957 FAST_SCALE = "fast_scale" 

958 FAST_SCORE = "fast_observable_score" 

959 FAST_PROCEDURE_ASSESSMENT = "fast_procedure_assessment" 

960 

961 # IRAC: none 

962 # FFT: none 

963 

964 # Patient Satisfaction Scale 

965 # Not sure. See XML 

966 

967 # Referrer Satisfaction Scale (patient-specific, survey): none 

968 # Frontotemporal Dementia Rating Scale: none 

969 

970 # GAD-7 

971 GAD7_SCALE = "gad7_scale" 

972 GAD7_SCORE = "gad7_observable_score" 

973 GAD7_PROCEDURE_ASSESSMENT = "gad7_procedure_assessment" 

974 

975 # GAF 

976 GAF_SCALE = "gaf_scale" 

977 # no observable/procedure 

978 

979 # GDS-15 

980 GDS15_SCALE = "gds15_scale" 

981 GDS15_SCORE = "gds15_observable_score" 

982 GDS15_PROCEDURE_ASSESSMENT = "gds15_procedure_assessment" 

983 

984 # UK GMC Patient Questionnaire: none 

985 

986 # HADS, HADS-respondent 

987 HADS_SCALE = "hads_scale" 

988 HADS_ANXIETY_SCORE = "hads_observable_anxiety_score" 

989 HADS_DEPRESSION_SCORE = "hads_observable_depression_score" 

990 HADS_PROCEDURE_ASSESSMENT = "hads_procedure_assessment" 

991 

992 # HAMA: none 

993 

994 # HAMD 

995 HAMD_SCALE = "hamd_scale" 

996 HAMD_SCORE = "hamd_observable_score" 

997 HAMD_PROCEDURE_ASSESSMENT = "hamd_procedure_assessment" 

998 

999 # HAMD-7: none 

1000 

1001 # HoNOS, HoNOS-65+, HoNOSCA, etc. (there are others too) 

1002 HONOSCA_SCALE = "honos_childrenadolescents_scale" 

1003 HONOSCA_SECTION_A_SCALE = "honos_childrenadolescents_section_a_scale" 

1004 HONOSCA_SECTION_B_SCALE = "honos_childrenadolescents_section_b_scale" 

1005 HONOSCA_SCORE = "honos_childrenadolescents_observable_score" 

1006 HONOSCA_SECTION_A_SCORE = ( 

1007 "honos_childrenadolescents_observable_section_a_score" 

1008 ) 

1009 HONOSCA_SECTION_B_SCORE = ( 

1010 "honos_childrenadolescents_observable_section_b_score" 

1011 ) 

1012 HONOSCA_SECTION_A_PLUS_B_SCORE = ( 

1013 "honos_childrenadolescents_observable_section_a_plus_b_score" 

1014 ) 

1015 HONOSCA_PROCEDURE_ASSESSMENT = ( 

1016 "honos_childrenadolescents_procedure_assessment" 

1017 ) 

1018 # 

1019 HONOS65_SCALE = "honos_olderadults_scale" 

1020 HONOS65_SCORE = "honos_olderadults_observable_score" 

1021 HONOS65_PROCEDURE_ASSESSMENT = "honos_olderadults_procedure_assessment" 

1022 # 

1023 HONOSWA_SCALE = "honos_workingage_scale" 

1024 HONOSWA_SUBSCALE_1_OVERACTIVE = "honos_workingage_subscale_1_overactive" 

1025 HONOSWA_SUBSCALE_2_SELFINJURY = "honos_workingage_subscale_2_selfinjury" 

1026 HONOSWA_SUBSCALE_3_SUBSTANCE = "honos_workingage_subscale_3_substance" 

1027 HONOSWA_SUBSCALE_4_COGNITIVE = "honos_workingage_subscale_4_cognitive" 

1028 HONOSWA_SUBSCALE_5_PHYSICAL = "honos_workingage_subscale_5_physical" 

1029 HONOSWA_SUBSCALE_6_PSYCHOSIS = "honos_workingage_subscale_6_psychosis" 

1030 HONOSWA_SUBSCALE_7_DEPRESSION = "honos_workingage_subscale_7_depression" 

1031 HONOSWA_SUBSCALE_8_OTHERMENTAL = "honos_workingage_subscale_8_othermental" 

1032 HONOSWA_SUBSCALE_9_RELATIONSHIPS = ( 

1033 "honos_workingage_subscale_9_relationships" 

1034 ) 

1035 HONOSWA_SUBSCALE_10_ADL = "honos_workingage_subscale_10_adl" 

1036 HONOSWA_SUBSCALE_11_LIVINGCONDITIONS = ( 

1037 "honos_workingage_subscale_11_livingconditions" 

1038 ) 

1039 HONOSWA_SUBSCALE_12_OCCUPATION = "honos_workingage_subscale_12_occupation" 

1040 HONOSWA_SCORE = "honos_workingage_observable_score" 

1041 HONOSWA_1_OVERACTIVE_SCORE = ( 

1042 "honos_workingage_observable_1_overactive_score" 

1043 ) 

1044 HONOSWA_2_SELFINJURY_SCORE = ( 

1045 "honos_workingage_observable_2_selfinjury_score" 

1046 ) 

1047 HONOSWA_3_SUBSTANCE_SCORE = "honos_workingage_observable_3_substance_score" 

1048 HONOSWA_4_COGNITIVE_SCORE = "honos_workingage_observable_4_cognitive_score" 

1049 HONOSWA_5_PHYSICAL_SCORE = "honos_workingage_observable_5_physical_score" 

1050 HONOSWA_6_PSYCHOSIS_SCORE = "honos_workingage_observable_6_psychosis_score" 

1051 HONOSWA_7_DEPRESSION_SCORE = ( 

1052 "honos_workingage_observable_7_depression_score" 

1053 ) 

1054 HONOSWA_8_OTHERMENTAL_SCORE = ( 

1055 "honos_workingage_observable_8_othermental_score" 

1056 ) 

1057 HONOSWA_9_RELATIONSHIPS_SCORE = ( 

1058 "honos_workingage_observable_9_relationships_score" 

1059 ) 

1060 HONOSWA_10_ADL_SCORE = "honos_workingage_observable_10_adl_score" 

1061 HONOSWA_11_LIVINGCONDITIONS_SCORE = ( 

1062 "honos_workingage_observable_11_livingconditions_score" 

1063 ) 

1064 HONOSWA_12_OCCUPATION_SCORE = ( 

1065 "honos_workingage_observable_12_occupation_score" 

1066 ) 

1067 HONOSWA_PROCEDURE_ASSESSMENT = "honos_workingage_procedure_assessment" 

1068 

1069 # ICD-9-CM: see below; separate file 

1070 

1071 # ICD-10: see below; separate file 

1072 

1073 # IDED3D: none 

1074 

1075 # IES-R 

1076 IESR_SCALE = "iesr_scale" 

1077 IESR_SCORE = "iesr_observable_score" 

1078 IESR_PROCEDURE_ASSESSMENT = "iesr_procedure_assessment" 

1079 

1080 # INECO Frontal Screening: none 

1081 # Khandaker G, Insight: none 

1082 

1083 # MAST, SMAST 

1084 MAST_SCALE = "mast_scale" 

1085 MAST_SCORE = "mast_observable_score" 

1086 MAST_PROCEDURE_ASSESSMENT = "mast_procedure_assessment" 

1087 

1088 SMAST_SCALE = "smast_scale" 

1089 # SMAST: no observable/procedure 

1090 

1091 # MDS-UPDRS 

1092 UPDRS_SCALE = "updrs_scale" 

1093 # MDS-UPDRS: no observable/procedure 

1094 

1095 # MoCA 

1096 MOCA_SCALE = "moca_scale" 

1097 MOCA_SCORE = "moca_observable_score" 

1098 MOCA_PROCEDURE_ASSESSMENT = "moca_procedure_assessment" 

1099 

1100 # NART 

1101 NART_SCALE = "nart_scale" 

1102 NART_SCORE = "nart_observable_score" 

1103 NART_PROCEDURE_ASSESSMENT = "nart_procedure_assessment" 

1104 

1105 # NPI-Q: none 

1106 

1107 # PANSS 

1108 PANSS_SCALE = "panss_scale" 

1109 # PANSS: no observable/procedure 

1110 

1111 # PCL and variants: none 

1112 

1113 # PDSS 

1114 PDSS_SCALE = "pdss_scale" 

1115 PDSS_SCORE = "pdss_observable_score" 

1116 # PDSS: no procedure 

1117 

1118 # "Perinatal" and "scale": none 

1119 

1120 # PHQ-9 

1121 PHQ9_FINDING_NEGATIVE_SCREENING_FOR_DEPRESSION = ( 

1122 "phq9_finding_negative_screening_for_depression" 

1123 ) 

1124 PHQ9_FINDING_POSITIVE_SCREENING_FOR_DEPRESSION = ( 

1125 "phq9_finding_positive_screening_for_depression" 

1126 ) 

1127 PHQ9_SCORE = "phq9_observable_score" 

1128 PHQ9_PROCEDURE_DEPRESSION_SCREENING = "phq9_procedure_depression_screening" 

1129 PHQ9_SCALE = ( 

1130 "phq9_scale" # https://snomedbrowser.com/Codes/Details/758711000000105 

1131 ) 

1132 

1133 # PHQ-15 

1134 PHQ15_SCORE = "phq15_observable_score" 

1135 PHQ15_PROCEDURE = "phq15_procedure_assessment" 

1136 PHQ15_SCALE = "phq15_scale" 

1137 

1138 # PIIOS or "parent infant scale": none 

1139 

1140 # PSWQ 

1141 PSWQ_SCALE = "pswq_scale" 

1142 PSWQ_SCORE = "pswq_observable_score" 

1143 PSWQ_PROCEDURE_ASSESSMENT = "pswq_procedure_assessment" 

1144 

1145 # QOL-Basic, QOL-SG 

1146 # Unsure, but there is this: 

1147 QOL_SCALE = "qol_scale" 

1148 # ... with no observable/procedure 

1149 

1150 # RAND-36: none directly 

1151 

1152 # SLUMS: none 

1153 

1154 # WEMWBS, SWEMWBS 

1155 WEMWBS_SCALE = "wemwbs_scale" 

1156 WEMWBS_SCORE = "wemwbs_observable_score" 

1157 WEMWBS_PROCEDURE_ASSESSMENT = "wemwbs_procedure_assessment" 

1158 # 

1159 SWEMWBS_SCALE = "swemwbs_scale" 

1160 SWEMWBS_SCORE = "swemwbs_observable_score" 

1161 SWEMWBS_PROCEDURE_ASSESSMENT = "swemwbs_procedure_assessment" 

1162 

1163 # WSAS 

1164 WSAS_SCALE = "wsas_scale" 

1165 WSAS_SCORE = "wsas_observable_score" 

1166 WSAS_WORK_SCORE = "wsas_observable_work_score" 

1167 WSAS_RELATIONSHIPS_SCORE = "wsas_observable_relationships_score" 

1168 WSAS_HOME_MANAGEMENT_SCORE = "wsas_observable_home_management_score" 

1169 WSAS_SOCIAL_LEISURE_SCORE = "wsas_observable_social_leisure_score" 

1170 WSAS_PRIVATE_LEISURE_SCORE = "wsas_observable_private_leisure_score" 

1171 WSAS_PROCEDURE_ASSESSMENT = "wsas_procedure_assessment" 

1172 

1173 # Y-BOCS, Y-BOCS-SC: none 

1174 # ZBI: none 

1175 

1176 

1177# ----------------------------------------------------------------------------- 

1178# Perform the lookup 

1179# ----------------------------------------------------------------------------- 

1180 

1181VALID_SNOMED_LOOKUPS = set( 

1182 [ 

1183 getattr(SnomedLookup, k) 

1184 for k in dir(SnomedLookup) 

1185 if not k.startswith("_") 

1186 ] 

1187) 

1188 

1189 

1190@cache_region_static.cache_on_arguments(function_key_generator=fkg) 

1191def get_all_task_snomed_concepts( 

1192 xml_filename: str, 

1193) -> Dict[str, SnomedConcept]: 

1194 """ 

1195 Reads in all SNOMED-CT codes for CamCOPS tasks, from the custom CamCOPS XML 

1196 file for this. 

1197 

1198 Args: 

1199 xml_filename: XML filename to read 

1200 

1201 Returns: 

1202 dict: maps lookup strings to :class:`SnomedConcept` objects 

1203 

1204 """ 

1205 xml_concepts = get_snomed_concepts_from_xml(xml_filename) 

1206 camcops_concepts = {} # type: Dict[str, SnomedConcept] 

1207 identifiers_seen = set() # type: Set[int] 

1208 for lookup, concepts in xml_concepts.items(): 

1209 # Check it 

1210 if lookup not in VALID_SNOMED_LOOKUPS: 

1211 log.debug("Ignoring unknown SNOMED-CT lookup: {!r}", lookup) 

1212 continue 

1213 assert ( 

1214 len(concepts) == 1 # type: ignore[arg-type] 

1215 ), f"More than one SNOMED-CT concept for lookup: {lookup!r}" 

1216 concept = concepts[0] # type: ignore[index] 

1217 assert ( 

1218 concept.identifier not in identifiers_seen 

1219 ), f"Duplicate SNOMED-CT identifier: {concept.identifier!r}" 

1220 identifiers_seen.add(concept.identifier) 

1221 # Stash it 

1222 camcops_concepts[lookup] = concept 

1223 # Check if any are missing 

1224 missing = sorted(list(VALID_SNOMED_LOOKUPS - set(camcops_concepts.keys()))) 

1225 if missing: 

1226 raise ValueError( 

1227 f"The following SNOMED-CT concepts required by CamCOPS are " 

1228 f"missing from the XML ({xml_filename}): {missing!r}" 

1229 ) 

1230 # Done 

1231 return camcops_concepts 

1232 

1233 

1234# ============================================================================= 

1235# UMLS ICD-9-CM 

1236# ============================================================================= 

1237 

1238 

1239class UmlsIcd9SnomedRow(object): 

1240 """ 

1241 Simple information-holding class for a row of the ICD-9-CM TSV file, from 

1242 https://www.nlm.nih.gov/research/umls/mapping_projects/icd9cm_to_snomedct.html. 

1243 

1244 NOT CURRENTLY USED. 

1245 """ 

1246 

1247 HEADER = [ 

1248 "ICD_CODE", 

1249 "ICD_NAME", 

1250 "IS_CURRENT_ICD", 

1251 "IP_USAGE", 

1252 "OP_USAGE", 

1253 "AVG_USAGE", 

1254 "IS_NEC", 

1255 "SNOMED_CID", 

1256 "SNOMED_FSN", 

1257 "IS_1-1MAP", 

1258 "CORE_USAGE", 

1259 "IN_CORE", 

1260 ] 

1261 

1262 @staticmethod 

1263 def to_float(x: str) -> Optional[float]: 

1264 return None if x == "NULL" else float(x) 

1265 

1266 def __init__( 

1267 self, 

1268 icd_code: str, 

1269 icd_name: str, 

1270 is_current_icd: str, 

1271 ip_usage: str, 

1272 op_usage: str, 

1273 avg_usage: str, 

1274 is_nec: str, 

1275 snomed_cid: str, 

1276 snomed_fsn: str, 

1277 is_one_to_one_map: str, 

1278 core_usage: str, 

1279 in_core: str, 

1280 ) -> None: 

1281 """ 

1282 Argument order is important. 

1283 

1284 Args: 

1285 icd_code: ICD-9-CM code 

1286 icd_name: Name of ICD-9-CM entity 

1287 is_current_icd: ? 

1288 ip_usage: ? 

1289 op_usage: ? 

1290 avg_usage: ? 

1291 is_nec: ? 

1292 snomed_cid: SNOMED-CT concept ID 

1293 snomed_fsn: SNOMED-CT fully specified name 

1294 is_one_to_one_map: ?; possibly always true in this dataset but not 

1295 true in a broader dataset including things other than 1:1 

1296 mappings? 

1297 core_usage: ? 

1298 in_core: ? 

1299 """ 

1300 self.icd_code = icd_code 

1301 self.icd_name = icd_name 

1302 self.is_current_icd = bool(int(is_current_icd)) 

1303 self.ip_usage = self.to_float(ip_usage) 

1304 self.op_usage = self.to_float(op_usage) 

1305 self.avg_usage = self.to_float(avg_usage) 

1306 self.is_nec = bool(int(is_nec)) 

1307 self.snomed_cid = int(snomed_cid) 

1308 self.snomed_fsn = snomed_fsn 

1309 self.is_one_to_one_map = bool(int(is_one_to_one_map)) 

1310 self.core_usage = self.to_float(core_usage) 

1311 self.in_core = bool(int(in_core)) 

1312 

1313 def __repr__(self) -> str: 

1314 return simple_repr( 

1315 self, 

1316 [ 

1317 "icd_code", 

1318 "icd_name", 

1319 "is_current_icd", 

1320 "ip_usage", 

1321 "op_usage", 

1322 "avg_usage", 

1323 "is_nec", 

1324 "snomed_cid", 

1325 "snomed_fsn", 

1326 "is_one_to_one_map", 

1327 "core_usage", 

1328 "in_core", 

1329 ], 

1330 ) 

1331 

1332 def snomed_concept(self) -> SnomedConcept: 

1333 """ 

1334 Returns the associated SNOMED-CT concept. 

1335 """ 

1336 return SnomedConcept(self.snomed_cid, self.snomed_fsn) 

1337 

1338 def __str__(self) -> str: 

1339 return ( 

1340 f"ICD-9-CM {self.icd_code} ({self.icd_name}) " 

1341 f"-> SNOMED-CT {self.snomed_concept()}" 

1342 ) 

1343 

1344 

1345@cache_region_static.cache_on_arguments(function_key_generator=fkg) 

1346def get_all_icd9cm_snomed_concepts_from_umls( 

1347 tsv_filename: str, 

1348) -> Dict[str, SnomedConcept]: 

1349 """ 

1350 Reads in all ICD-9-CM SNOMED-CT codes that are supported by the client, 

1351 from the UMLS data file, from 

1352 https://www.nlm.nih.gov/research/umls/mapping_projects/icd9cm_to_snomedct.html. 

1353 

1354 Args: 

1355 tsv_filename: TSV filename to read 

1356 

1357 Returns: 

1358 dict: maps lookup strings to :class:`SnomedConcept` objects 

1359 

1360 NOT CURRENTLY USED. 

1361 """ 

1362 log.info("Loading SNOMED-CT ICD-9-CM codes from file: {}", tsv_filename) 

1363 concepts = {} # type: Dict[str, SnomedConcept] 

1364 with open(tsv_filename, "r") as tsvin: 

1365 reader = csv.reader(tsvin, delimiter="\t") 

1366 header = next(reader, None) 

1367 if header != UmlsIcd9SnomedRow.HEADER: 

1368 raise ValueError( 

1369 f"ICD-9-CM TSV file has unexpected header: {header!r}; " 

1370 f"expected {UmlsIcd9SnomedRow.HEADER!r}" 

1371 ) 

1372 for row in reader: 

1373 entry = UmlsIcd9SnomedRow(*row) 

1374 if entry.icd_code not in CLIENT_ICD9CM_CODES: 

1375 continue 

1376 if not entry.is_one_to_one_map: 

1377 continue 

1378 if entry.icd_code in concepts: 

1379 log.warning( 

1380 "Duplicate ICD-9-CM code found in SNOMED file " 

1381 "{!r}: {!r}", 

1382 tsv_filename, 

1383 entry.icd_code, 

1384 ) 

1385 continue 

1386 concept = entry.snomed_concept() 

1387 # log.debug("{}", entry) 

1388 concepts[entry.icd_code] = concept 

1389 missing = CLIENT_ICD9CM_CODES - set(concepts.keys()) 

1390 if missing: 

1391 log.info( 

1392 "No SNOMED-CT codes for ICD-9-CM codes: {}", 

1393 ", ".join(sorted(missing)), 

1394 ) 

1395 return concepts 

1396 

1397 

1398# ============================================================================= 

1399# UMLS ICD-10 

1400# ============================================================================= 

1401 

1402 

1403class UmlsSnomedToIcd10Row(object): 

1404 """ 

1405 Simple information-holding class for a row of the ICD-10-CM TSV file from 

1406 https://www.nlm.nih.gov/research/umls/mapping_projects/snomedct_to_icd10cm.html. 

1407 

1408 However, that is unhelpful (many to one). 

1409 

1410 NOT CURRENTLY USED. 

1411 """ 

1412 

1413 HEADER = [ 

1414 "id", 

1415 "effectiveTime", 

1416 "active", 

1417 "moduleId", 

1418 "refsetId", 

1419 "referencedComponentId", 

1420 "referencedComponentName", 

1421 "mapGroup", 

1422 "mapPriority", 

1423 "mapRule", 

1424 "mapAdvice", 

1425 "mapTarget", 

1426 "mapTargetName", 

1427 "correlationId", 

1428 "mapCategoryId", 

1429 "mapCategoryName", 

1430 ] 

1431 MAP_GOOD = "MAP SOURCE CONCEPT IS PROPERLY CLASSIFIED" 

1432 

1433 def __init__( 

1434 self, 

1435 row_id: str, 

1436 effective_time: str, 

1437 active: str, 

1438 module_id: str, 

1439 refset_id: str, 

1440 referenced_component_id: str, 

1441 referenced_component_name: str, 

1442 map_group: str, 

1443 map_priority: str, 

1444 map_rule: str, 

1445 map_advice: str, 

1446 map_target: str, 

1447 map_target_name: str, 

1448 correlation_id: str, 

1449 map_category_id: str, 

1450 map_category_name: str, 

1451 ) -> None: 

1452 """ 

1453 Argument order is important. 

1454 

1455 Args: 

1456 row_id: UUID format or similar 

1457 effective_time: date in YYYYMMDD format 

1458 active: ? 

1459 module_id: ? 

1460 refset_id: ? 

1461 referenced_component_id: SNOMED-CT concept ID 

1462 referenced_component_name: SNOMED-CT concept name 

1463 map_group: ?; e.g. 1 

1464 map_priority: ? but e.g. 1, 2; correlates with map_rule 

1465 map_rule: ?; e.g. "TRUE"; "OTHERWISE TRUE" 

1466 map_advice: ?, but e.g. "ALWAYS F32.2" or "ALWAYS F32.2 | 

1467 DESCENDANTS NOT EXHAUSTIVELY MAPPED" 

1468 map_target: ICD-10 code 

1469 map_target_name: ICD-10 name 

1470 correlation_id: a SNOMED-CT concept for the mapping, e.g. 

1471 447561005 = "SNOMED CT source code to target map code 

1472 correlation not specified (foundation metadata concept)" 

1473 map_category_id: a SNOMED-CT concept for the mapping, e.g. 

1474 447637006 = "Map source concept is properly classified 

1475 (foundation metadata concept)" 

1476 map_category_name: SNOMED-CT name corresponding to map_category_id, 

1477 e.g. "MAP SOURCE CONCEPT IS PROPERLY CLASSIFIED" 

1478 """ 

1479 self.row_id = row_id 

1480 self.effective_time = effective_time 

1481 self.active = bool(int(active)) 

1482 self.module_id = int(module_id) 

1483 self.refset_id = int(refset_id) 

1484 self.referenced_component_id = int(referenced_component_id) 

1485 self.referenced_component_name = referenced_component_name 

1486 self.map_group = int(map_group) 

1487 self.map_priority = int(map_priority) 

1488 self.map_rule = map_rule 

1489 self.map_advice = map_advice 

1490 self.map_target = map_target 

1491 self.map_target_name = map_target_name 

1492 self.correlation_id = int(correlation_id) 

1493 self.map_category_id = int(map_category_id) 

1494 self.map_category_name = map_category_name 

1495 

1496 def __repr__(self) -> str: 

1497 return simple_repr( 

1498 self, 

1499 [ 

1500 "row_id", 

1501 "effective_time", 

1502 "active", 

1503 "module_id", 

1504 "refset_id", 

1505 "referenced_component_id", 

1506 "referenced_component_name", 

1507 "map_group", 

1508 "map_priority", 

1509 "map_rule", 

1510 "map_advice", 

1511 "map_target", 

1512 "map_target_name", 

1513 "correlation_id", 

1514 "map_category_id", 

1515 "map_category_name", 

1516 ], 

1517 ) 

1518 

1519 def snomed_concept(self) -> SnomedConcept: 

1520 """ 

1521 Returns the associated SNOMED-CT concept. 

1522 """ 

1523 return SnomedConcept( 

1524 self.referenced_component_id, self.referenced_component_name 

1525 ) 

1526 

1527 @property 

1528 def icd_code(self) -> str: 

1529 return self.map_target 

1530 

1531 @property 

1532 def icd_name(self) -> str: 

1533 return self.map_target_name 

1534 

1535 def __str__(self) -> str: 

1536 return ( 

1537 f"ICD-10 {self.icd_code} ({self.icd_name}) " 

1538 f"-> SNOMED-CT {self.snomed_concept()}" 

1539 ) 

1540 

1541 

1542def get_all_icd10_snomed_concepts_from_umls( 

1543 tsv_filename: str, 

1544) -> Dict[str, SnomedConcept]: 

1545 """ 

1546 Reads in all ICD-10 SNOMED-CT codes that are supported by the client, 

1547 from the UMLS data file, from 

1548 https://www.nlm.nih.gov/research/umls/mapping_projects/snomedct_to_icd10cm.html. 

1549 

1550 Args: 

1551 tsv_filename: TSV filename to read 

1552 

1553 Returns: 

1554 dict: maps lookup strings to :class:`SnomedConcept` objects 

1555 

1556 NOT CURRENTLY USED. 

1557 """ 

1558 log.info("Loading SNOMED-CT ICD-10-CM codes from file: {}", tsv_filename) 

1559 concepts = {} # type: Dict[str, SnomedConcept] 

1560 with open(tsv_filename, "r") as tsvin: 

1561 reader = csv.reader(tsvin, delimiter="\t") 

1562 header = next(reader, None) 

1563 if header != UmlsSnomedToIcd10Row.HEADER: 

1564 raise ValueError( 

1565 f"ICD-9-CM TSV file has unexpected header: {header!r}; " 

1566 f"expected {UmlsSnomedToIcd10Row.HEADER!r}" 

1567 ) 

1568 for row in reader: 

1569 entry = UmlsSnomedToIcd10Row(*row) 

1570 if entry.icd_code not in CLIENT_ICD10_CODES: 

1571 continue 

1572 if entry.icd_code in concepts: 

1573 log.warning( 

1574 "Duplicate ICD-10-CM code found in SNOMED file " 

1575 "{!r}: {!r}", 

1576 tsv_filename, 

1577 entry.icd_code, 

1578 ) 

1579 continue 

1580 concept = entry.snomed_concept() 

1581 # log.debug("{}", entry) 

1582 concepts[entry.icd_code] = concept 

1583 missing = CLIENT_ICD10_CODES - set(concepts.keys()) 

1584 if missing: 

1585 log.info( 

1586 "No SNOMED-CT codes for ICD-10 codes: {}", 

1587 ", ".join(sorted(missing)), 

1588 ) 

1589 return concepts 

1590 

1591 

1592# ============================================================================= 

1593# Athena OHDSI mapping 

1594# ============================================================================= 

1595 

1596# ----------------------------------------------------------------------------- 

1597# Fetch ICD-9-CM and ICD-10 codes (shared for fewer passes through the files) 

1598# ----------------------------------------------------------------------------- 

1599 

1600 

1601def get_icd9cm_icd10_snomed_concepts_from_athena( 

1602 athena_concept_tsv_filename: str, 

1603 athena_concept_relationship_tsv_filename: str, 

1604) -> Tuple[Dict[str, List[SnomedConcept]], Dict[str, List[SnomedConcept]]]: 

1605 """ 

1606 Takes Athena concept and concept-relationship files, and fetches details 

1607 of SNOMED-CT code for all ICD-9-CM and ICD-10[-CM] codes used by CamCOPS. 

1608 

1609 A bit of human review is required; this is probably preferable to using 

1610 gensim or some other automatic similarity check. 

1611 

1612 Args: 

1613 athena_concept_tsv_filename: 

1614 path to ``CONCEPT.csv`` (a tab-separated value file) 

1615 athena_concept_relationship_tsv_filename: 

1616 path to ``CONCEPT_RELATIONSHIP.csv`` (a tab-separated value file) 

1617 

1618 Returns: 

1619 tuple: ``icd9cm, icd10``, where each is a dictionary mapping ICD codes 

1620 to a list of mapped :class:`SnomedConcept` objects. 

1621 

1622 """ 

1623 athena_icd_concepts = get_athena_concepts( 

1624 tsv_filename=athena_concept_tsv_filename, 

1625 vocabulary_ids={AthenaVocabularyId.ICD9CM, AthenaVocabularyId.ICD10CM}, 

1626 concept_codes=CLIENT_ICD9CM_CODES | CLIENT_ICD10_CODES, 

1627 ) 

1628 athena_icd_concepts.sort(key=lambda x: x.concept_code) 

1629 relationships = get_athena_concept_relationships( 

1630 tsv_filename=athena_concept_relationship_tsv_filename, 

1631 concept_id_1_values=set(x.concept_id for x in athena_icd_concepts), 

1632 relationship_id_values={AthenaRelationshipId.MAPS_TO}, 

1633 ) 

1634 athena_snomed_concepts = get_athena_concepts( 

1635 tsv_filename=athena_concept_tsv_filename, 

1636 vocabulary_ids={AthenaVocabularyId.SNOMED}, 

1637 concept_ids=set(x.concept_id_2 for x in relationships), 

1638 ) 

1639 snomed_concepts_icd9 = ( 

1640 OrderedDict() 

1641 ) # type: Dict[str, List[SnomedConcept]] 

1642 snomed_concepts_icd10 = ( 

1643 OrderedDict() 

1644 ) # type: Dict[str, List[SnomedConcept]] 

1645 for icd in athena_icd_concepts: 

1646 target = ( 

1647 snomed_concepts_icd9 

1648 if icd.vocabulary_id == AthenaVocabularyId.ICD9CM 

1649 else snomed_concepts_icd10 

1650 ) 

1651 icd_code = icd.concept_code 

1652 # log.debug("Processing icd = {}", icd) 

1653 possible_snomed = [] # type: List[AthenaConceptRow] 

1654 for rel in relationships: 

1655 if rel.concept_id_1 != icd.concept_id: 

1656 continue 

1657 # log.debug("Processing rel = {}", rel) 

1658 for snomed in athena_snomed_concepts: 

1659 if snomed.concept_id != rel.concept_id_2: 

1660 continue 

1661 # log.debug("Processing snomed = {}", snomed) 

1662 possible_snomed.append(snomed) 

1663 if possible_snomed: 

1664 sclist = [ 

1665 SnomedConcept.create(s.snomed_concept()) 

1666 for s in possible_snomed 

1667 ] 

1668 sclist.sort(key=lambda sc: sc.identifier) 

1669 log.debug("Mapping {} -> {}", icd, sclist) 

1670 target[icd_code] = sclist 

1671 else: 

1672 log.debug("No SNOMED code found for {}", icd) 

1673 return snomed_concepts_icd9, snomed_concepts_icd10 

1674 

1675 

1676# ----------------------------------------------------------------------------- 

1677# Fetch codes from Athena data set and write them to CamCOPS XML 

1678# ----------------------------------------------------------------------------- 

1679 

1680 

1681def send_athena_icd_snomed_to_xml( 

1682 athena_concept_tsv_filename: str, 

1683 athena_concept_relationship_tsv_filename: str, 

1684 icd9_xml_filename: str, 

1685 icd10_xml_filename: str, 

1686) -> None: 

1687 """ 

1688 Reads SNOMED-CT codes for ICD-9-CM and ICD10 from Athena OHDSI files, and 

1689 writes 

1690 

1691 Args: 

1692 athena_concept_tsv_filename: 

1693 path to ``CONCEPT.csv`` (a tab-separated value file) 

1694 athena_concept_relationship_tsv_filename: 

1695 path to ``CONCEPT_RELATIONSHIP.csv`` (a tab-separated value file) 

1696 icd9_xml_filename: 

1697 ICD-9 XML filename to write 

1698 icd10_xml_filename: 

1699 ICD-10 XML filename to write 

1700 """ 

1701 icd9, icd10 = get_icd9cm_icd10_snomed_concepts_from_athena( 

1702 athena_concept_tsv_filename=athena_concept_tsv_filename, 

1703 athena_concept_relationship_tsv_filename=athena_concept_relationship_tsv_filename, # noqa 

1704 ) 

1705 write_snomed_concepts_to_xml(icd9_xml_filename, icd9) 

1706 write_snomed_concepts_to_xml(icd10_xml_filename, icd10) 

1707 

1708 

1709# ----------------------------------------------------------------------------- 

1710# Fetch data from XML 

1711# ----------------------------------------------------------------------------- 

1712 

1713 

1714def get_multiple_snomed_concepts_from_xml( 

1715 xml_filename: str, 

1716 valid_lookups: Set[str] = None, 

1717 require_all: bool = False, 

1718) -> Dict[str, List[SnomedConcept]]: 

1719 """ 

1720 Reads in all SNOMED-CT codes for ICD-9 or ICD-10, from the custom CamCOPS 

1721 XML file for this (made by e.g. :func:`send_athena_icd_snomed_to_xml`). 

1722 

1723 Args: 

1724 xml_filename: XML filename to read 

1725 valid_lookups: possible lookup values 

1726 require_all: require that ``valid_lookups`` is truthy and that all 

1727 values in it are present in the XML 

1728 

1729 Returns: 

1730 dict: maps lookup strings to lists of :class:`SnomedConcept` objects 

1731 

1732 """ 

1733 valid_lookups = set(valid_lookups or []) # type: Set[str] 

1734 xml_concepts = get_snomed_concepts_from_xml(xml_filename) 

1735 camcops_concepts = {} # type: Dict[str, List[SnomedConcept]] 

1736 for lookup, concepts in xml_concepts.items(): 

1737 # Check it 

1738 if valid_lookups and lookup not in valid_lookups: 

1739 log.debug("Ignoring unknown SNOMED-CT lookup: {!r}", lookup) 

1740 continue 

1741 # Stash it 

1742 camcops_concepts[lookup] = concepts # type: ignore[assignment] 

1743 if require_all: 

1744 # Check if any are missing 

1745 assert valid_lookups, "require_all specified but valid_lookups missing" 

1746 if valid_lookups: 

1747 missing = sorted(list(valid_lookups - set(camcops_concepts.keys()))) 

1748 if missing: 

1749 msg = ( 

1750 f'The following {"required " if require_all else ""}' 

1751 f"SNOMED-CT concepts are missing from the XML " 

1752 f"({xml_filename!r}): {missing!r}" 

1753 ) 

1754 if require_all: 

1755 log.critical(msg) 

1756 raise ValueError(msg) 

1757 else: 

1758 log.debug(msg) 

1759 # Done 

1760 return camcops_concepts 

1761 

1762 

1763@cache_region_static.cache_on_arguments(function_key_generator=fkg) 

1764def get_icd9_snomed_concepts_from_xml( 

1765 xml_filename: str, 

1766) -> Dict[str, List[SnomedConcept]]: 

1767 """ 

1768 Reads in all ICD-9-CM SNOMED-CT codes from a custom CamCOPS XML file. 

1769 

1770 Args: 

1771 xml_filename: filename to read 

1772 

1773 Returns: 

1774 dict: maps ICD-9-CM codes to lists of :class:`SnomedConcept` objects 

1775 """ 

1776 return get_multiple_snomed_concepts_from_xml( 

1777 xml_filename, CLIENT_ICD9CM_CODES 

1778 ) 

1779 

1780 

1781@cache_region_static.cache_on_arguments(function_key_generator=fkg) 

1782def get_icd10_snomed_concepts_from_xml( 

1783 xml_filename: str, 

1784) -> Dict[str, List[SnomedConcept]]: 

1785 """ 

1786 Reads in all ICD-10 SNOMED-CT codes from a custom CamCOPS XML file. 

1787 

1788 Args: 

1789 xml_filename: filename to read 

1790 

1791 Returns: 

1792 dict: maps ICD-10 codes to lists of :class:`SnomedConcept` objects 

1793 """ 

1794 return get_multiple_snomed_concepts_from_xml( 

1795 xml_filename, CLIENT_ICD10_CODES 

1796 )