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      render(x.para(), pw.value());
129    }
130
131    pw = getProperty(dr, "conclusionCode");
132    if (!valued(pw)) {
133      pw = getProperty(dr, "codedDiagnosis");    
134    }
135    if (valued(pw)) {
136      XhtmlNode p = x.para();
137      p.b().tx("Coded Conclusions :");
138      XhtmlNode ul = x.ul();
139      for (BaseWrapper v : pw.getValues()) {
140        render(ul.li(), v);
141      }
142    }
143    return false;
144  }
145
146  public boolean render(XhtmlNode x, DiagnosticReport dr) throws IOException, FHIRException, EOperationOutcome {
147    render(x, new DirectWrappers.ResourceWrapperDirect(this.context, dr));
148
149    return true;
150  }
151
152  public void describe(XhtmlNode x, DiagnosticReport dr) {
153    x.tx(display(dr));
154  }
155
156  public String display(DiagnosticReport dr) {
157    return display(dr.getCode());
158  }
159
160  @Override
161  public String display(Resource r) throws UnsupportedEncodingException, IOException {
162    return display((DiagnosticReport) r);
163  }
164
165  @Override
166  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
167    return "Not done yet";
168  }
169
170  private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome {
171    ResourceWrapper r = fetchResource(subject);
172    if (r == null)
173      container.tx("Unable to get Patient Details");
174    else if (r.getName().equals("Patient"))
175      generatePatientSummary(container, r);
176    else
177      container.tx("Not done yet");
178  }
179
180  private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
181    new PatientRenderer(context).describe(c, r);
182  }
183  
184  private List<ObservationNode> fetchObservations(List<BaseWrapper> list, ResourceWrapper rw) throws UnsupportedEncodingException, FHIRException, IOException {
185    List<ObservationNode> res = new ArrayList<ObservationNode>();
186    for (BaseWrapper b : list) {
187      if (b.has("reference")) {
188        ObservationNode obs = new ObservationNode();
189        obs.ref = b.get("reference").primitiveValue();
190        obs.obs = resolveReference(rw, obs.ref);
191        if (obs.obs != null && obs.obs.getResource() != null) {
192          PropertyWrapper t = getProperty(obs.obs.getResource(), "contained");
193          if (t != null && t.hasValues()) {
194            obs.contained = fetchObservations(t.getValues(), rw);
195          }
196        }
197        res.add(obs);
198      }
199    }
200    return res;
201  }
202
203  private void buildObservationsTable(XhtmlNode root, List<ObservationNode> observations, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException {
204    XhtmlNode tbl = root.table("grid");
205    boolean refRange = scanObsForRefRange(observations);
206    boolean flags = scanObsForFlags(observations); 
207    boolean note = scanObsForNote(observations);
208    boolean effectiveTime = scanObsForEffective(observations, eff);
209    boolean issued = scanObsForIssued(observations, iss);
210    int cs = 2;
211    if (refRange) cs++;
212    if (flags) cs++;
213    if (note) cs++;
214    if (issued) cs++;
215    if (effectiveTime) cs++;
216    XhtmlNode tr = tbl.tr();
217    tr.td().b().tx("Code");
218    tr.td().b().tx("Value");
219    if (refRange) {
220      tr.td().b().tx("Reference Range");
221    }
222    if (flags) {
223      tr.td().b().tx("Flags");
224    }
225    if (note) {
226      tr.td().b().tx("Note");
227    }
228    if (effectiveTime) {
229      tr.td().b().tx("When For");
230    }
231    if (issued) {
232      tr.td().b().tx("Reported");
233    }
234    for (ObservationNode o : observations) {
235      addObservationToTable(tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss);
236    }
237  }
238
239  private boolean scanObsForRefRange(List<ObservationNode> observations) {
240    for (ObservationNode o : observations) {
241      if (o.obs != null && o.obs.getResource() != null) {
242        PropertyWrapper pw = getProperty(o.obs.getResource(), "referenceRange");
243        if (valued(pw)) {
244          return true;
245        }        
246      }
247      if (o.contained != null) {
248        if (scanObsForRefRange(o.contained)) {
249          return true;
250        }
251      }
252    }      
253    return false;
254  }
255
256  private boolean scanObsForNote(List<ObservationNode> observations) {
257    for (ObservationNode o : observations) {
258      if (o.obs != null && o.obs.getResource() != null) {
259        PropertyWrapper pw = getProperty(o.obs.getResource(), "note");
260        if (valued(pw)) {
261          return true;
262        }        
263      }
264      if (o.contained != null) {
265        if (scanObsForNote(o.contained)) {
266          return true;
267        }
268      }
269    }      
270    return false;
271  }
272
273  private boolean scanObsForIssued(List<ObservationNode> observations, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException {
274    for (ObservationNode o : observations) {
275      if (o.obs != null && o.obs.getResource() != null) {
276        PropertyWrapper pw = getProperty(o.obs.getResource(), "issued");
277        if (valued(pw)) {
278          if (!Base.compareDeep(pw.value().getBase(), iss, true)) {
279            return true;
280          }
281        }        
282      }
283      if (o.contained != null) {
284        if (scanObsForIssued(o.contained, iss)) {
285          return true;
286        }
287      }
288    }      
289    return false;
290  }
291
292  private boolean scanObsForEffective(List<ObservationNode> observations, DataType eff) throws UnsupportedEncodingException, FHIRException, IOException {
293    for (ObservationNode o : observations) {
294      if (o.obs != null && o.obs.getResource() != null) {
295        PropertyWrapper pw = getProperty(o.obs.getResource(), "effective[x]");
296        if (valued(pw)) {
297          if (!Base.compareDeep(pw.value().getBase(), eff, true)) {
298            return true;
299          }
300        }        
301      }
302      if (o.contained != null) {
303        if (scanObsForEffective(o.contained, eff)) {
304          return true;
305        }
306      }
307    }      
308    return false;
309  }
310
311  private boolean scanObsForFlags(List<ObservationNode> observations) throws UnsupportedEncodingException, FHIRException, IOException {
312    for (ObservationNode o : observations) {
313      if (o.obs != null && o.obs.getResource() != null) {
314        PropertyWrapper pw = getProperty(o.obs.getResource(), "interpretation");
315        if (valued(pw)) {
316          return true;
317        }
318        pw = getProperty(o.obs.getResource(), "status");
319        if (valued(pw)) {
320          if (!pw.value().getBase().primitiveValue().equals("final")) {
321            return true;
322          }
323        }
324        
325      }
326      if (o.contained != null) {
327        if (scanObsForFlags(o.contained)) {
328          return true;
329        }
330      }
331    }      
332    return false;
333  }
334
335  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 {
336    XhtmlNode tr = tbl.tr();
337    if (o.obs != null && o.obs.getReference()  == null) {
338      XhtmlNode td = tr.td().colspan(cs);
339      td.i().tx("This Observation could not be resolved");
340    } else {
341      if (o.obs != null && o.obs.getResource() != null) {
342        addObservationToTable(tr, o.obs.getResource(), i, o.obs.getReference(), refRange, flags, note, effectiveTime, issued, eff, iss);
343      } else {
344        XhtmlNode td = tr.td().colspan(cs);
345        td.i().tx("Observation");
346      }
347      if (o.contained != null) {
348        for (ObservationNode c : o.contained) {
349          addObservationToTable(tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss);
350        }
351      }
352    } 
353  }
354
355  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 {
356
357    // code (+bodysite)
358    XhtmlNode td = tr.td();
359    PropertyWrapper pw = getProperty(obs, "code");
360    if (valued(pw)) {
361      render(td.ah(ref), pw.value());
362    }
363    pw = getProperty(obs, "bodySite");
364    if (valued(pw)) {
365      td.tx(" (");
366      render(td, pw.value());
367      td.tx(")");
368    }
369
370    // value / dataAbsentReason (in red)
371    td = tr.td();
372    pw = getProperty(obs, "value[x]");
373    if (valued(pw)) {
374      render(td, pw.value());
375    } else {
376      pw = getProperty(obs, "dataAbsentReason");
377      if (valued(pw)) {
378        XhtmlNode span = td.span("color: maroon", "Error");
379        span.tx("Error: ");
380        render(span.b(), pw.value());
381      }
382    }
383    if (refRange) {
384      // reference range
385      td = tr.td();
386      pw = getProperty(obs, "referenceRange");
387      if (valued(pw)) {
388        boolean first = true;
389        for (BaseWrapper v : pw.getValues()) {
390          if (first) first = false; else td.br();
391          PropertyWrapper pwr = getProperty(v, "type"); 
392          if (valued(pwr)) {
393            render(td, pwr.value());
394            td.tx(": ");
395          }
396          PropertyWrapper pwt = getProperty(v, "text"); 
397          if (valued(pwt)) {
398            render(td, pwt.value());
399          } else {
400            PropertyWrapper pwl = getProperty(v, "low"); 
401            PropertyWrapper pwh = getProperty(v, "high"); 
402            if (valued(pwl) && valued(pwh)) {
403              render(td, pwl.value());
404              td.tx(" - ");
405              render(td, pwh.value());
406            } else if (valued(pwl)) {
407              td.tx(">");
408              render(td, pwl.value());
409            } else if (valued(pwh)) {
410              td.tx("<");
411              render(td, pwh.value());
412            } else {
413              td.tx("??");
414            }
415          }
416          pwr = getProperty(v, "appliesTo"); 
417          PropertyWrapper pwrA = getProperty(v, "age"); 
418          if (valued(pwr) || valued(pwrA)) {
419            boolean firstA = true;
420            td.tx(" for ");
421            if (valued(pwr)) {
422              for (BaseWrapper va : pwr.getValues()) {
423                if (firstA) firstA = false; else td.tx(", ");
424                render(td, va);
425              }
426            }
427            if (valued(pwrA)) {
428              if (firstA) firstA = false; else td.tx(", ");
429              td.tx("Age ");
430              render(td, pwrA.value());
431            }
432          }
433        }        
434      }      
435    }
436    if (flags) {
437      // flags (status other than F, interpretation, )
438      td = tr.td();
439      boolean first = true;
440      pw = getProperty(obs, "status");
441      if (valued(pw)) {
442        if (!pw.value().getBase().primitiveValue().equals("final")) {
443          if (first) first = false; else td.br();
444          render(td, pw.value());
445        }
446      }
447      pw = getProperty(obs, "interpretation");
448      if (valued(pw)) {
449        for (BaseWrapper v : pw.getValues()) {
450          if (first) first = false; else td.br();
451          render(td, v);
452        }
453      }
454    }
455
456    if (note) {
457      td = tr.td();
458      pw = getProperty(obs, "note");
459      if (valued(pw)) {
460        render(td, pw.value());
461      }
462    }
463    if (effectiveTime) {
464      // effective if different to DR
465      td = tr.td();
466      pw = getProperty(obs, "effective[x]");
467      if (valued(pw)) {
468        if (!Base.compareDeep(pw.value().getBase(), eff, true)) {
469          render(td, pw.value());
470        }
471      }
472    }
473    if (issued) {
474      // issued if different to DR
475      td = tr.td();
476      pw = getProperty(obs, "issued");
477      if (valued(pw)) {
478        if (!Base.compareDeep(pw.value().getBase(), eff, true)) {
479          render(td, pw.value());
480        }
481      }
482    }
483  }
484}