
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 render(x.para(), pw.value()); 129 } 130 131 pw = getProperty(dr, "conclusionCode"); 132 if (!valued(pw)) { 133 pw = getProperty(dr, "codedDiagnosis"); 134 } 135 if (valued(pw)) { 136 XhtmlNode p = x.para(); 137 p.b().tx("Coded Conclusions :"); 138 XhtmlNode ul = x.ul(); 139 for (BaseWrapper v : pw.getValues()) { 140 render(ul.li(), v); 141 } 142 } 143 return false; 144 } 145 146 public boolean render(XhtmlNode x, DiagnosticReport dr) throws IOException, FHIRException, EOperationOutcome { 147 render(x, new DirectWrappers.ResourceWrapperDirect(this.context, dr)); 148 149 return true; 150 } 151 152 public void describe(XhtmlNode x, DiagnosticReport dr) { 153 x.tx(display(dr)); 154 } 155 156 public String display(DiagnosticReport dr) { 157 return display(dr.getCode()); 158 } 159 160 @Override 161 public String display(Resource r) throws UnsupportedEncodingException, IOException { 162 return display((DiagnosticReport) r); 163 } 164 165 @Override 166 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 167 return "Not done yet"; 168 } 169 170 private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome { 171 ResourceWrapper r = fetchResource(subject); 172 if (r == null) 173 container.tx("Unable to get Patient Details"); 174 else if (r.getName().equals("Patient")) 175 generatePatientSummary(container, r); 176 else 177 container.tx("Not done yet"); 178 } 179 180 private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 181 new PatientRenderer(context).describe(c, r); 182 } 183 184 private List<ObservationNode> fetchObservations(List<BaseWrapper> list, ResourceWrapper rw) throws UnsupportedEncodingException, FHIRException, IOException { 185 List<ObservationNode> res = new ArrayList<ObservationNode>(); 186 for (BaseWrapper b : list) { 187 if (b.has("reference")) { 188 ObservationNode obs = new ObservationNode(); 189 obs.ref = b.get("reference").primitiveValue(); 190 obs.obs = resolveReference(rw, obs.ref); 191 if (obs.obs != null && obs.obs.getResource() != null) { 192 PropertyWrapper t = getProperty(obs.obs.getResource(), "contained"); 193 if (t != null && t.hasValues()) { 194 obs.contained = fetchObservations(t.getValues(), rw); 195 } 196 } 197 res.add(obs); 198 } 199 } 200 return res; 201 } 202 203 private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 204 XhtmlNode tbl = root.table("grid"); 205 boolean refRange = scanObsForRefRange(observations); 206 boolean flags = scanObsForFlags(observations); 207 boolean note = scanObsForNote(observations); 208 boolean effectiveTime = scanObsForEffective(observations, eff); 209 boolean issued = scanObsForIssued(observations, iss); 210 int cs = 2; 211 if (refRange) cs++; 212 if (flags) cs++; 213 if (note) cs++; 214 if (issued) cs++; 215 if (effectiveTime) cs++; 216 XhtmlNode tr = tbl.tr(); 217 tr.td().b().tx("Code"); 218 tr.td().b().tx("Value"); 219 if (refRange) { 220 tr.td().b().tx("Reference Range"); 221 } 222 if (flags) { 223 tr.td().b().tx("Flags"); 224 } 225 if (note) { 226 tr.td().b().tx("Note"); 227 } 228 if (effectiveTime) { 229 tr.td().b().tx("When For"); 230 } 231 if (issued) { 232 tr.td().b().tx("Reported"); 233 } 234 for (ObservationNode o : observations) { 235 addObservationToTable(tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss); 236 } 237 } 238 239 private boolean scanObsForRefRange(List<ObservationNode> observations) { 240 for (ObservationNode o : observations) { 241 if (o.obs != null && o.obs.getResource() != null) { 242 PropertyWrapper pw = getProperty(o.obs.getResource(), "referenceRange"); 243 if (valued(pw)) { 244 return true; 245 } 246 } 247 if (o.contained != null) { 248 if (scanObsForRefRange(o.contained)) { 249 return true; 250 } 251 } 252 } 253 return false; 254 } 255 256 private boolean scanObsForNote(List<ObservationNode> observations) { 257 for (ObservationNode o : observations) { 258 if (o.obs != null && o.obs.getResource() != null) { 259 PropertyWrapper pw = getProperty(o.obs.getResource(), "note"); 260 if (valued(pw)) { 261 return true; 262 } 263 } 264 if (o.contained != null) { 265 if (scanObsForNote(o.contained)) { 266 return true; 267 } 268 } 269 } 270 return false; 271 } 272 273 private boolean scanObsForIssued(List<ObservationNode> observations, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 274 for (ObservationNode o : observations) { 275 if (o.obs != null && o.obs.getResource() != null) { 276 PropertyWrapper pw = getProperty(o.obs.getResource(), "issued"); 277 if (valued(pw)) { 278 if (!Base.compareDeep(pw.value().getBase(), iss, true)) { 279 return true; 280 } 281 } 282 } 283 if (o.contained != null) { 284 if (scanObsForIssued(o.contained, iss)) { 285 return true; 286 } 287 } 288 } 289 return false; 290 } 291 292 private boolean scanObsForEffective(List<ObservationNode> observations, DataType eff) throws UnsupportedEncodingException, FHIRException, IOException { 293 for (ObservationNode o : observations) { 294 if (o.obs != null && o.obs.getResource() != null) { 295 PropertyWrapper pw = getProperty(o.obs.getResource(), "effective[x]"); 296 if (valued(pw)) { 297 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 298 return true; 299 } 300 } 301 } 302 if (o.contained != null) { 303 if (scanObsForEffective(o.contained, eff)) { 304 return true; 305 } 306 } 307 } 308 return false; 309 } 310 311 private boolean scanObsForFlags(List<ObservationNode> observations) throws UnsupportedEncodingException, FHIRException, IOException { 312 for (ObservationNode o : observations) { 313 if (o.obs != null && o.obs.getResource() != null) { 314 PropertyWrapper pw = getProperty(o.obs.getResource(), "interpretation"); 315 if (valued(pw)) { 316 return true; 317 } 318 pw = getProperty(o.obs.getResource(), "status"); 319 if (valued(pw)) { 320 if (!pw.value().getBase().primitiveValue().equals("final")) { 321 return true; 322 } 323 } 324 325 } 326 if (o.contained != null) { 327 if (scanObsForFlags(o.contained)) { 328 return true; 329 } 330 } 331 } 332 return false; 333 } 334 335 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 { 336 XhtmlNode tr = tbl.tr(); 337 if (o.obs != null && o.obs.getReference() == null) { 338 XhtmlNode td = tr.td().colspan(cs); 339 td.i().tx("This Observation could not be resolved"); 340 } else { 341 if (o.obs != null && o.obs.getResource() != null) { 342 addObservationToTable(tr, o.obs.getResource(), i, o.obs.getReference(), refRange, flags, note, effectiveTime, issued, eff, iss); 343 } else { 344 XhtmlNode td = tr.td().colspan(cs); 345 td.i().tx("Observation"); 346 } 347 if (o.contained != null) { 348 for (ObservationNode c : o.contained) { 349 addObservationToTable(tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss); 350 } 351 } 352 } 353 } 354 355 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 { 356 357 // code (+bodysite) 358 XhtmlNode td = tr.td(); 359 PropertyWrapper pw = getProperty(obs, "code"); 360 if (valued(pw)) { 361 render(td.ah(ref), pw.value()); 362 } 363 pw = getProperty(obs, "bodySite"); 364 if (valued(pw)) { 365 td.tx(" ("); 366 render(td, pw.value()); 367 td.tx(")"); 368 } 369 370 // value / dataAbsentReason (in red) 371 td = tr.td(); 372 pw = getProperty(obs, "value[x]"); 373 if (valued(pw)) { 374 render(td, pw.value()); 375 } else { 376 pw = getProperty(obs, "dataAbsentReason"); 377 if (valued(pw)) { 378 XhtmlNode span = td.span("color: maroon", "Error"); 379 span.tx("Error: "); 380 render(span.b(), pw.value()); 381 } 382 } 383 if (refRange) { 384 // reference range 385 td = tr.td(); 386 pw = getProperty(obs, "referenceRange"); 387 if (valued(pw)) { 388 boolean first = true; 389 for (BaseWrapper v : pw.getValues()) { 390 if (first) first = false; else td.br(); 391 PropertyWrapper pwr = getProperty(v, "type"); 392 if (valued(pwr)) { 393 render(td, pwr.value()); 394 td.tx(": "); 395 } 396 PropertyWrapper pwt = getProperty(v, "text"); 397 if (valued(pwt)) { 398 render(td, pwt.value()); 399 } else { 400 PropertyWrapper pwl = getProperty(v, "low"); 401 PropertyWrapper pwh = getProperty(v, "high"); 402 if (valued(pwl) && valued(pwh)) { 403 render(td, pwl.value()); 404 td.tx(" - "); 405 render(td, pwh.value()); 406 } else if (valued(pwl)) { 407 td.tx(">"); 408 render(td, pwl.value()); 409 } else if (valued(pwh)) { 410 td.tx("<"); 411 render(td, pwh.value()); 412 } else { 413 td.tx("??"); 414 } 415 } 416 pwr = getProperty(v, "appliesTo"); 417 PropertyWrapper pwrA = getProperty(v, "age"); 418 if (valued(pwr) || valued(pwrA)) { 419 boolean firstA = true; 420 td.tx(" for "); 421 if (valued(pwr)) { 422 for (BaseWrapper va : pwr.getValues()) { 423 if (firstA) firstA = false; else td.tx(", "); 424 render(td, va); 425 } 426 } 427 if (valued(pwrA)) { 428 if (firstA) firstA = false; else td.tx(", "); 429 td.tx("Age "); 430 render(td, pwrA.value()); 431 } 432 } 433 } 434 } 435 } 436 if (flags) { 437 // flags (status other than F, interpretation, ) 438 td = tr.td(); 439 boolean first = true; 440 pw = getProperty(obs, "status"); 441 if (valued(pw)) { 442 if (!pw.value().getBase().primitiveValue().equals("final")) { 443 if (first) first = false; else td.br(); 444 render(td, pw.value()); 445 } 446 } 447 pw = getProperty(obs, "interpretation"); 448 if (valued(pw)) { 449 for (BaseWrapper v : pw.getValues()) { 450 if (first) first = false; else td.br(); 451 render(td, v); 452 } 453 } 454 } 455 456 if (note) { 457 td = tr.td(); 458 pw = getProperty(obs, "note"); 459 if (valued(pw)) { 460 render(td, pw.value()); 461 } 462 } 463 if (effectiveTime) { 464 // effective if different to DR 465 td = tr.td(); 466 pw = getProperty(obs, "effective[x]"); 467 if (valued(pw)) { 468 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 469 render(td, pw.value()); 470 } 471 } 472 } 473 if (issued) { 474 // issued if different to DR 475 td = tr.td(); 476 pw = getProperty(obs, "issued"); 477 if (valued(pw)) { 478 if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 479 render(td, pw.value()); 480 } 481 } 482 } 483 } 484}