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.model.Base; 
012import org.hl7.fhir.r5.model.DataType; 
013import org.hl7.fhir.r5.model.DiagnosticReport; 
014import org.hl7.fhir.r5.model.Resource; 
015import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 
016import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 
017import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 
018import org.hl7.fhir.r5.renderers.utils.DirectWrappers; 
019import org.hl7.fhir.r5.renderers.utils.RenderingContext; 
020import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 
021import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 
022import org.hl7.fhir.r5.utils.EOperationOutcome; 
023import org.hl7.fhir.utilities.Utilities; 
024import org.hl7.fhir.utilities.xhtml.XhtmlNode; 
025 
026public class DiagnosticReportRenderer extends ResourceRenderer { 
027 
028  public class ObservationNode { 
029    private String ref; 
030    private ResourceWithReference obs; 
031    private List<ObservationNode> contained; 
032  } 
033 
034 
035  public DiagnosticReportRenderer(RenderingContext context) { 
036    super(context); 
037  } 
038 
039  public DiagnosticReportRenderer(RenderingContext context, ResourceContext rcontext) { 
040    super(context, rcontext); 
041  } 
042   
043  public boolean render(XhtmlNode x, Resource dr) throws IOException, FHIRException, EOperationOutcome { 
044    return render(x, (DiagnosticReport) dr); 
045  } 
046 
047  public boolean render(XhtmlNode x, ResourceWrapper dr) throws IOException, FHIRException, EOperationOutcome { 
048    XhtmlNode h2 = x.h2(); 
049    render(h2, getProperty(dr, "code").value()); 
050    h2.tx(" "); 
051    PropertyWrapper pw = getProperty(dr, "category"); 
052    if (valued(pw)) { 
053      h2.tx("("); 
054      boolean first = true; 
055      for (BaseWrapper b : pw.getValues()) { 
056        if (first) first = false; else h2.tx(", "); 
057        render(h2, b); 
058      } 
059      h2.tx(") "); 
060    } 
061    XhtmlNode tbl = x.table("grid"); 
062    XhtmlNode tr; 
063    if (dr.has("subject")) { 
064       tr = tbl.tr(); 
065       tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 
066       populateSubjectSummary(tr.td(), getProperty(dr, "subject").value()); 
067    } 
068     
069    DataType eff = null; 
070    DataType iss = null; 
071     
072    if (dr.has("effective[x]")) { 
073      tr = tbl.tr(); 
074      tr.td().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_WHEN)); 
075      eff = (DataType) getProperty(dr, "effective[x]").value().getBase(); 
076      render(tr.td(), eff); 
077    } 
078    if (dr.has("issued")) { 
079      tr = tbl.tr(); 
080      tr.td().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REP)); 
081      eff = (DataType) getProperty(dr, "issued").value().getBase(); 
082      render(tr.td(), getProperty(dr, "issued").value()); 
083    } 
084 
085    pw = getProperty(dr, "perfomer"); 
086    if (valued(pw)) { 
087      tr = tbl.tr(); 
088      tr.td().tx(Utilities.pluralize((context.formatPhrase(RenderingContext.DIAG_REP_REND_PER)), pw.getValues().size())); 
089      XhtmlNode tdr = tr.td(); 
090      for (BaseWrapper v : pw.getValues()) { 
091        tdr.tx(" "); 
092        render(tdr, v); 
093      } 
094    } 
095    pw = getProperty(dr, "identifier"); 
096    if (valued(pw)) { 
097      tr = tbl.tr(); 
098      tr.td().tx(Utilities.pluralize((context.formatPhrase(RenderingContext.DIAG_REP_REND_IDENTIFIER)), pw.getValues().size())+":"); 
099      XhtmlNode tdr = tr.td(); 
100      for (BaseWrapper v : pw.getValues()) { 
101        tdr.tx(" "); 
102        render(tdr, v); 
103      } 
104    } 
105    pw = getProperty(dr, "request"); 
106    if (valued(pw)) { 
107      tr = tbl.tr(); 
108      tr.td().tx(Utilities.pluralize((context.formatPhrase(RenderingContext.GENERAL_REQUEST)), pw.getValues().size())+":"); 
109      XhtmlNode tdr = tr.td(); 
110      for (BaseWrapper v : pw.getValues()) { 
111        tdr.tx(" "); 
112        render(tdr, v); 
113      } 
114      tdr.br(); 
115    } 
116 
117     
118    x.para().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REPDET)); 
119 
120    pw = getProperty(dr, "result"); 
121    if (valued(pw)) { 
122      List<ObservationNode> observations = fetchObservations(pw.getValues(), dr); 
123      buildObservationsTable(x, observations, eff, iss); 
124    } 
125 
126    pw = getProperty(dr, "conclusion"); 
127    if (valued(pw)) { 
128      if (pw.fhirType().equals("markdown")) {         
129        render(x, pw.value());         
130      } else { 
131        render(x.para(), pw.value()); 
132      } 
133    } 
134 
135    pw = getProperty(dr, "conclusionCode"); 
136    if (!valued(pw)) { 
137      pw = getProperty(dr, "codedDiagnosis");     
138    } 
139    if (valued(pw)) { 
140      XhtmlNode p = x.para(); 
141      p.b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_CODECON)); 
142      XhtmlNode ul = x.ul(); 
143      for (BaseWrapper v : pw.getValues()) { 
144        render(ul.li(), v); 
145      } 
146    } 
147    return false; 
148  } 
149 
150  public boolean render(XhtmlNode x, DiagnosticReport dr) throws IOException, FHIRException, EOperationOutcome { 
151    render(x, new DirectWrappers.ResourceWrapperDirect(this.context, dr)); 
152 
153    return true; 
154  } 
155 
156  public void describe(XhtmlNode x, DiagnosticReport dr) { 
157    x.tx(display(dr)); 
158  } 
159 
160  public String display(DiagnosticReport dr) { 
161    return display(dr.getCode()); 
162  } 
163 
164  @Override 
165  public String display(Resource r) throws UnsupportedEncodingException, IOException { 
166    return display((DiagnosticReport) r); 
167  } 
168 
169  @Override 
170  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 
171    return "Not done yet"; 
172  } 
173 
174  private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome { 
175    ResourceWrapper r = fetchResource(subject); 
176    if (r == null) 
177      container.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_UNABLE)); 
178    else if (r.getName().equals("Patient")) 
179      generatePatientSummary(container, r); 
180    else 
181      container.tx(context.formatPhrase(RenderingContext.GENERAL_TODO)); 
182  } 
183 
184  private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 
185    new PatientRenderer(context).describe(c, r); 
186  } 
187   
188  private List<ObservationNode> fetchObservations(List<BaseWrapper> list, ResourceWrapper rw) throws UnsupportedEncodingException, FHIRException, IOException { 
189    List<ObservationNode> res = new ArrayList<ObservationNode>(); 
190    for (BaseWrapper b : list) { 
191      if (b.has("reference")) { 
192        ObservationNode obs = new ObservationNode(); 
193        obs.ref = b.get("reference").primitiveValue(); 
194        obs.obs = resolveReference(rw, obs.ref); 
195        if (obs.obs != null && obs.obs.getResource() != null) { 
196          PropertyWrapper t = getProperty(obs.obs.getResource(), "contained"); 
197          if (t != null && t.hasValues()) { 
198            obs.contained = fetchObservations(t.getValues(), rw); 
199          } 
200        } 
201        res.add(obs); 
202      } 
203    } 
204    return res; 
205  } 
206 
207  private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 
208    XhtmlNode tbl = root.table("grid"); 
209    boolean refRange = scanObsForRefRange(observations); 
210    boolean flags = scanObsForFlags(observations);  
211    boolean note = scanObsForNote(observations); 
212    boolean effectiveTime = scanObsForEffective(observations, eff); 
213    boolean issued = scanObsForIssued(observations, iss); 
214    int cs = 2; 
215    if (refRange) cs++; 
216    if (flags) cs++; 
217    if (note) cs++; 
218    if (issued) cs++; 
219    if (effectiveTime) cs++; 
220    XhtmlNode tr = tbl.tr(); 
221    tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
222    tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 
223    if (refRange) { 
224      tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REFRAN)); 
225    } 
226    if (flags) { 
227      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_FLAGS)); 
228    } 
229    if (note) { 
230      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NOTE)); 
231    } 
232    if (effectiveTime) { 
233      tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_WHEN)); 
234    } 
235    if (issued) { 
236      tr.td().b().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_REP)); 
237    } 
238    for (ObservationNode o : observations) { 
239      addObservationToTable(tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss); 
240    } 
241  } 
242 
243  private boolean scanObsForRefRange(List<ObservationNode> observations) { 
244    for (ObservationNode o : observations) { 
245      if (o.obs != null && o.obs.getResource() != null) { 
246        PropertyWrapper pw = getProperty(o.obs.getResource(), "referenceRange"); 
247        if (valued(pw)) { 
248          return true; 
249        }         
250      } 
251      if (o.contained != null) { 
252        if (scanObsForRefRange(o.contained)) { 
253          return true; 
254        } 
255      } 
256    }       
257    return false; 
258  } 
259 
260  private boolean scanObsForNote(List<ObservationNode> observations) { 
261    for (ObservationNode o : observations) { 
262      if (o.obs != null && o.obs.getResource() != null) { 
263        PropertyWrapper pw = getProperty(o.obs.getResource(), "note"); 
264        if (valued(pw)) { 
265          return true; 
266        }         
267      } 
268      if (o.contained != null) { 
269        if (scanObsForNote(o.contained)) { 
270          return true; 
271        } 
272      } 
273    }       
274    return false; 
275  } 
276 
277  private boolean scanObsForIssued(List<ObservationNode> observations, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 
278    for (ObservationNode o : observations) { 
279      if (o.obs != null && o.obs.getResource() != null) { 
280        PropertyWrapper pw = getProperty(o.obs.getResource(), "issued"); 
281        if (valued(pw)) { 
282          if (!Base.compareDeep(pw.value().getBase(), iss, true)) { 
283            return true; 
284          } 
285        }         
286      } 
287      if (o.contained != null) { 
288        if (scanObsForIssued(o.contained, iss)) { 
289          return true; 
290        } 
291      } 
292    }       
293    return false; 
294  } 
295 
296  private boolean scanObsForEffective(List<ObservationNode> observations, DataType eff) throws UnsupportedEncodingException, FHIRException, IOException { 
297    for (ObservationNode o : observations) { 
298      if (o.obs != null && o.obs.getResource() != null) { 
299        PropertyWrapper pw = getProperty(o.obs.getResource(), "effective[x]"); 
300        if (valued(pw)) { 
301          if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 
302            return true; 
303          } 
304        }         
305      } 
306      if (o.contained != null) { 
307        if (scanObsForEffective(o.contained, eff)) { 
308          return true; 
309        } 
310      } 
311    }       
312    return false; 
313  } 
314 
315  private boolean scanObsForFlags(List<ObservationNode> observations) throws UnsupportedEncodingException, FHIRException, IOException { 
316    for (ObservationNode o : observations) { 
317      if (o.obs != null && o.obs.getResource() != null) { 
318        PropertyWrapper pw = getProperty(o.obs.getResource(), "interpretation"); 
319        if (valued(pw)) { 
320          return true; 
321        } 
322        pw = getProperty(o.obs.getResource(), "status"); 
323        if (valued(pw)) { 
324          if (!pw.value().getBase().primitiveValue().equals("final")) { 
325            return true; 
326          } 
327        } 
328         
329      } 
330      if (o.contained != null) { 
331        if (scanObsForFlags(o.contained)) { 
332          return true; 
333        } 
334      } 
335    }       
336    return false; 
337  } 
338 
339  private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i, String cs, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 
340    XhtmlNode tr = tbl.tr(); 
341    if (o.obs != null && o.obs.getReference()  == null) { 
342      XhtmlNode td = tr.td().colspan(cs); 
343      td.i().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_NOTRES)); 
344    } else { 
345      if (o.obs != null && o.obs.getResource() != null) { 
346        addObservationToTable(tr, o.obs.getResource(), i, o.obs.getReference(), refRange, flags, note, effectiveTime, issued, eff, iss); 
347      } else { 
348        XhtmlNode td = tr.td().colspan(cs); 
349        td.i().tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_OBS)); 
350      } 
351      if (o.contained != null) { 
352        for (ObservationNode c : o.contained) { 
353          addObservationToTable(tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss); 
354        } 
355      } 
356    }  
357  } 
358 
359  private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i, String ref, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { 
360 
361    // code (+bodysite) 
362    XhtmlNode td = tr.td(); 
363    PropertyWrapper pw = getProperty(obs, "code"); 
364    if (valued(pw)) { 
365      render(td.ah(ref), pw.value()); 
366    } 
367    pw = getProperty(obs, "bodySite"); 
368    if (valued(pw)) { 
369      td.tx(" ("); 
370      render(td, pw.value()); 
371      td.tx(")"); 
372    } 
373 
374    // value / dataAbsentReason (in red) 
375    td = tr.td(); 
376    pw = getProperty(obs, "value[x]"); 
377    if (valued(pw)) { 
378      render(td, pw.value()); 
379    } else { 
380      pw = getProperty(obs, "dataAbsentReason"); 
381      if (valued(pw)) { 
382        XhtmlNode span = td.span("color: maroon", "Error"); 
383        span.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_ERR) + " "); 
384        render(span.b(), pw.value()); 
385      } 
386    } 
387    if (refRange) { 
388      // reference range 
389      td = tr.td(); 
390      pw = getProperty(obs, "referenceRange"); 
391      if (valued(pw)) { 
392        boolean first = true; 
393        for (BaseWrapper v : pw.getValues()) { 
394          if (first) first = false; else td.br(); 
395          PropertyWrapper pwr = getProperty(v, "type");  
396          if (valued(pwr)) { 
397            render(td, pwr.value()); 
398            td.tx(": "); 
399          } 
400          PropertyWrapper pwt = getProperty(v, "text");  
401          if (valued(pwt)) { 
402            render(td, pwt.value()); 
403          } else { 
404            PropertyWrapper pwl = getProperty(v, "low");  
405            PropertyWrapper pwh = getProperty(v, "high");  
406            if (valued(pwl) && valued(pwh)) { 
407              render(td, pwl.value()); 
408              td.tx(" - "); 
409              render(td, pwh.value()); 
410            } else if (valued(pwl)) { 
411              td.tx(">"); 
412              render(td, pwl.value()); 
413            } else if (valued(pwh)) { 
414              td.tx("<"); 
415              render(td, pwh.value()); 
416            } else { 
417              td.tx("??"); 
418            } 
419          } 
420          pwr = getProperty(v, "appliesTo");  
421          PropertyWrapper pwrA = getProperty(v, "age");  
422          if (valued(pwr) || valued(pwrA)) { 
423            boolean firstA = true; 
424            td.tx(" "+ (context.formatPhrase(RenderingContext.DIAG_REP_REND_FOR)) + " "); 
425            if (valued(pwr)) { 
426              for (BaseWrapper va : pwr.getValues()) { 
427                if (firstA) firstA = false; else td.tx(", "); 
428                render(td, va); 
429              } 
430            } 
431            if (valued(pwrA)) { 
432              if (firstA) firstA = false; else td.tx(", "); 
433              td.tx(context.formatPhrase(RenderingContext.DIAG_REP_REND_AGE) + " "); 
434              render(td, pwrA.value()); 
435            } 
436          } 
437        }         
438      }       
439    } 
440    if (flags) { 
441      // flags (status other than F, interpretation, ) 
442      td = tr.td(); 
443      boolean first = true; 
444      pw = getProperty(obs, "status"); 
445      if (valued(pw)) { 
446        if (!pw.value().getBase().primitiveValue().equals("final")) { 
447          if (first) first = false; else td.br(); 
448          render(td, pw.value()); 
449        } 
450      } 
451      pw = getProperty(obs, "interpretation"); 
452      if (valued(pw)) { 
453        for (BaseWrapper v : pw.getValues()) { 
454          if (first) first = false; else td.br(); 
455          render(td, v); 
456        } 
457      } 
458    } 
459 
460    if (note) { 
461      td = tr.td(); 
462      pw = getProperty(obs, "note"); 
463      if (valued(pw)) { 
464        render(td, pw.value()); 
465      } 
466    } 
467    if (effectiveTime) { 
468      // effective if different to DR 
469      td = tr.td(); 
470      pw = getProperty(obs, "effective[x]"); 
471      if (valued(pw)) { 
472        if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 
473          render(td, pw.value()); 
474        } 
475      } 
476    } 
477    if (issued) { 
478      // issued if different to DR 
479      td = tr.td(); 
480      pw = getProperty(obs, "issued"); 
481      if (valued(pw)) { 
482        if (!Base.compareDeep(pw.value().getBase(), eff, true)) { 
483          render(td, pw.value()); 
484        } 
485      } 
486    } 
487  } 
488}