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