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