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