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}