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}