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