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(container, r.getResource()); 
153    else 
154      container.tx(context.formatPhrase(RenderingContext.GENERAL_TODO)); 
155  } 
156
157  private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 
158    new PatientRenderer(context).describe(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}