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