001package org.hl7.fhir.r4.ips; 002 003import java.util.Date; 004import java.util.List; 005 006import org.hl7.fhir.r4.ips.IPSBuilder.TypeAndId; 007import org.hl7.fhir.r4.model.Age; 008import org.hl7.fhir.r4.model.Annotation; 009import org.hl7.fhir.r4.model.Bundle; 010import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; 011import org.hl7.fhir.r4.model.Bundle.BundleType; 012import org.hl7.fhir.r4.model.CodeableConcept; 013import org.hl7.fhir.r4.model.Coding; 014import org.hl7.fhir.r4.model.Composition; 015import org.hl7.fhir.r4.model.Patient; 016import org.hl7.fhir.r4.model.Period; 017import org.hl7.fhir.r4.model.Quantity; 018import org.hl7.fhir.r4.model.Range; 019import org.hl7.fhir.r4.model.Reference; 020import org.hl7.fhir.r4.model.Type; 021import org.hl7.fhir.r4.model.Composition.CompositionStatus; 022import org.hl7.fhir.r4.model.Composition.SectionComponent; 023import org.hl7.fhir.r4.model.Condition; 024import org.hl7.fhir.r4.model.Device; 025import org.hl7.fhir.r4.model.DomainResource; 026import org.hl7.fhir.r4.model.Dosage; 027import org.hl7.fhir.r4.model.Identifier; 028import org.hl7.fhir.r4.model.Medication; 029import org.hl7.fhir.r4.model.MedicationStatement; 030import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; 031import org.hl7.fhir.r4.utils.client.FHIRToolingClient; 032import org.hl7.fhir.utilities.Utilities; 033import org.hl7.fhir.utilities.VersionUtil; 034import org.hl7.fhir.utilities.xhtml.XhtmlNode; 035 036public class IPSBuilder { 037 038 public static class TypeAndId { 039 private String type; 040 private String id; 041 protected TypeAndId(String type, String id) { 042 super(); 043 this.type = type; 044 this.id = id; 045 } 046 public String getType() { 047 return type; 048 } 049 public String getId() { 050 return id; 051 } 052 } 053 054 public static Bundle generateIPS(FHIRToolingClient server, String patientId) { 055 Patient pat = server.fetchResource(Patient.class, patientId); 056 Bundle bnd = initBundle(); 057 Composition cmp = initComposition(bnd, server.getAddress(), pat); 058 pat = processPatient(bnd, server.getAddress(), pat); 059// addMedications(bnd, cmp, server, patientId); 060 addConditions(bnd, cmp, server, patientId); 061 return bnd; 062 } 063 064 private static Bundle initBundle() { 065 Bundle bnd = new Bundle(); 066 bnd.getIdentifier().setSystem("urn:ietf:rfc:3986"); 067 bnd.getIdentifier().setValue(Utilities.makeUuidUrn()); 068 bnd.setType(BundleType.DOCUMENT); 069 bnd.setTimestamp(new Date()); 070 return bnd; 071 } 072 073 private static Composition initComposition(Bundle bnd, String url, Patient pat) { 074 Composition cmp = new Composition(); 075 cmp.setIdBase(Utilities.makeUuidLC()); 076 cmp.setStatus(CompositionStatus.FINAL); 077 cmp.getType().addCoding().setSystem("http://loinc.org").setCode("60591-5"); 078 cmp.setDate(new Date()); 079 cmp.setTitle("International Patient Summary"); 080 cmp.getSubject().setReference("Patient/"+pat.getIdBase()); 081 cmp.addAuthor().setReference("Device/java"); 082 bnd.addEntry().setResource(cmp).setFullUrl(Utilities.pathURL(url, "Composition", cmp.getIdBase())); 083 Device dev = new Device(); 084 dev.setId("java"); 085 dev.addDeviceName().setName("Java Core Library"); 086 dev.addVersion().setValue(VersionUtil.getVersion()); 087 bnd.addEntry().setResource(dev).setFullUrl(Utilities.pathURL(url, "Device", dev.getIdBase())); 088 return cmp; 089 } 090 091 private static Patient processPatient(Bundle bnd, String url, Patient pat) { 092 bnd.addEntry().setResource(pat).setFullUrl(Utilities.pathURL(url, "Patient", pat.getIdBase())); 093 return pat; 094 } 095 096 private static void addMedications(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) { 097 Bundle sb = server.search("MedicationStatement", "?patient="+patientId+"&_include=MedicationStatement:medication&_include=MedicationStatement:source"); 098 SectionComponent sct = cmp.addSection(); 099 sct.setTitle("Medications"); 100 sct.getCode().addCoding().setSystem("http://loinc.org").setCode("10160-0"); 101 sct.getText().setStatus(NarrativeStatus.GENERATED); 102 var x = sct.getText().getDiv(); 103 var tbl = x.table("grid"); 104 var tr = tbl.tr(); 105 tr.th().tx("Medication"); 106 tr.th().tx("Category"); 107 tr.th().tx("Status"); 108 tr.th().tx("When"); 109 tr.th().tx("Dosage"); 110 tr.th().tx("Reason"); 111 tr.th().tx("Source"); 112 tr.th().tx("Notes"); 113 114 boolean ok = false; 115 for (BundleEntryComponent be : sb.getEntry()) { 116 if (be.hasResource() && be.getResource() instanceof MedicationStatement) { 117 MedicationStatement mdstmt = (MedicationStatement) be.getResource(); 118 ok = true; 119 bnd.addEntry().setResource(mdstmt).setFullUrl(Utilities.pathURL(server.getAddress(), "MedicationStatement", mdstmt.getIdBase())); 120 sct.addEntry().setReference("MedicationStatement/"+mdstmt.getIdBase()); 121 tr = tbl.tr(); 122 if (mdstmt.hasMedicationReference()) { 123 Medication med = findMedication(sb, server, mdstmt, mdstmt.getMedicationReference()); 124 if (med == null) { 125 tr.td().b().tx("Unknown?"); 126 } else { 127 tr.td().tx(summarise(med)); 128 bnd.addEntry().setResource(med).setFullUrl(Utilities.pathURL(server.getAddress(), "Medication", med.getIdBase())); 129 } 130 } else { 131 tr.td().tx(genCC(mdstmt.getMedicationCodeableConcept())); 132 } 133 tr.td().tx(genCC(mdstmt.getCategory())); 134 var td = tr.td(); 135 td.tx(mdstmt.getStatus().getDisplay()); 136 if (mdstmt.hasStatusReason()) { 137 td.tx(" ("); 138 td.tx(genCC(mdstmt.getStatusReason())); 139 td.tx(")"); 140 } 141 tr.td().tx(genDT(mdstmt.getEffective())); 142 genDosages(tr.td(), mdstmt.getDosage()); 143 tr.td().tx(genReference(mdstmt, mdstmt.getInformationSource(), bnd, sb, server)); 144 genNotes(tr.td(), mdstmt.getNote()); 145 } 146 } 147 if (!ok) { 148 Condition cnd = new Condition(); 149 cnd.setId(Utilities.makeUuidLC()); 150 151 cnd.getText().setStatus(NarrativeStatus.GENERATED); 152 var rx = cnd.getText().getDiv(); 153 rx.tx("No information is provided about the patient's medical problems"); 154 tr = tbl.tr(); 155 tr.td().colspan(7).tx("No information is provided about the patient's medical problems"); 156 cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active"); 157 cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems"); 158 cnd.getSubject().setReference("Patient/"+patientId); 159 } 160 } 161 162 163 private static void genDosages(XhtmlNode x, List<Dosage> dosages) { 164 if (dosages == null || dosages.size() == 0) { 165 166 } else if (dosages.size() == 1) { 167 genDosage(x, dosages.get(0)); 168 } else { 169 var ul = x.ul(); 170 for (Dosage d : dosages) { 171 genDosage(ul.li(), d); 172 } 173 } 174 } 175 176 private static void genDosage(XhtmlNode x, Dosage dosage) { 177 x.tx(dosage.getText()); 178 if (dosage.hasAsNeeded()) { 179 x.nbsp(); 180 if (dosage.hasAsNeededBooleanType()) { 181 if (dosage.getAsNeededBooleanType().booleanValue()) { 182 x.tx(" (as needed)"); 183 } 184 } else { 185 x.tx(genDT(dosage.getAsNeeded())); 186 } 187 } else if (dosage.hasTiming()) { 188 x.nbsp(); 189 x.tx(genDT(dosage.getTiming())); 190 } 191 if (dosage.hasSite()) { 192 x.tx(". "); 193 x.tx(genDT(dosage.getSite())); 194 } 195 if (dosage.hasRoute()) { 196 x.tx(". "); 197 x.tx(genDT(dosage.getRoute())); 198 } 199 } 200 201 private static Medication findMedication(Bundle sb, FHIRToolingClient server, MedicationStatement mdstmt, Reference ref) { 202 if (ref == null || !ref.hasReference()) { 203 return null; 204 } 205 if (ref.getReference().startsWith("#")) { 206 207 } else { 208 209 } 210 return null; 211 } 212 213 private static void addConditions(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) { 214 Bundle sb = server.search("Condition", "?patient="+patientId+"&_include=Condition:asserter"); 215 SectionComponent sct = cmp.addSection(); 216 sct.setTitle("Problems"); 217 sct.getCode().addCoding().setSystem("http://loinc.org").setCode("11450-4"); 218 sct.getText().setStatus(NarrativeStatus.GENERATED); 219 var x = sct.getText().getDiv(); 220 var tbl = x.table("grid"); 221 var tr = tbl.tr(); 222 tr.th().tx("Code"); 223 tr.th().tx("Category"); 224 tr.th().tx("Severity"); 225 tr.th().tx("Status"); 226 tr.th().tx("Onset"); 227 tr.th().tx("Abatement"); 228 tr.th().tx("Source"); 229 tr.th().tx("Notes"); 230 231 boolean ok = false; 232 for (BundleEntryComponent be : sb.getEntry()) { 233 if (be.hasResource() && be.getResource() instanceof Condition) { 234 Condition cnd = (Condition) be.getResource(); 235 ok = true; 236 bnd.addEntry().setResource(cnd).setFullUrl(Utilities.pathURL(server.getAddress(), "Condition", cnd.getIdBase())); 237 sct.addEntry().setReference("Condition/"+cnd.getIdBase()); 238 tr = tbl.tr(); 239 tr.td().tx(genCC(cnd.getCode())); 240 tr.td().tx(genCC(cnd.getCategory())); 241 tr.td().tx(genCC(cnd.getSeverity())); 242 tr.td().tx(genStatus(cnd)); 243 tr.td().tx(genDT(cnd.getOnset())); 244 tr.td().tx(genDT(cnd.getAbatement())); 245 tr.td().tx(genSource(cnd, bnd, sb, server)); 246 genNotes(tr.td(), cnd.getNote()); 247 248 } 249 } 250 if (!ok) { 251 Condition cnd = new Condition(); 252 cnd.setId(Utilities.makeUuidLC()); 253 254 cnd.getText().setStatus(NarrativeStatus.GENERATED); 255 var rx = cnd.getText().getDiv(); 256 rx.tx("No information is provided about the patient's medical problems"); 257 tr = tbl.tr(); 258 tr.td().colspan(7).tx("No information is provided about the patient's medical problems"); 259 cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active"); 260 cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems"); 261 cnd.getSubject().setReference("Patient/"+patientId); 262 } 263 } 264 265 266 private static String genReference(DomainResource src, Reference ref, Bundle bnd, Bundle search, FHIRToolingClient server) { 267 if (ref == null || ref.isEmpty()) { 268 return null; 269 } 270 boolean contained = false; 271 DomainResource tgt = null; 272 if (ref.hasReference()) { 273 if (ref.getReference().startsWith("#")) { 274 tgt = (DomainResource) src.getContained(ref.getReference()); 275 contained = true; 276 } else { 277 TypeAndId tid = getTypeAndId(server.getAddress(), ref.getReference()); 278 if (tid != null) { 279 tgt = findInBundle(bnd, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId())); 280 if (tgt == null) { 281 tgt = findInBundle(search, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId())); 282 if (tgt == null) { 283 tgt = (DomainResource) server.read(tid.getType(), tid.getId()); 284 } 285 } else { 286 contained = true; 287 } 288 } 289 } 290 } 291 if (tgt != null) { 292 if (!contained) { 293 bnd.addEntry().setResource(tgt).setFullUrl(Utilities.pathURL(server.getAddress(), tgt.fhirType(), tgt.getIdBase())); 294 } 295 return summarise(tgt); 296 } else if (ref.hasDisplay()) { 297 return ref.getDisplay(); 298 } else if (ref.hasReference()) { 299 return ref.getReference(); 300 } else if (ref.hasIdentifier()) { 301 return genIdentifier(ref.getIdentifier()); 302 } else { 303 return "unknown"; 304 } 305 } 306 307 308 private static TypeAndId getTypeAndId(String baseUrl, String url) { 309 if (Utilities.noString(url)) { 310 return null; 311 } 312 if (url.startsWith(baseUrl+"/")) { 313 url = url.substring(baseUrl.length()+1); 314 } 315 String[] p = url.split("\\/"); 316 if (p.length > 1) { 317 if ("_history".equals(p[p.length-2]) && p.length > 3) { 318 return new TypeAndId(p[p.length-4], p[p.length-3]); 319 } else { 320 return new TypeAndId(p[p.length-2], p[p.length-1]); 321 } 322 } 323 return null; 324 } 325 326 private static DomainResource findInBundle(Bundle bnd, String url) { 327 for (BundleEntryComponent be : bnd.getEntry()) { 328 if (url.equals(be.getFullUrl()) && be.hasResource() && be.getResource() instanceof DomainResource) { 329 return (DomainResource) be.getResource(); 330 } 331 } 332 return null; 333 } 334 335 private static String summarise(DomainResource tgt) { 336 // TODO Auto-generated method stub 337 return null; 338 } 339 340 private static String genIdentifier(Identifier id) { 341 return id.getValue(); 342 } 343 344 private static void genNotes(XhtmlNode td, List<Annotation> notes) { 345 if (notes.size() > 0) { 346 if (notes.size() == 1) { 347 genNote(td, notes.get(0)); 348 } else { 349 var ul = td.ul(); 350 for (Annotation a : notes) { 351 genNote(ul.li(), a); 352 } 353 } 354 } 355 } 356 357 private static void genNote(XhtmlNode td, Annotation annotation) { 358 td.tx(annotation.getText()); 359 } 360 361 private static String genSource(Condition cnd, Bundle bnd, Bundle sb, FHIRToolingClient server) { 362 if (cnd.hasAsserter()) { 363 return genReference(cnd, cnd.getAsserter(), bnd, sb, server); 364 } else if (cnd.hasRecorder()) { 365 return genReference(cnd, cnd.getRecorder(), bnd, sb, server); 366 } else { 367 return null; 368 } 369 } 370 371 private static String genDT(Type v) { 372 if (v == null) { 373 return null; 374 } 375 if (v.isPrimitive()) { 376 return v.primitiveValue(); 377 } 378 if (v instanceof Age) { 379 return genQty((Age) v); 380 } 381 if (v instanceof Period) { 382 Period p = (Period) v; 383 return genDT(p.getStartElement())+" - "+genDT(p.getStartElement()); 384 } 385 if (v instanceof Range) { 386 Range p = (Range) v; 387 return genDT(p.getLow())+" - "+genDT(p.getHigh()); 388 } 389 return "not done: "+v.fhirType(); 390 } 391 392 private static String genQty(Quantity v) { 393 return v.getValue().toPlainString()+v.getUnit(); 394 } 395 396 private static String genStatus(Condition cnd) { 397 if (cnd.hasClinicalStatus() && cnd.hasVerificationStatus()) { 398 return genCC(cnd.getClinicalStatus()) +"/"+genCC(cnd.getVerificationStatus()); 399 } else if (cnd.hasClinicalStatus()) { 400 return genCC(cnd.getClinicalStatus()); 401 } else if (cnd.hasVerificationStatus()) { 402 return genCC(cnd.getVerificationStatus()); 403 } else { 404 return null; 405 } 406 } 407 408 private static String genCC(List<CodeableConcept> list) { 409 if (list != null && list.size() == 1) { 410 return genCC(list.get(0)); 411 } else { 412 return null; 413 } 414 } 415 416 private static String genCC(CodeableConcept code) { 417 if (code.hasText()) { 418 return code.getText(); 419 } else if (code.hasCoding()) { 420 Coding c = code.getCodingFirstRep(); 421 if (c.hasDisplay()) { 422 return c.getDisplay(); 423 } else { 424 return c.getCode(); 425 } 426 } else { 427 return null; 428 } 429 } 430 431}