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