
001package org.hl7.fhir.convertors.misc; 002 003import java.io.IOException; 004import java.util.Arrays; 005import java.util.Date; 006import java.util.HashSet; 007import java.util.Set; 008 009import lombok.extern.slf4j.Slf4j; 010import org.hl7.fhir.r4.formats.IParser.OutputStyle; 011import org.hl7.fhir.r4.formats.XmlParser; 012import org.hl7.fhir.r4.formats.XmlParserBase.XmlVersion; 013import org.hl7.fhir.r4.model.BooleanType; 014import org.hl7.fhir.r4.model.CodeSystem; 015import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; 016import org.hl7.fhir.r4.model.CodeSystem.CodeSystemHierarchyMeaning; 017import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent; 018import org.hl7.fhir.r4.model.CodeSystem.PropertyType; 019import org.hl7.fhir.r4.model.CodeType; 020import org.hl7.fhir.r4.model.Coding; 021import org.hl7.fhir.r4.model.DateTimeType; 022import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 023import org.hl7.fhir.r4.model.StringType; 024import org.hl7.fhir.r4.model.ValueSet; 025import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 026import org.hl7.fhir.r4.model.ValueSet.FilterOperator; 027import org.hl7.fhir.r4.terminologies.CodeSystemUtilities; 028import org.hl7.fhir.r4.utils.ToolingExtensions; 029import org.hl7.fhir.utilities.Utilities; 030import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 031import org.hl7.fhir.utilities.http.HTTPResult; 032import org.hl7.fhir.utilities.http.ManagedWebAccess; 033import org.hl7.fhir.utilities.json.model.JsonElement; 034import org.hl7.fhir.utilities.json.model.JsonObject; 035import org.hl7.fhir.utilities.json.parser.JsonParser; 036@Slf4j 037@SuppressWarnings("checkstyle:systemout") 038public class ICD11Generator { 039 040 public static void main(String[] args) throws IOException { 041 new ICD11Generator().execute(args[0], args[1]); 042 } 043 044 private void execute(String base, String dest) throws IOException { 045 CodeSystem cs = makeMMSCodeSystem(); 046 JsonObject version = fetchJson(Utilities.pathURL(base, "/icd/release/11/mms")); 047 String[] p = version.asString("latestRelease").split("\\/"); 048 cs.setVersion(p[6]); 049 JsonObject root = fetchJson(url(base, version.asString("latestRelease"))); 050 cs.setDateElement(new DateTimeType(root.asString("releaseDate"))); 051 for (JsonElement child : root.getJsonArray("child")) { 052 processMMSEntity(cs, base, child.asString(), cs.addConcept(), dest); 053 System.out.println(); 054 } 055 new XmlParser(XmlVersion.V1_1).setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "icd-11-mms.xml")), cs); 056 makeFullVs(dest, cs); 057 058 cs = makeEntityCodeSystem(); 059 root = fetchJson(Utilities.pathURL(base, "/icd/entity")); 060 cs.setVersion(root.asString("releaseId")); 061 cs.setDateElement(new DateTimeType(root.asString("releaseDate"))); 062 cs.setTitle(readString(root, "title")); 063 Set<String> ids = new HashSet<>(); 064 for (JsonElement child : root.getJsonArray("child")) { 065 processEntity(cs, ids, base, tail(child.asString()), dest); 066 System.out.println(); 067 } 068 new XmlParser(XmlVersion.V1_1).setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "icd-11-foundation.xml")), cs); 069 makeFullVs2(dest, cs); 070 System.out.println("finished"); 071 } 072 073 private void processEntity(CodeSystem cs, Set<String> ids, String base, String id, String dest) throws IOException { 074 if (!ids.contains(id)) { 075 System.out.print("."); 076 ids.add(id); 077 org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc = cs.addConcept().setCode(id); 078 JsonObject entity = fetchJson(Utilities.pathURL(base, "icd", "entity", id)); 079 cc.setDisplay(readString(entity, "title")); 080 String d = readString(entity, "definition"); 081 if (d != null) { 082 cc.setDefinition(d); 083 } 084 if (entity.has("inclusion")) { 085 for (JsonElement child : entity.getJsonArray("inclusion")) { 086 JsonObject co = (JsonObject) child; 087 String v = readString(co, "label"); 088 if (v != null) { 089 if (co.has("foundationReference")) { 090 cc.addProperty().setValue(new Coding().setSystem("http://id.who.int/icd11/foundation").setCode(tail(co.asString("foundationReference"))).setDisplay(v)).setCode("inclusion"); 091 } 092 } 093 } 094 } 095 if (entity.has("exclusion")) { 096 for (JsonElement child : entity.getJsonArray("exclusion")) { 097 JsonObject co = (JsonObject) child; 098 String v = readString(co, "label"); 099 if (v != null) { 100 if (co.has("foundationReference")) { 101 cc.addProperty().setValue(new Coding().setSystem("http://id.who.int/icd11/foundation").setCode(tail(co.asString("foundationReference"))).setDisplay(v)).setCode("exclusion"); 102 } 103 } 104 } 105 } 106 if (entity.has("narrowerTerm")) { 107 for (JsonElement child : entity.getJsonArray("narrowerTerm")) { 108 JsonObject co = (JsonObject) child; 109 String v = readString(co, "label"); 110 if (v != null) { 111 if (co.has("narrowerTerm")) { 112 cc.addProperty().setValue(new Coding().setSystem("http://id.who.int/icd11/foundation").setCode(tail(co.asString("foundationReference"))).setDisplay(v)).setCode("narrowerTerm"); 113 } 114 } 115 } 116 } 117 addDesignation(readString(entity, "longDefinition"), cc, "http://id.who.int/icd11/mms/designation", "longDefinition"); 118 addDesignation(readString(entity, "fullySpecifiedName"), cc, "http://snomed.info/sct", "900000000000003001"); 119 if (entity.has("synonym")) { 120 for (JsonElement j : entity.getJsonArray("synonym")) { 121 String v = readString((JsonObject) j, "label"); 122 if (v != null && !v.equals(cc.getDisplay())) { 123 addDesignation(v, cc, "http://id.who.int/icd11/mms/designation", "synonym"); 124 } 125 } 126 } 127 for (JsonElement j : entity.getJsonArray("parent")) { 128 String v = j.asString(); 129 if (!"http://id.who.int/icd/entity".equals(v)) { 130 cc.addProperty().setValue(new CodeType(tail(v))).setCode("narrowerTerm"); 131 } 132 } 133 if (entity.has("child")) { 134 for (JsonElement j : entity.getJsonArray("child")) { 135 String v = j.asString(); 136 cc.addProperty().setValue(new CodeType(tail(v))).setCode("child"); 137 processEntity(cs, ids, base, tail(v), dest); 138 } 139 } 140 } 141 } 142 143 private void makeFullVs(String dest, CodeSystem cs) throws IOException { 144 String url = "http://id.who.int/icd11/ValueSet/all-MMS"; 145 ValueSet vs = new ValueSet(); 146 vs.setId("all-MMS"); 147 vs.setUrl(url); 148 vs.setName("ICDMMSAll"); 149 vs.setTitle("Value Set for all ICD MMS Codes"); 150 vs.setStatus(PublicationStatus.ACTIVE); 151 vs.setExperimental(false); 152 vs.setDate(cs.getDate()); 153 vs.setPublisher("WHO"); 154 vs.setCopyright("Consult WHO For terms of use"); 155 vs.setVersion(cs.getVersion()); 156 vs.setStatus(cs.getStatus()); 157 ConceptSetComponent inc = vs.getCompose().addInclude(); 158 inc.setSystem(cs.getUrl()); 159 new XmlParser(XmlVersion.V1_1).setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "vs-all-MMS.xml")), vs); 160 } 161 162 private void makeFullVs2(String dest, CodeSystem cs) throws IOException { 163 String url = "http://id.who.int/icd11/ValueSet/all-foundation"; 164 ValueSet vs = new ValueSet(); 165 vs.setId("all-foundation"); 166 vs.setUrl(url); 167 vs.setName("ICDFoundationAll"); 168 vs.setTitle("Value Set for all ICD Foundation Concepts"); 169 vs.setStatus(PublicationStatus.ACTIVE); 170 vs.setExperimental(false); 171 vs.setDate(cs.getDate()); 172 vs.setPublisher("WHO"); 173 vs.setCopyright("Consult WHO For terms of use"); 174 vs.setVersion(cs.getVersion()); 175 vs.setStatus(cs.getStatus()); 176 ConceptSetComponent inc = vs.getCompose().addInclude(); 177 inc.setSystem(cs.getUrl()); 178 new XmlParser(XmlVersion.V1_1).setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "vs-all-foundation.xml")), vs); 179 } 180 181 private void processMMSEntity(CodeSystem cs, String base, String ref, org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, String dest) throws IOException { 182 System.out.print("."); 183 JsonObject entity = fetchJson(url(base, ref)); 184 cc.setId(tail(ref)); 185 if (entity.has("code") && !Utilities.noString(entity.asString("code"))) { 186 cc.setCode(entity.asString("code")); 187 } else if (entity.has("blockId") && !Utilities.noString(entity.asString("blockId"))) { 188 cc.setCode(entity.asString("blockId")); 189 } else { 190 cc.setCode(cc.getId()); 191 cc.addProperty().setCode("abstract").setValue(new BooleanType(true)); 192 } 193 if (entity.has("classKind") && !Utilities.noString(entity.asString("classKind")) && !"category".equals(entity.asString("classKind"))) { 194 cc.addProperty().setCode("kind").setValue(new CodeType(entity.asString("classKind"))); 195 } 196 cc.setDisplay(readString(entity, "title")); 197 StringBuilder defn = new StringBuilder(); 198 String d = readString(entity, "definition"); 199 if (d != null) { 200 defn.append(d); 201 } 202 if (d == null && (entity.has("inclusion") || entity.has("exclusion"))) { 203 defn.append(cc.getDisplay()); 204 } 205 if (entity.has("inclusion")) { 206 defn.append(". Includes: "); 207 boolean first = true; 208 for (JsonElement child : entity.getJsonArray("inclusion")) { 209 if (first) first = false; 210 else defn.append(", "); 211 defn.append(readString((JsonObject) child, "label")); 212 } 213 } 214 if (entity.has("exclusion")) { 215 defn.append(". Excludes: "); 216 boolean first = true; 217 for (JsonElement child : entity.getJsonArray("exclusion")) { 218 if (first) first = false; 219 else defn.append(", "); 220 JsonObject co = (JsonObject) child; 221 String v = readString(co, "label"); 222 if (v != null) { 223 defn.append(v); 224 if (co.has("linearizationReference")) { 225 cc.addProperty().setValue(new Coding().setSystem("http://id.who.int/icd11/mms").setCode(tail(co.asString("linearizationReference"))).setDisplay(v)).setCode("exclusion"); 226 } 227 } 228 } 229 } 230 cc.setDefinition(defn.toString()); 231 addDesignation(readString(entity, "longDefinition"), cc, "http://id.who.int/icd11/mms/designation", "longDefinition"); 232 addDesignation(readString(entity, "fullySpecifiedName"), cc, "http://snomed.info/sct", "900000000000003001"); 233 addProperty(readString(entity, "codingNote"), cc, "codingNote"); 234 if (entity.has("indexTerm")) { 235// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); 236// for (JsonElement child : entity.getJsonArray("indexTerm")) { 237// processIndexTerm(cc, b, (JsonObject) child); 238// } 239// if (b.length() > 0) { 240// cc.addProperty().setCode("terms").setValue(new StringType(b.toString())); 241// } 242 for (JsonElement child : entity.getJsonArray("indexTerm")) { 243 processIndexTerm(cc, (JsonObject) child); 244 } 245 } 246 if (entity.has("postcoordinationScale")) { 247 for (JsonElement child : entity.getJsonArray("postcoordinationScale")) { 248 JsonObject o = (JsonObject) child; 249 String name = tail(o.asString("axisName")); 250 ConceptPropertyComponent prop = cc.addProperty(); 251 prop.setCode("postcoordinationScale"); 252 prop.setValue(new CodeType(name)); 253 ToolingExtensions.addBooleanExtension(prop, "http://id.who.int/icd11/extensions/required", o.asBoolean("requiredPostcoordination")); 254 ToolingExtensions.addBooleanExtension(prop, "http://id.who.int/icd11/extensions/repeats", o.asBoolean("allowMultipleValues")); 255 if (o.has("scaleEntity")) { 256 ToolingExtensions.addUriExtension(prop, "http://id.who.int/icd11/extensions/valueSet", buildValueSet(cs, cc.getCode(), name, o, dest)); 257 } 258 } 259 } 260 if (entity.has("child")) { 261 for (JsonElement child : entity.getJsonArray("child")) { 262 processMMSEntity(cs, base, child.asString(), cc.addConcept(), dest); 263 } 264 } 265 } 266 267 private String buildValueSet(CodeSystem cs, String code, String name, JsonObject o, String dest) throws IOException { 268 String id = code + "-" + name; 269 String url = "http://id.who.int/icd11/ValueSet/" + id; 270 ValueSet vs = new ValueSet(); 271 vs.setId(id); 272 vs.setUrl(url); 273 vs.setName("VS" + name + "4" + code); 274 vs.setTitle("Value Set for " + name + " on " + code); 275 vs.setStatus(PublicationStatus.ACTIVE); 276 vs.setExperimental(false); 277 vs.setDate(cs.getDate()); 278 vs.setPublisher("WHO"); 279 vs.setCopyright("Consult WHO For terms of use"); 280 vs.setVersion(cs.getVersion()); 281 vs.setStatus(cs.getStatus()); 282 ConceptSetComponent inc = vs.getCompose().addInclude(); 283 inc.setSystem(cs.getUrl()); 284 for (JsonElement e : o.getJsonArray("scaleEntity")) { 285 inc.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue(tail(e.asString())); 286 } 287 new XmlParser(XmlVersion.V1_1).setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "vs-" + id + ".xml")), vs); 288 return url; 289 } 290 291 private void processIndexTerm(org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, JsonObject child) { 292 String s = readString(child, "label"); 293 if (s != null) { 294 if (!s.equals(cc.getDisplay())) { 295 cc.addDesignation().setValue(s).setUse(new Coding().setSystem("http://id.who.int/icd11/mms/designation").setCode("term")); 296 } 297 } 298 } 299 300 // private void processIndexTerm(org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, CommaSeparatedStringBuilder b, JsonObject child) { 301// String s = readString(child, "label"); 302// if (s != null) { 303// if (!s.equals(cc.getDisplay())) { 304// b.append(s); 305// } 306// } 307// 308// } 309// 310 private String tail(String ref) { 311 return ref.substring(ref.lastIndexOf("/") + 1); 312 } 313 314 private void addExtension(String v, org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, String url) { 315 if (v != null) { 316 ToolingExtensions.setStringExtension(cc, url, v); 317 } 318 } 319 320 private void addDesignation(String v, org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, String system, String code) { 321 if (v != null) { 322 cc.addDesignation().setValue(v.replace("\r", "").replace("\n", "")).setUse(new Coding().setSystem(system).setCode(code)); 323 } 324 } 325 326 private void addProperty(String v, org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent cc, String code) { 327 if (v != null) { 328 cc.addProperty().setValue(new StringType(v.replace("\r", " ").replace("\n", ""))).setCode(code); 329 } 330 } 331 332 private String readString(JsonObject obj, String name) { 333 JsonObject p = obj.getJsonObject(name); 334 if (p == null) { 335 return null; 336 } 337 if (p.has("@value")) { 338 return p.get("@value").asString(); 339 } 340 return null; 341 } 342 343 private String url(String base, String u) { 344 return u.replace("http://id.who.int", base); 345 } 346 347 private CodeSystem makeMMSCodeSystem() { 348 CodeSystem cs = new CodeSystem(); 349 cs.setId("icd11-mms"); 350 cs.setUrl("http://id.who.int/icd11/mms"); 351 cs.setName("ICD11MMS"); 352 cs.setTitle("ICD-11 MMS Linearization"); 353 cs.setStatus(PublicationStatus.ACTIVE); 354 cs.setExperimental(false); 355 cs.setDate(new Date()); 356 cs.setPublisher("WHO"); 357 cs.setCopyright("Consult WHO For terms of use"); 358 cs.setCaseSensitive(true); 359 cs.setHierarchyMeaning(CodeSystemHierarchyMeaning.CLASSIFIEDWITH); 360 cs.setCompositional(true); 361 cs.setVersionNeeded(true); 362 cs.setValueSet("http://id.who.int/icd11/ValueSet/all-MMS"); 363 cs.setContent(CodeSystemContentMode.COMPLETE); 364 CodeSystemUtilities.defineCodeSystemProperty(cs, "kind", "The kind of artifact this concept represents", PropertyType.CODE).setUri("http://id.who.int/icd11/properties#kind"); 365 CodeSystemUtilities.defineCodeSystemProperty(cs, "terms", "Other keywords for searching", PropertyType.STRING).setUri("http://id.who.int/icd11/properties#terms"); 366 CodeSystemUtilities.defineCodeSystemProperty(cs, "codingNote", "Coding advice for this concept", PropertyType.STRING).setUri("http://id.who.int/icd11/properties#codingNote"); 367 CodeSystemUtilities.defineCodeSystemProperty(cs, "exclusion", "References to diseases that are excluded from this concept", PropertyType.CODING).setUri("http://id.who.int/icd11/properties#exclusion"); 368 CodeSystemUtilities.defineCodeSystemProperty(cs, "abstract", "If concept is abstract", PropertyType.BOOLEAN); 369 CodeSystemUtilities.defineCodeSystemProperty(cs, "postcoordinationScale", "", PropertyType.CODE).setUri("http://id.who.int/icd11/properties#postcoordinationScale"); 370 return cs; 371 } 372 373 private CodeSystem makeEntityCodeSystem() { 374 CodeSystem cs = new CodeSystem(); 375 cs.setId("icd11-foundation"); 376 cs.setUrl("http://id.who.int/icd11/foundation"); 377 cs.setName("ICD11Entity"); 378 cs.setTitle("ICD-11 Entities (Foundation)"); 379 cs.setStatus(PublicationStatus.ACTIVE); 380 cs.setExperimental(false); 381 cs.setDate(new Date()); 382 cs.setPublisher("WHO"); 383 cs.setCopyright("Consult WHO For terms of use"); 384 cs.setCaseSensitive(true); 385 cs.setHierarchyMeaning(CodeSystemHierarchyMeaning.ISA); // though we aren't going to have a hierarchy 386// cs.setCompositional(true); 387// cs.setVersionNeeded(true); 388 cs.setValueSet("http://id.who.int/icd11/ValueSet/all-foundation"); 389 cs.setContent(CodeSystemContentMode.COMPLETE); 390 CodeSystemUtilities.defineCodeSystemProperty(cs, "exclusion", "References to diseases that are excluded from this concept", PropertyType.CODING).setUri("http://id.who.int/icd11/properties#exclusion"); 391 CodeSystemUtilities.defineCodeSystemProperty(cs, "inclusion", "References to diseases that are included from this concept", PropertyType.CODING).setUri("http://id.who.int/icd11/properties#inclusion"); 392 CodeSystemUtilities.defineCodeSystemProperty(cs, "narrowerTerm", "Narrower terms for this entity", PropertyType.CODE).setUri("http://id.who.int/icd11/properties#narrowerTerm"); 393 CodeSystemUtilities.defineCodeSystemProperty(cs, "parent", "Parent for this concept", PropertyType.CODE); 394 CodeSystemUtilities.defineCodeSystemProperty(cs, "child", "Child for this concept", PropertyType.CODE); 395 return cs; 396 } 397 398 399 private JsonObject fetchJson(String source) throws IOException { 400 HTTPResult res = ManagedWebAccess.accessor(Arrays.asList("web")).withHeader("API-Version", "v2").withHeader("Accept-Language", "en").get(source,"application/json"); 401 res.checkThrowException(); 402 return JsonParser.parseObject(res.getContent()); 403 } 404 405 406}