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