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.renderers.utils.RenderingContext; 012import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 013import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 014import org.hl7.fhir.r5.utils.EOperationOutcome; 015import org.hl7.fhir.utilities.Utilities; 016import org.hl7.fhir.utilities.xhtml.XhtmlNode; 017 018public class DiagnosticReportRenderer extends ResourceRenderer { 019 020 public class ObservationNode { 021 private String ref; 022 private ResourceWithReference resolution; 023 private List<ObservationNode> contained; 024 } 025 026 public DiagnosticReportRenderer(RenderingContext context) { 027 super(context); 028 } 029 030 @Override 031 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws IOException, FHIRException, EOperationOutcome { 032 renderDiagnosticReport(status, x, dr); 033 } 034 035 public void renderDiagnosticReport(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws IOException, FHIRException, EOperationOutcome { 036 renderResourceTechDetails(dr, x); 037 038 XhtmlNode h2 = x.h2(); 039 renderDataType(status, h2, dr.child("code")); 040 h2.tx(" "); 041 List<ResourceWrapper> cats = dr.children("category"); 042 if (!cats.isEmpty()) { 043 h2.tx("("); 044 boolean first = true; 045 for (ResourceWrapper b : cats) { 046 if (first) first = false; else h2.tx(", "); 047 renderDataType(status, h2, b); 048 } 049 h2.tx(") "); 050 } 051 XhtmlNode tbl = x.table("grid"); 052 XhtmlNode tr; 053 if (dr.has("subject")) { 054 tr = tbl.tr(); 055 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 056 populateSubjectSummary(status, tr.td(), dr.child("subject")); 057 } 058 059 ResourceWrapper eff = null; 060 ResourceWrapper iss = null; 061 if (dr.has("effective[x]")) { 062 tr = tbl.tr(); 063 tr.td().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_WHEN)); 064 eff = dr.child("effective[x]"); 065 renderDataType(status, tr.td(), eff); 066 } 067 if (dr.has("issued")) { 068 tr = tbl.tr(); 069 tr.td().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REP)); 070 iss = dr.child("issued"); 071 renderDataType(status, tr.td(), iss); 072 } 073 074 addTableRow(status, tbl, dr, RenderingContext.DIAG_REP_REND_PER, "performer"); 075 addTableRow(status, tbl, dr, RenderingContext.DIAG_REP_REND_IDENTIFIER, "identifier"); 076 addTableRow(status, tbl, dr, RenderingContext.GENERAL_REQUEST, "request"); 077 078 x.para().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REPDET)); 079 080 List<ResourceWrapper> items = dr.children("result"); 081 if (!items.isEmpty()) { 082 List<ObservationNode> observations = fetchObservations(items); 083 buildObservationsTable(status, x, observations, eff, iss); 084 } 085 086 if (dr.has("conclusion")) { 087 ResourceWrapper conc = dr.child("conclusion"); 088 if (conc.fhirType().equals("markdown")) { 089 renderDataType(status, x, conc); 090 } else { 091 renderDataType(status, x.para(), conc); 092 } 093 } 094 095 if (dr.hasMN("conclusionCode", "codedDiagnosis")) { 096 x.para().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_CODECON)); 097 addListRows(status, x.ul(), dr, RenderingContext.DIAG_REP_REND_CODECON, "conclusionCode", "codedDiagnosis"); 098 } 099 100 for (ResourceWrapper cont : dr.children("contained")) { 101 x.hr(); 102 RendererFactory.factory(cont, context.forContained()).buildNarrative(status, x, cont); 103 } 104 } 105 106 private void addTableRow(RenderingStatus status, XhtmlNode tbl, ResourceWrapper dr, String constName, String... names) throws FHIRFormatError, DefinitionException, IOException { 107 List<ResourceWrapper> items = dr.childrenMN(names); 108 if (!items.isEmpty()) { 109 XhtmlNode tr = tbl.tr(); 110 tr.td().tx(Utilities.pluralize(context.formatPhrase(constName), items.size())); 111 XhtmlNode tdr = tr.td(); 112 for (ResourceWrapper v : items) { 113 tdr.tx(" "); 114 renderDataType(status, tdr, v); 115 } 116 } 117 } 118 119 private void addListRows(RenderingStatus status, XhtmlNode ul, ResourceWrapper dr, String constName, String... names) throws FHIRFormatError, DefinitionException, IOException { 120 List<ResourceWrapper> items = dr.childrenMN(names); 121 if (!items.isEmpty()) { 122 for (ResourceWrapper v : items) { 123 XhtmlNode li = ul.li(); 124 renderDataType(status, li, v); 125 } 126 } 127 } 128 129 public void describeDiagnosticReport(XhtmlNode x, ResourceWrapper dr) { 130 x.tx(displayDiagnosticReport(dr)); 131 } 132 133 public String displayDiagnosticReport(ResourceWrapper dr) { 134 ResourceWrapper c = dr.child("code"); 135 String cd = c == null ? context.formatPhrase(RenderingContext.DIAG_REP_UNSPECIFIED_CODE) : displayCodeableConcept(c); 136 ResourceWrapper s = dr.child("subject"); 137 String sd = s == null ? context.formatPhrase(RenderingContext.DIAG_REP_UNSPECIFIED_SUBJECT) : displayReference(s); 138 return context.formatPhrase(RenderingContext.DIAG_REP_SUMMARY, cd, sd); 139 } 140 141 @Override 142 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 143 return displayDiagnosticReport(r); 144 } 145 146 147 private void populateSubjectSummary(RenderingStatus status, XhtmlNode container, ResourceWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome { 148 ResourceWithReference r = resolveReference(subject); 149 if (r == null) 150 container.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_UNABLE)); 151 else if (r.getResource().fhirType().equals("Patient")) 152 generatePatientSummary(status, container, r.getResource()); 153 else 154 container.tx(context.formatPhrase(RenderingContext.GENERAL_TODO)); 155 } 156 157 private void generatePatientSummary(RenderingStatus status, XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 158 new PatientRenderer(context).buildSummary(status, c, r); 159 } 160 161 private List<ObservationNode> fetchObservations(List<ResourceWrapper> list) throws UnsupportedEncodingException, FHIRException, IOException { 162 List<ObservationNode> res = new ArrayList<ObservationNode>(); 163 for (ResourceWrapper b : list) { 164 if (b.has("reference")) { 165 ObservationNode obs = new ObservationNode(); 166 obs.ref = b.primitiveValue("reference"); 167 obs.resolution = resolveReference(b.child("reference")); 168 if (obs.resolution != null && obs.resolution.getResource() != null) { 169 List<ResourceWrapper> t = obs.resolution.getResource().children("contained"); 170 if (!t.isEmpty()) { 171 obs.contained = fetchObservations(t); 172 } 173 } 174 res.add(obs); 175 } 176 } 177 return res; 178 } 179 180 private void buildObservationsTable(RenderingStatus status, XhtmlNode root, List<ObservationNode> observations, ResourceWrapper eff, ResourceWrapper iss) throws UnsupportedEncodingException, FHIRException, IOException { 181 XhtmlNode tbl = root.table("grid"); 182 boolean refRange = scanObsForRefRange(observations); 183 boolean flags = scanObsForFlags(observations); 184 boolean note = scanObsForNote(observations); 185 boolean effectiveTime = scanObsForEffective(observations, eff); 186 boolean issued = scanObsForIssued(observations, iss); 187 int cs = 2; 188 if (refRange) cs++; 189 if (flags) cs++; 190 if (note) cs++; 191 if (issued) cs++; 192 if (effectiveTime) cs++; 193 XhtmlNode tr = tbl.tr(); 194 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 195 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 196 if (refRange) { 197 tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REFRAN)); 198 } 199 if (flags) { 200 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_FLAGS)); 201 } 202 if (note) { 203 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NOTE)); 204 } 205 if (effectiveTime) { 206 tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_WHEN)); 207 } 208 if (issued) { 209 tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REP)); 210 } 211 for (ObservationNode o : observations) { 212 addObservationToTable(status, tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss); 213 } 214 } 215 216 private boolean scanObsForRefRange(List<ObservationNode> observations) { 217 for (ObservationNode o : observations) { 218 if (o.resolution != null) { 219 ResourceWrapper obs = o.resolution.getResource(); 220 if (obs != null && obs.has("referenceRange")) { 221 return true; 222 } 223 if (o.contained != null) { 224 if (scanObsForRefRange(o.contained)) { 225 return true; 226 } 227 } 228 } 229 } 230 return false; 231 } 232 233 private boolean scanObsForNote(List<ObservationNode> observations) { 234 for (ObservationNode o : observations) { 235 if (o.resolution != null) { 236 ResourceWrapper obs = o.resolution.getResource(); 237 if (obs != null && obs.has("note")) { 238 return true; 239 } 240 if (o.contained != null) { 241 if (scanObsForNote(o.contained)) { 242 return true; 243 } 244 } 245 } 246 } 247 return false; 248 } 249 250 private boolean scanObsForIssued(List<ObservationNode> observations, ResourceWrapper iss) throws UnsupportedEncodingException, FHIRException, IOException { 251 for (ObservationNode o : observations) { 252 if (o.resolution != null) { 253 ResourceWrapper obs = o.resolution.getResource(); 254 if (obs != null && obs.has("issued") && (iss == null || !iss.matches(obs.child("issued")))) { 255 return true; 256 } 257 if (o.contained != null) { 258 if (scanObsForIssued(o.contained, iss)) { 259 return true; 260 } 261 } 262 } 263 } 264 return false; 265 } 266 267 private boolean scanObsForEffective(List<ObservationNode> observations, ResourceWrapper eff) throws UnsupportedEncodingException, FHIRException, IOException { 268 for (ObservationNode o : observations) { 269 if (o.resolution != null) { 270 ResourceWrapper obs = o.resolution.getResource(); 271 if (obs != null && obs.has("effective[x]") && (eff == null || !eff.matches(obs.child("effective[x]")))) { 272 return true; 273 } 274 if (o.contained != null) { 275 if (scanObsForEffective(o.contained, eff)) { 276 return true; 277 } 278 } 279 } 280 } 281 return false; 282 } 283 284 private boolean scanObsForFlags(List<ObservationNode> observations) throws UnsupportedEncodingException, FHIRException, IOException { 285 for (ObservationNode o : observations) { 286 if (o.resolution != null) { 287 ResourceWrapper obs = o.resolution.getResource(); 288 if (obs != null && (obs.has("interpretation") || obs.has("status"))) { 289 return true; 290 } 291 if (o.contained != null) { 292 if (scanObsForFlags(o.contained)) { 293 return true; 294 } 295 } 296 } 297 } 298 return false; 299 } 300 301 private void addObservationToTable(RenderingStatus status, XhtmlNode tbl, ObservationNode o, int i, String cs, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, ResourceWrapper eff, ResourceWrapper iss) throws UnsupportedEncodingException, FHIRException, IOException { 302 XhtmlNode tr = tbl.tr(); 303 if (o.resolution == null) { 304 XhtmlNode td = tr.td().colspan(cs); 305 td.i().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_NOTRES, o.ref)); 306 } else { 307 if (o.resolution.getResource() != null) { 308 addObservationToTable(status, tr, o.resolution.getResource(), i, o.resolution.getWebPath(), refRange, flags, note, effectiveTime, issued, eff, iss); 309 } else { 310 XhtmlNode td = tr.td().colspan(cs); 311 td.i().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_OBS)); 312 } 313 if (o.contained != null) { 314 for (ObservationNode c : o.contained) { 315 addObservationToTable(status, tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss); 316 } 317 } 318 } 319 } 320 321 private void addObservationToTable(RenderingStatus status, XhtmlNode tr, ResourceWrapper obs, int i, String ref, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, ResourceWrapper eff, ResourceWrapper iss) throws UnsupportedEncodingException, FHIRException, IOException { 322 323 // code (+bodysite) 324 XhtmlNode td = tr.td(); 325 if (obs.has("code")) { 326 renderDataType(status, td.ah(context.prefixLocalHref(ref)), obs.child("code")); 327 } 328 if (obs.has("bodySite")) { 329 td.tx(" ("); 330 renderDataType(status, td, obs.child("bodySite")); 331 td.tx(")"); 332 } 333 334 // value / dataAbsentReason (in red) 335 td = tr.td(); 336 if (obs.has("value[x]")) { 337 renderDataType(status, td, obs.child("value[x]")); 338 } else if (obs.has("dataAbsentReason")) { 339 XhtmlNode span = td.span("color: maroon", "Error"); 340 span.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_ERR) + " "); 341 renderDataType(status, span.b(), obs.child("dataAbsentReason")); 342 } 343 344 if (refRange) { 345 // reference range 346 td = tr.td(); 347 List<ResourceWrapper> items = obs.children("referenceRange"); 348 if (!items.isEmpty()) { 349 boolean first = true; 350 for (ResourceWrapper v : items) { 351 if (first) first = false; else td.br(); 352 ResourceWrapper pwr = v.child("type"); 353 if (pwr != null) { 354 renderDataType(status, td, pwr); 355 td.tx(": "); 356 } 357 ResourceWrapper pwt = v.child("text"); 358 if (pwt != null) { 359 renderDataType(status, td, pwt); 360 } else { 361 ResourceWrapper pwl = v.child("low"); 362 ResourceWrapper pwh = v.child("high"); 363 if (pwl != null && pwh != null) { 364 renderDataType(status, td, pwl); 365 td.tx(" - "); 366 renderDataType(status, td, pwh); 367 } else if (pwl != null) { 368 td.tx(">"); 369 renderDataType(status, td, pwl); 370 } else if (pwh != null) { 371 td.tx("<"); 372 renderDataType(status, td, pwh); 373 } else { 374 td.tx("??"); 375 } 376 } 377 List<ResourceWrapper> pwrF = v.children("appliesTo"); 378 ResourceWrapper pwrA = v.child("age"); 379 if (!pwrF.isEmpty() || pwrA != null) { 380 boolean firstA = true; 381 td.tx(" "+ (context.formatPhrase(RenderingContext.DIAG_REP_REND_FOR)) + " "); 382 if (!pwrF.isEmpty()) { 383 for (ResourceWrapper va : pwrF) { 384 if (firstA) firstA = false; else td.tx(", "); 385 renderDataType(status, td, va); 386 } 387 } 388 if (pwrA != null) { 389 if (firstA) firstA = false; else td.tx(", "); 390 td.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_AGE) + " "); 391 renderDataType(status, td, pwrA); 392 } 393 } 394 } 395 } 396 } 397 398 addCellToTable(flags, status, tr, obs, null, "status", "interpretation"); 399 addCellToTable(note, status, tr, obs, null, "note"); 400 addCellToTable(effectiveTime, status, tr, obs, eff, "effective[x]"); 401 addCellToTable(issued, status, tr, obs, iss, "issued"); 402 403 } 404 405 private void addCellToTable(boolean included, RenderingStatus status, XhtmlNode tr, ResourceWrapper obs, ResourceWrapper diff, String... names) throws FHIRFormatError, DefinitionException, IOException { 406 if (included) { 407 XhtmlNode td = tr.td(); 408 List<ResourceWrapper> list = obs.childrenMN(names); 409 if (!list.isEmpty()) { 410 boolean first = true; 411 for (ResourceWrapper b : list) { 412 if (diff == null || !diff.matches(b)) { 413 if (first) first = false; else td.tx(", "); 414 renderDataType(status, td, b); 415 } 416 } 417 } 418 } 419 } 420 421}