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