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