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