001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.List; 007 008import org.hl7.fhir.exceptions.DefinitionException; 009import org.hl7.fhir.exceptions.FHIRException; 010import org.hl7.fhir.exceptions.FHIRFormatError; 011import org.hl7.fhir.r5.model.DomainResource; 012import org.hl7.fhir.r5.model.Resource; 013import org.hl7.fhir.r5.model.Base; 014import org.hl7.fhir.r5.model.DataType; 015import org.hl7.fhir.r5.model.DiagnosticReport; 016import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 017import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 018import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 019import org.hl7.fhir.r5.renderers.utils.DirectWrappers; 020import org.hl7.fhir.r5.renderers.utils.RenderingContext; 021import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 022import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 023import org.hl7.fhir.r5.utils.EOperationOutcome; 024import org.hl7.fhir.utilities.Utilities; 025import org.hl7.fhir.utilities.xhtml.XhtmlNode; 026 027public class DiagnosticReportRenderer extends ResourceRenderer { 028 029 public class ObservationNode { 030 private String ref; 031 private ResourceWithReference obs; 032 private List<ObservationNode> contained; 033 } 034 035 036 public DiagnosticReportRenderer(RenderingContext context) { 037 super(context); 038 } 039 040 public DiagnosticReportRenderer(RenderingContext context, ResourceContext rcontext) { 041 super(context, rcontext); 042 } 043 044 public boolean render(XhtmlNode x, Resource dr) throws IOException, FHIRException, EOperationOutcome { 045 return render(x, (DiagnosticReport) dr); 046 } 047 048 public boolean render(XhtmlNode x, ResourceWrapper dr) throws IOException, FHIRException, EOperationOutcome { 049 XhtmlNode h2 = x.h2(); 050 render(h2, getProperty(dr, "code").value()); 051 h2.tx(" "); 052 PropertyWrapper pw = getProperty(dr, "category"); 053 if (valued(pw)) { 054 h2.tx("("); 055 boolean first = true; 056 for (BaseWrapper b : pw.getValues()) { 057 if (first) first = false; else h2.tx(", "); 058 render(h2, b); 059 } 060 h2.tx(") "); 061 } 062 XhtmlNode tbl = x.table("grid"); 063 XhtmlNode tr; 064 if (dr.has("subject")) { 065 tr = tbl.tr(); 066 tr.td().tx("Subject"); 067 populateSubjectSummary(tr.td(), getProperty(dr, "subject").value()); 068 } 069 070 DataType eff = null; 071 DataType iss = null; 072 073 if (dr.has("effective[x]")) { 074 tr = tbl.tr(); 075 tr.td().tx("When For"); 076 eff = (DataType) getProperty(dr, "effective[x]").value().getBase(); 077 render(tr.td(), eff); 078 } 079 if (dr.has("issued")) { 080 tr = tbl.tr(); 081 tr.td().tx("Reported"); 082 eff = (DataType) getProperty(dr, "issued").value().getBase(); 083 render(tr.td(), getProperty(dr, "issued").value()); 084 } 085 086 pw = getProperty(dr, "perfomer"); 087 if (valued(pw)) { 088 tr = tbl.tr(); 089 tr.td().tx(Utilities.pluralize("Performer", pw.getValues().size())); 090 XhtmlNode tdr = tr.td(); 091 for (BaseWrapper v : pw.getValues()) { 092 tdr.tx(" "); 093 render(tdr, v); 094 } 095 } 096 pw = getProperty(dr, "identifier"); 097 if (valued(pw)) { 098 tr = tbl.tr(); 099 tr.td().tx(Utilities.pluralize("Identifier", pw.getValues().size())+":"); 100 XhtmlNode tdr = tr.td(); 101 for (BaseWrapper v : pw.getValues()) { 102 tdr.tx(" "); 103 render(tdr, v); 104 } 105 } 106 pw = getProperty(dr, "request"); 107 if (valued(pw)) { 108 tr = tbl.tr(); 109 tr.td().tx(Utilities.pluralize("Request", pw.getValues().size())+":"); 110 XhtmlNode tdr = tr.td(); 111 for (BaseWrapper v : pw.getValues()) { 112 tdr.tx(" "); 113 render(tdr, v); 114 } 115 tdr.br(); 116 } 117 118 119 x.para().b().tx("Report Details"); 120 121 pw = getProperty(dr, "result"); 122 if (valued(pw)) { 123 List<ObservationNode> observations = fetchObservations(pw.getValues(), dr); 124 buildObservationsTable(x, observations, eff, iss); 125 } 126 127 pw = getProperty(dr, "conclusion"); 128 if (valued(pw)) { 129 render(x.para(), pw.value()); 130 } 131 132 pw = getProperty(dr, "conclusionCode"); 133 if (!valued(pw)) { 134 pw = getProperty(dr, "codedDiagnosis"); 135 } 136 if (valued(pw)) { 137 XhtmlNode p = x.para(); 138 p.b().tx("Coded Conclusions :"); 139 XhtmlNode ul = x.ul(); 140 for (BaseWrapper v : pw.getValues()) { 141 render(ul.li(), v); 142 } 143 } 144 return false; 145 } 146 147 public boolean render(XhtmlNode x, DiagnosticReport dr) throws IOException, FHIRException, EOperationOutcome { 148 render(x, new DirectWrappers.ResourceWrapperDirect(this.context, dr)); 149 150 return true; 151 } 152 153 public void describe(XhtmlNode x, DiagnosticReport dr) { 154 x.tx(display(dr)); 155 } 156 157 public String display(DiagnosticReport dr) { 158 return display(dr.getCode()); 159 } 160 161 @Override 162 public String display(Resource r) throws UnsupportedEncodingException, IOException { 163 return display((DiagnosticReport) r); 164 } 165 166 @Override 167 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 168 return "Not done yet"; 169 } 170 171 private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome { 172 ResourceWrapper r = fetchResource(subject); 173 if (r == null) 174 container.tx("Unable to get Patient Details"); 175 else if (r.getName().equals("Patient")) 176 generatePatientSummary(container, r); 177 else 178 container.tx("Not done yet"); 179 } 180 181 private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 182 new PatientRenderer(context).describe(c, r); 183 } 184 185 private List<ObservationNode> fetchObservations(List<BaseWrapper> list, ResourceWrapper rw) throws UnsupportedEncodingException, FHIRException, IOException { 186 List<ObservationNode> res = new ArrayList<ObservationNode>(); 187 for (BaseWrapper b : list) { 188 if (b.has("reference")) { 189 ObservationNode obs = new ObservationNode(); 190 obs.ref = b.get("reference").primitiveValue(); 191 obs.obs = resolveReference(rw, obs.ref); 192 if (obs.obs != null && obs.obs.getResource() != null) { 193 PropertyWrapper t = getProperty(obs.obs.getResource(), "contained"); 194 if (t != null && t.hasValues()) { 195 obs.contained = fetchObservations(t.getValues(), rw); 196 } 197 } 198 res.add(obs); 199 } 200 } 201 return res; 202 } 203 204 private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 205 XhtmlNode tbl = root.table("grid"); 206 boolean refRange = scanObsForRefRange(observations); 207 boolean flags = scanObsForFlags(observations); 208 boolean note = scanObsForNote(observations); 209 boolean effectiveTime = scanObsForEffective(observations, eff); 210 boolean issued = scanObsForIssued(observations, iss); 211 int cs = 2; 212 if (refRange) cs++; 213 if (flags) cs++; 214 if (note) cs++; 215 if (issued) cs++; 216 if (effectiveTime) cs++; 217 XhtmlNode tr = tbl.tr(); 218 tr.td().b().tx("Code"); 219 tr.td().b().tx("Value"); 220 if (refRange) { 221 tr.td().b().tx("Reference Range"); 222 } 223 if (flags) { 224 tr.td().b().tx("Flags"); 225 } 226 if (note) { 227 tr.td().b().tx("Note"); 228 } 229 if (effectiveTime) { 230 tr.td().b().tx("When For"); 231 } 232 if (issued) { 233 tr.td().b().tx("Reported"); 234 } 235 for (ObservationNode o : observations) { 236 addObservationToTable(tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss); 237 } 238 } 239 240 private boolean scanObsForRefRange(List<ObservationNode> observations) { 241 for (ObservationNode o : observations) { 242 if (o.obs != null && o.obs.getResource() != null) { 243 PropertyWrapper pw = getProperty(o.obs.getResource(), "referenceRange"); 244 if (valued(pw)) { 245 return true; 246 } 247 } 248 if (o.contained != null) { 249 if (scanObsForRefRange(o.contained)) { 250 return true; 251 } 252 } 253 } 254 return false; 255 } 256 257 private boolean scanObsForNote(List<ObservationNode> observations) { 258 for (ObservationNode o : observations) { 259 if (o.obs != null && o.obs.getResource() != null) { 260 PropertyWrapper pw = getProperty(o.obs.getResource(), "note"); 261 if (valued(pw)) { 262 return true; 263 } 264 } 265 if (o.contained != null) { 266 if (scanObsForNote(o.contained)) { 267 return true; 268 } 269 } 270 } 271 return false; 272 } 273 274 private boolean scanObsForIssued(List<ObservationNode> observations, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 275 for (ObservationNode o : observations) { 276 if (o.obs != null && o.obs.getResource() != null) { 277 PropertyWrapper pw = getProperty(o.obs.getResource(), "issued"); 278 if (valued(pw)) { 279 if (!Base.compareDeep(pw.value().getBase(), iss, true)) { 280 return true; 281 } 282 } 283 } 284 if (o.contained != null) { 285 if (scanObsForIssued(o.contained, iss)) { 286 return true; 287 } 288 } 289 } 290 return false; 291 } 292 293 private boolean scanObsForEffective(List<ObservationNode> observations, DataType eff) throws UnsupportedEncodingException, FHIRException, IOException { 294 for (ObservationNode o : observations) { 295 if (o.obs != null && o.obs.getResource() != null) { 296 PropertyWrapper pw = getProperty(o.obs.getResource(), "effective[x]"); 297 if (valued(pw)) { 298 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 299 return true; 300 } 301 } 302 } 303 if (o.contained != null) { 304 if (scanObsForEffective(o.contained, eff)) { 305 return true; 306 } 307 } 308 } 309 return false; 310 } 311 312 private boolean scanObsForFlags(List<ObservationNode> observations) throws UnsupportedEncodingException, FHIRException, IOException { 313 for (ObservationNode o : observations) { 314 if (o.obs != null && o.obs.getResource() != null) { 315 PropertyWrapper pw = getProperty(o.obs.getResource(), "interpretation"); 316 if (valued(pw)) { 317 return true; 318 } 319 pw = getProperty(o.obs.getResource(), "status"); 320 if (valued(pw)) { 321 if (!pw.value().getBase().primitiveValue().equals("final")) { 322 return true; 323 } 324 } 325 326 } 327 if (o.contained != null) { 328 if (scanObsForFlags(o.contained)) { 329 return true; 330 } 331 } 332 } 333 return false; 334 } 335 336 private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i, String cs, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 337 XhtmlNode tr = tbl.tr(); 338 if (o.obs != null && o.obs.getReference() == null) { 339 XhtmlNode td = tr.td().colspan(cs); 340 td.i().tx("This Observation could not be resolved"); 341 } else { 342 if (o.obs != null && o.obs.getResource() != null) { 343 addObservationToTable(tr, o.obs.getResource(), i, o.obs.getReference(), refRange, flags, note, effectiveTime, issued, eff, iss); 344 } else { 345 XhtmlNode td = tr.td().colspan(cs); 346 td.i().tx("Observation"); 347 } 348 if (o.contained != null) { 349 for (ObservationNode c : o.contained) { 350 addObservationToTable(tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss); 351 } 352 } 353 } 354 } 355 356 private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i, String ref, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 357 358 // code (+bodysite) 359 XhtmlNode td = tr.td(); 360 PropertyWrapper pw = getProperty(obs, "code"); 361 if (valued(pw)) { 362 render(td.ah(ref), pw.value()); 363 } 364 pw = getProperty(obs, "bodySite"); 365 if (valued(pw)) { 366 td.tx(" ("); 367 render(td, pw.value()); 368 td.tx(")"); 369 } 370 371 // value / dataAbsentReason (in red) 372 td = tr.td(); 373 pw = getProperty(obs, "value[x]"); 374 if (valued(pw)) { 375 render(td, pw.value()); 376 } else { 377 pw = getProperty(obs, "dataAbsentReason"); 378 if (valued(pw)) { 379 XhtmlNode span = td.span("color: maroon", "Error"); 380 span.tx("Error: "); 381 render(span.b(), pw.value()); 382 } 383 } 384 if (refRange) { 385 // reference range 386 td = tr.td(); 387 pw = getProperty(obs, "referenceRange"); 388 if (valued(pw)) { 389 boolean first = true; 390 for (BaseWrapper v : pw.getValues()) { 391 if (first) first = false; else td.br(); 392 PropertyWrapper pwr = getProperty(v, "type"); 393 if (valued(pwr)) { 394 render(td, pwr.value()); 395 td.tx(": "); 396 } 397 PropertyWrapper pwt = getProperty(v, "text"); 398 if (valued(pwt)) { 399 render(td, pwt.value()); 400 } else { 401 PropertyWrapper pwl = getProperty(v, "low"); 402 PropertyWrapper pwh = getProperty(v, "high"); 403 if (valued(pwl) && valued(pwh)) { 404 render(td, pwl.value()); 405 td.tx(" - "); 406 render(td, pwh.value()); 407 } else if (valued(pwl)) { 408 td.tx(">"); 409 render(td, pwl.value()); 410 } else if (valued(pwh)) { 411 td.tx("<"); 412 render(td, pwh.value()); 413 } else { 414 td.tx("??"); 415 } 416 } 417 pwr = getProperty(v, "appliesTo"); 418 PropertyWrapper pwrA = getProperty(v, "age"); 419 if (valued(pwr) || valued(pwrA)) { 420 boolean firstA = true; 421 td.tx(" for "); 422 if (valued(pwr)) { 423 for (BaseWrapper va : pwr.getValues()) { 424 if (firstA) firstA = false; else td.tx(", "); 425 render(td, va); 426 } 427 } 428 if (valued(pwrA)) { 429 if (firstA) firstA = false; else td.tx(", "); 430 td.tx("Age "); 431 render(td, pwrA.value()); 432 } 433 } 434 } 435 } 436 } 437 if (flags) { 438 // flags (status other than F, interpretation, ) 439 td = tr.td(); 440 boolean first = true; 441 pw = getProperty(obs, "status"); 442 if (valued(pw)) { 443 if (!pw.value().getBase().primitiveValue().equals("final")) { 444 if (first) first = false; else td.br(); 445 render(td, pw.value()); 446 } 447 } 448 pw = getProperty(obs, "interpretation"); 449 if (valued(pw)) { 450 for (BaseWrapper v : pw.getValues()) { 451 if (first) first = false; else td.br(); 452 render(td, v); 453 } 454 } 455 } 456 457 if (note) { 458 td = tr.td(); 459 pw = getProperty(obs, "note"); 460 if (valued(pw)) { 461 render(td, pw.value()); 462 } 463 } 464 if (effectiveTime) { 465 // effective if different to DR 466 td = tr.td(); 467 pw = getProperty(obs, "effective[x]"); 468 if (valued(pw)) { 469 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 470 render(td, pw.value()); 471 } 472 } 473 } 474 if (issued) { 475 // issued if different to DR 476 td = tr.td(); 477 pw = getProperty(obs, "issued"); 478 if (valued(pw)) { 479 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 480 render(td, pw.value()); 481 } 482 } 483 } 484 } 485}