001package org.hl7.fhir.r4.ips;
002
003import java.util.Date;
004import java.util.List;
005
006import org.hl7.fhir.r4.ips.IPSBuilder.TypeAndId;
007import org.hl7.fhir.r4.model.Age;
008import org.hl7.fhir.r4.model.Annotation;
009import org.hl7.fhir.r4.model.Bundle;
010import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
011import org.hl7.fhir.r4.model.Bundle.BundleType;
012import org.hl7.fhir.r4.model.CodeableConcept;
013import org.hl7.fhir.r4.model.Coding;
014import org.hl7.fhir.r4.model.Composition;
015import org.hl7.fhir.r4.model.Patient;
016import org.hl7.fhir.r4.model.Period;
017import org.hl7.fhir.r4.model.Quantity;
018import org.hl7.fhir.r4.model.Range;
019import org.hl7.fhir.r4.model.Reference;
020import org.hl7.fhir.r4.model.Type;
021import org.hl7.fhir.r4.model.Composition.CompositionStatus;
022import org.hl7.fhir.r4.model.Composition.SectionComponent;
023import org.hl7.fhir.r4.model.Condition;
024import org.hl7.fhir.r4.model.Device;
025import org.hl7.fhir.r4.model.DomainResource;
026import org.hl7.fhir.r4.model.Dosage;
027import org.hl7.fhir.r4.model.Identifier;
028import org.hl7.fhir.r4.model.Medication;
029import org.hl7.fhir.r4.model.MedicationStatement;
030import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
031import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
032import org.hl7.fhir.utilities.Utilities;
033import org.hl7.fhir.utilities.VersionUtil;
034import org.hl7.fhir.utilities.xhtml.XhtmlNode;
035
036public class IPSBuilder {
037
038  public static class TypeAndId {
039    private String type;
040    private String id;
041    protected TypeAndId(String type, String id) {
042      super();
043      this.type = type;
044      this.id = id;
045    }
046    public String getType() {
047      return type;
048    }
049    public String getId() {
050      return id;
051    }
052  }
053
054  public static Bundle generateIPS(FHIRToolingClient server, String patientId) {
055    Patient pat = server.fetchResource(Patient.class, patientId);
056    Bundle bnd = initBundle();
057    Composition cmp = initComposition(bnd, server.getAddress(), pat);
058    pat = processPatient(bnd, server.getAddress(), pat);
059//    addMedications(bnd, cmp, server, patientId);
060    addConditions(bnd, cmp, server, patientId);
061    return bnd;
062  }
063
064  private static Bundle initBundle() {
065    Bundle bnd = new Bundle();
066    bnd.getIdentifier().setSystem("urn:ietf:rfc:3986");
067    bnd.getIdentifier().setValue(Utilities.makeUuidUrn());
068    bnd.setType(BundleType.DOCUMENT);
069    bnd.setTimestamp(new Date());
070    return bnd;
071  }
072  
073  private static Composition initComposition(Bundle bnd, String url, Patient pat) {
074    Composition cmp = new Composition();
075    cmp.setIdBase(Utilities.makeUuidLC());
076    cmp.setStatus(CompositionStatus.FINAL);
077    cmp.getType().addCoding().setSystem("http://loinc.org").setCode("60591-5");
078    cmp.setDate(new Date());
079    cmp.setTitle("International Patient Summary");
080    cmp.getSubject().setReference("Patient/"+pat.getIdBase());
081    cmp.addAuthor().setReference("Device/java");
082    bnd.addEntry().setResource(cmp).setFullUrl(Utilities.pathURL(url, "Composition", cmp.getIdBase()));
083    Device dev = new Device();
084    dev.setId("java");
085    dev.addDeviceName().setName("Java Core Library");
086    dev.addVersion().setValue(VersionUtil.getVersion());
087    bnd.addEntry().setResource(dev).setFullUrl(Utilities.pathURL(url, "Device", dev.getIdBase()));
088    return cmp;
089  }
090
091  private static Patient processPatient(Bundle bnd,  String url, Patient pat) {
092    bnd.addEntry().setResource(pat).setFullUrl(Utilities.pathURL(url, "Patient", pat.getIdBase()));
093    return pat;
094  }
095  
096  private static void addMedications(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) {
097    Bundle sb = server.search("MedicationStatement", "?patient="+patientId+"&_include=MedicationStatement:medication&_include=MedicationStatement:source");
098    SectionComponent sct = cmp.addSection();
099    sct.setTitle("Medications");
100    sct.getCode().addCoding().setSystem("http://loinc.org").setCode("10160-0");
101    sct.getText().setStatus(NarrativeStatus.GENERATED);
102    var x = sct.getText().getDiv();
103    var tbl = x.table("grid");
104    var tr = tbl.tr();
105    tr.th().tx("Medication");
106    tr.th().tx("Category");
107    tr.th().tx("Status");
108    tr.th().tx("When");
109    tr.th().tx("Dosage");
110    tr.th().tx("Reason");
111    tr.th().tx("Source");
112    tr.th().tx("Notes");
113
114    boolean ok = false;
115    for (BundleEntryComponent be : sb.getEntry()) {
116      if (be.hasResource() && be.getResource() instanceof MedicationStatement) {
117        MedicationStatement mdstmt = (MedicationStatement) be.getResource();
118        ok = true;
119        bnd.addEntry().setResource(mdstmt).setFullUrl(Utilities.pathURL(server.getAddress(), "MedicationStatement", mdstmt.getIdBase()));
120        sct.addEntry().setReference("MedicationStatement/"+mdstmt.getIdBase());
121        tr = tbl.tr();
122        if (mdstmt.hasMedicationReference()) {
123          Medication med = findMedication(sb, server, mdstmt, mdstmt.getMedicationReference());
124          if (med == null) {
125            tr.td().b().tx("Unknown?");
126          } else {
127            tr.td().tx(summarise(med));
128            bnd.addEntry().setResource(med).setFullUrl(Utilities.pathURL(server.getAddress(), "Medication", med.getIdBase()));
129          }
130        } else {
131          tr.td().tx(genCC(mdstmt.getMedicationCodeableConcept()));
132        }
133        tr.td().tx(genCC(mdstmt.getCategory()));
134        var td = tr.td();
135        td.tx(mdstmt.getStatus().getDisplay());
136        if (mdstmt.hasStatusReason()) {
137          td.tx(" (");
138          td.tx(genCC(mdstmt.getStatusReason()));
139          td.tx(")");
140        }
141        tr.td().tx(genDT(mdstmt.getEffective()));
142        genDosages(tr.td(), mdstmt.getDosage());
143        tr.td().tx(genReference(mdstmt, mdstmt.getInformationSource(), bnd, sb, server));
144        genNotes(tr.td(), mdstmt.getNote());
145      }
146    }
147    if (!ok) {
148      Condition cnd = new Condition();
149      cnd.setId(Utilities.makeUuidLC());
150
151      cnd.getText().setStatus(NarrativeStatus.GENERATED);
152      var rx = cnd.getText().getDiv();
153      rx.tx("No information is provided about the patient's medical problems");
154      tr = tbl.tr();
155      tr.td().colspan(7).tx("No information is provided about the patient's medical problems");
156      cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active");
157      cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems");
158      cnd.getSubject().setReference("Patient/"+patientId);
159    }
160  }
161
162
163  private static void genDosages(XhtmlNode x, List<Dosage> dosages) {
164    if (dosages == null || dosages.size() == 0) {
165      
166    } else if (dosages.size() == 1) {
167      genDosage(x, dosages.get(0));
168    } else {
169      var ul = x.ul();
170      for (Dosage d : dosages) {
171        genDosage(ul.li(), d);
172      }
173    } 
174  }
175
176  private static void genDosage(XhtmlNode x, Dosage dosage) {
177    x.tx(dosage.getText());
178    if (dosage.hasAsNeeded()) {
179      x.nbsp();
180      if (dosage.hasAsNeededBooleanType()) {
181        if (dosage.getAsNeededBooleanType().booleanValue()) {
182          x.tx(" (as needed)");
183        }
184      } else {
185        x.tx(genDT(dosage.getAsNeeded()));
186      }
187    } else if (dosage.hasTiming()) {
188      x.nbsp();
189      x.tx(genDT(dosage.getTiming()));
190    }
191    if (dosage.hasSite()) {
192      x.tx(". ");
193      x.tx(genDT(dosage.getSite()));
194    }
195    if (dosage.hasRoute()) {
196      x.tx(". ");
197      x.tx(genDT(dosage.getRoute()));
198    }
199  }
200
201  private static Medication findMedication(Bundle sb, FHIRToolingClient server, MedicationStatement mdstmt, Reference ref) {
202    if (ref == null || !ref.hasReference()) {
203      return null;
204    }
205    if (ref.getReference().startsWith("#")) {
206      
207    } else {
208      
209    }
210    return null;
211  }
212
213  private static void addConditions(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) {
214    Bundle sb = server.search("Condition", "?patient="+patientId+"&_include=Condition:asserter");
215    SectionComponent sct = cmp.addSection();
216    sct.setTitle("Problems");
217    sct.getCode().addCoding().setSystem("http://loinc.org").setCode("11450-4");
218    sct.getText().setStatus(NarrativeStatus.GENERATED);
219    var x = sct.getText().getDiv();
220    var tbl = x.table("grid");
221    var tr = tbl.tr();
222    tr.th().tx("Code");
223    tr.th().tx("Category");
224    tr.th().tx("Severity");
225    tr.th().tx("Status");
226    tr.th().tx("Onset");
227    tr.th().tx("Abatement");
228    tr.th().tx("Source");
229    tr.th().tx("Notes");
230
231    boolean ok = false;
232    for (BundleEntryComponent be : sb.getEntry()) {
233      if (be.hasResource() && be.getResource() instanceof Condition) {
234        Condition cnd = (Condition) be.getResource();
235        ok = true;
236        bnd.addEntry().setResource(cnd).setFullUrl(Utilities.pathURL(server.getAddress(), "Condition", cnd.getIdBase()));
237        sct.addEntry().setReference("Condition/"+cnd.getIdBase());
238        tr = tbl.tr();
239        tr.td().tx(genCC(cnd.getCode()));
240        tr.td().tx(genCC(cnd.getCategory()));
241        tr.td().tx(genCC(cnd.getSeverity()));
242        tr.td().tx(genStatus(cnd));
243        tr.td().tx(genDT(cnd.getOnset()));
244        tr.td().tx(genDT(cnd.getAbatement()));
245        tr.td().tx(genSource(cnd, bnd, sb, server));
246        genNotes(tr.td(), cnd.getNote());
247
248      }
249    }
250    if (!ok) {
251      Condition cnd = new Condition();
252      cnd.setId(Utilities.makeUuidLC());
253
254      cnd.getText().setStatus(NarrativeStatus.GENERATED);
255      var rx = cnd.getText().getDiv();
256      rx.tx("No information is provided about the patient's medical problems");
257      tr = tbl.tr();
258      tr.td().colspan(7).tx("No information is provided about the patient's medical problems");
259      cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active");
260      cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems");
261      cnd.getSubject().setReference("Patient/"+patientId);
262    }
263  }
264
265
266  private static String genReference(DomainResource src, Reference ref, Bundle bnd, Bundle search, FHIRToolingClient server) {
267    if (ref == null || ref.isEmpty()) {
268      return null;
269    }
270    boolean contained = false;
271    DomainResource tgt = null;
272    if (ref.hasReference()) {
273      if (ref.getReference().startsWith("#")) {
274        tgt = (DomainResource) src.getContained(ref.getReference());
275        contained = true;
276      } else {
277        TypeAndId tid = getTypeAndId(server.getAddress(), ref.getReference());
278        if (tid != null) {
279          tgt = findInBundle(bnd, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId()));
280          if (tgt == null) {
281            tgt = findInBundle(search, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId()));
282            if (tgt == null) {
283              tgt = (DomainResource) server.read(tid.getType(), tid.getId());
284            }
285          } else {
286            contained = true;
287          }
288        }
289      }
290    }
291    if (tgt != null) {
292      if (!contained) {
293        bnd.addEntry().setResource(tgt).setFullUrl(Utilities.pathURL(server.getAddress(), tgt.fhirType(), tgt.getIdBase()));
294      }
295      return summarise(tgt);
296    } else if (ref.hasDisplay()) {
297      return ref.getDisplay();
298    } else if (ref.hasReference()) {
299      return ref.getReference();
300    } else if (ref.hasIdentifier()) {
301      return genIdentifier(ref.getIdentifier());
302    } else {
303      return "unknown";
304    }
305  }
306
307  
308  private static TypeAndId getTypeAndId(String baseUrl, String url) {
309    if (Utilities.noString(url)) {
310      return null;
311    }
312    if (url.startsWith(baseUrl+"/")) {
313      url = url.substring(baseUrl.length()+1);
314    }
315    String[] p = url.split("\\/");
316    if (p.length > 1) {
317      if ("_history".equals(p[p.length-2]) && p.length > 3) {
318        return new TypeAndId(p[p.length-4], p[p.length-3]);
319      } else {
320        return new TypeAndId(p[p.length-2], p[p.length-1]);
321      }
322    }
323    return null;
324  }
325
326  private static DomainResource findInBundle(Bundle bnd, String url) {
327    for (BundleEntryComponent be : bnd.getEntry()) {
328      if (url.equals(be.getFullUrl()) && be.hasResource() && be.getResource() instanceof DomainResource) {
329        return (DomainResource) be.getResource();
330      }
331    }
332    return null;
333  }
334
335  private static String summarise(DomainResource tgt) {
336    // TODO Auto-generated method stub
337    return null;
338  }
339
340  private static String genIdentifier(Identifier id) {
341    return id.getValue();
342  }
343
344  private static void genNotes(XhtmlNode td, List<Annotation> notes) {
345    if (notes.size() > 0) {
346      if (notes.size() == 1) {
347        genNote(td, notes.get(0));
348      } else {
349        var ul = td.ul();
350        for (Annotation a : notes) {
351          genNote(ul.li(), a);
352        }
353      }
354    }
355  }
356
357  private static void genNote(XhtmlNode td, Annotation annotation) {
358    td.tx(annotation.getText());    
359  }
360
361  private static String genSource(Condition cnd, Bundle bnd, Bundle sb, FHIRToolingClient server) {
362    if (cnd.hasAsserter()) {
363      return genReference(cnd, cnd.getAsserter(), bnd, sb, server);
364    } else if (cnd.hasRecorder()) {
365      return genReference(cnd, cnd.getRecorder(), bnd, sb, server);
366    } else {
367      return null;
368    }
369  }
370
371  private static String genDT(Type v) {
372    if (v == null) {
373      return null;
374    }
375    if (v.isPrimitive()) {
376      return v.primitiveValue();
377    }
378    if (v instanceof Age) {
379      return genQty((Age) v);
380    }
381    if (v instanceof Period) {
382      Period p = (Period) v;
383      return genDT(p.getStartElement())+" - "+genDT(p.getStartElement());
384    }
385    if (v instanceof Range) {
386      Range p = (Range) v;
387      return genDT(p.getLow())+" - "+genDT(p.getHigh());
388    }
389    return "not done: "+v.fhirType();
390  }
391
392  private static String genQty(Quantity v) {
393    return v.getValue().toPlainString()+v.getUnit();
394  }
395
396  private static String genStatus(Condition cnd) {
397    if (cnd.hasClinicalStatus() && cnd.hasVerificationStatus()) {
398      return genCC(cnd.getClinicalStatus()) +"/"+genCC(cnd.getVerificationStatus());
399    } else if (cnd.hasClinicalStatus()) {
400      return genCC(cnd.getClinicalStatus());
401    } else if (cnd.hasVerificationStatus()) {
402      return genCC(cnd.getVerificationStatus());      
403    } else {
404      return null;
405    }
406  }
407
408  private static String genCC(List<CodeableConcept> list) {
409    if (list != null && list.size() == 1) {
410      return genCC(list.get(0));
411    } else {
412      return null;
413    }
414  }
415
416  private static String genCC(CodeableConcept code) {
417    if (code.hasText()) {
418      return code.getText();
419    } else if (code.hasCoding()) {
420      Coding c = code.getCodingFirstRep();
421      if (c.hasDisplay()) {
422        return c.getDisplay();
423      } else {
424        return c.getCode();
425      }
426    } else {
427      return null;
428    }
429  }
430
431}