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