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("Subject");
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("When For");
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("Reported");
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("Performer", 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("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("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("Report Details");
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("Coded Conclusions :");
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("Unable to get Patient Details");
178    else if (r.getName().equals("Patient"))
179      generatePatientSummary(container, r);
180    else
181      container.tx("Not done yet");
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("Code");
222    tr.td().b().tx("Value");
223    if (refRange) {
224      tr.td().b().tx("Reference Range");
225    }
226    if (flags) {
227      tr.td().b().tx("Flags");
228    }
229    if (note) {
230      tr.td().b().tx("Note");
231    }
232    if (effectiveTime) {
233      tr.td().b().tx("When For");
234    }
235    if (issued) {
236      tr.td().b().tx("Reported");
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("This Observation could not be resolved");
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("Observation");
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("Error: ");
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(" 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("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}