001package org.hl7.fhir.convertors.misc.argonaut;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import org.apache.commons.io.IOUtils;
034import org.fhir.ucum.UcumEssenceService;
035import org.fhir.ucum.UcumService;
036import org.hl7.fhir.convertors.misc.CDAUtilities;
037import org.hl7.fhir.convertors.misc.ccda.CcdaExtensions;
038import org.hl7.fhir.convertors.misc.Convert;
039import org.hl7.fhir.convertors.misc.ConverterBase;
040import org.hl7.fhir.dstu3.context.SimpleWorkerContext;
041import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
042import org.hl7.fhir.dstu3.formats.JsonParser;
043import org.hl7.fhir.dstu3.formats.XmlParser;
044import org.hl7.fhir.dstu3.model.*;
045import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceReactionComponent;
046import org.hl7.fhir.dstu3.model.Condition.ConditionVerificationStatus;
047import org.hl7.fhir.dstu3.model.DocumentReference.DocumentReferenceContentComponent;
048import org.hl7.fhir.dstu3.model.DocumentReference.DocumentReferenceContextComponent;
049import org.hl7.fhir.dstu3.model.Encounter.EncounterHospitalizationComponent;
050import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus;
051import org.hl7.fhir.dstu3.model.Enumerations.DocumentReferenceStatus;
052import org.hl7.fhir.dstu3.model.Immunization.ImmunizationExplanationComponent;
053import org.hl7.fhir.dstu3.model.Immunization.ImmunizationStatus;
054import org.hl7.fhir.dstu3.model.ListResource.ListMode;
055import org.hl7.fhir.dstu3.model.ListResource.ListStatus;
056import org.hl7.fhir.dstu3.model.MedicationStatement.MedicationStatementStatus;
057import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
058import org.hl7.fhir.dstu3.model.Observation.ObservationRelationshipType;
059import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
060import org.hl7.fhir.dstu3.model.Procedure.ProcedurePerformerComponent;
061import org.hl7.fhir.dstu3.model.Procedure.ProcedureStatus;
062import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
063import org.hl7.fhir.dstu3.utils.ResourceUtilities;
064import org.hl7.fhir.utilities.Utilities;
065import org.hl7.fhir.utilities.ZipGenerator;
066import org.hl7.fhir.utilities.validation.ValidationMessage;
067import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
068import org.hl7.fhir.utilities.xhtml.NodeType;
069import org.hl7.fhir.utilities.xhtml.XhtmlNode;
070import org.hl7.fhir.utilities.xml.XMLUtil;
071import org.w3c.dom.Element;
072
073import java.io.ByteArrayOutputStream;
074import java.io.File;
075import java.io.FileInputStream;
076import java.io.IOException;
077import java.util.*;
078
079public class ArgonautConverter extends ConverterBase {
080  //  public final static String DEF_TS_SERVER = "http://fhir-dev.healthintersections.com.au/open";
081  public final static String DEV_TS_SERVER = "http://local.fhir.org:960/open";
082  public static final String UCUM_PATH = "c:\\work\\org.hl7.fhir\\build\\implementations\\java\\org.hl7.fhir.convertors\\samples\\ucum-essence.xml";
083  public static final String SRC_PATH = "c:\\work\\org.hl7.fhir\\build\\publish\\";
084  private static final String DEFAULT_ID_SPACE = "urn:uuid:e8e06b15-0f74-4b8e-b5e2-609dae7119dc";
085
086  private static final boolean WANT_SAVE = true;
087  private static final boolean WANT_VALIDATE = false;
088  private final UcumService ucumSvc;
089  //    private ValidationEngine validator;
090  private final SimpleWorkerContext context;
091  private final Map<String, Map<String, Integer>> sections = new HashMap<>();
092  private final Map<String, Practitioner> practitionerCache = new HashMap<>();
093  private final Set<String> oids = new HashSet<>();
094  private final Map<String, ZipGenerator> zipsX = new HashMap<>();
095  private final Map<String, ZipGenerator> zipsJ = new HashMap<>();
096  private final Map<String, Stats> stats = new HashMap<>();
097  public int perfCount;
098  int errors = 0;
099  int warnings = 0;
100  Map<String, Integer> procCodes = new HashMap<>();
101  Map<String, Integer> condCodes = new HashMap<>();
102  Map<String, Integer> allergyCodes = new HashMap<>();
103  private String destFolder;
104  private ZipGenerator zipJ;
105  private ZipGenerator zipX;
106
107  public ArgonautConverter(UcumService ucumSvc, String path) throws Exception {
108    super();
109    this.ucumSvc = ucumSvc;
110    context = SimpleWorkerContext.fromPack(path);
111//              validator = new ValidationEngine();
112//              validator.readDefinitions(path);
113//              validator.setAnyExtensionsAllowed(true);
114  }
115
116  public static void main(String[] args) {
117    try {
118      ArgonautConverter c = new ArgonautConverter(new UcumEssenceService(UCUM_PATH), Utilities.path(SRC_PATH, "validation.xml.zip"));
119      c.destFolder = "C:\\work\\com.healthintersections.fhir\\argonaut\\fhir";
120      c.convert("C:\\work\\com.healthintersections.fhir\\argonaut\\cda\\file_emergency", new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("EMER"));
121      c.convert("C:\\work\\com.healthintersections.fhir\\argonaut\\cda\\file_ed", new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("IMP"));
122      c.convert("C:\\work\\com.healthintersections.fhir\\argonaut\\cda\\fileX", new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("AMB"));
123      c.printSectionSummaries();
124      c.closeZips();
125      System.out.println("All done. " + c.getErrors() + " errors, " + c.getWarnings() + " warnings");
126    } catch (Exception e) {
127      e.printStackTrace();
128    }
129  }
130
131  public int getErrors() {
132    return errors;
133  }
134
135  public int getWarnings() {
136    return warnings;
137  }
138
139  public void convert(String sourceFolder, Coding clss) throws Exception {
140    File source = new File(sourceFolder);
141    for (String f : source.list()) {
142      convert(sourceFolder, f, clss);
143    }
144  }
145
146  private void closeZips() throws Exception {
147    for (ZipGenerator z : zipsJ.values())
148      z.close();
149    for (ZipGenerator z : zipsX.values())
150      z.close();
151  }
152
153  public void printSectionSummaries() {
154    System.out.println("Statistics:");
155    for (String n : sorted(stats.keySet())) {
156      Stats s = stats.get(n);
157      System.out.println("  " + n + ": generated " + s.getInstances() + ", errors " + s.getErrors() + ", warnings " + s.getWarnings());
158    }
159
160    System.out.println("OIDs:");
161    for (String n : sorted(oids))
162      System.out.println("  " + n);
163
164    for (String n : sections.keySet()) {
165      System.out.println(n + " Analysis");
166      Map<String, Integer> s = sections.get(n);
167      for (String p : sorted(s.keySet())) {
168        System.out.println("  " + p + ": " + s.get(p));
169      }
170    }
171
172    dumpCodes();
173  }
174
175  private List<String> sorted(Set<String> keys) {
176    List<String> names = new ArrayList<>();
177    names.addAll(keys);
178    Collections.sort(names);
179    return names;
180  }
181
182  private void convert(String sourceFolder, String filename, Coding clss) throws IOException {
183    if (new File(Utilities.path(sourceFolder, filename)).length() == 0)
184      return;
185
186    CDAUtilities cda;
187    try {
188      System.out.println("Process " + Utilities.path(sourceFolder, filename));
189      cda = new CDAUtilities(new FileInputStream(Utilities.path(sourceFolder, filename)));
190      zipJ = new ZipGenerator(Utilities.path(destFolder, "json/doc", Utilities.changeFileExt(filename, ".json.zip")));
191      zipX = new ZipGenerator(Utilities.path(destFolder, "xml/doc", Utilities.changeFileExt(filename, ".xml.zip")));
192      Element doc = cda.getElement();
193      Convert convert = new Convert(cda, ucumSvc, "-0400");
194      convert.setGenerateMissingExtensions(true);
195      Context context = new Context();
196      context.setBaseId(Utilities.changeFileExt(filename, ""));
197      context.setEncClass(clss);
198      makeSubject(cda, convert, doc, context, context.getBaseId() + "-patient");
199      makeAuthor(cda, convert, doc, context, context.getBaseId() + "-author");
200      makeEncounter(cda, convert, doc, context, context.getBaseId() + "-encounter");
201      Element body = cda.getDescendent(doc, "component/structuredBody");
202      for (Element c : cda.getChildren(body, "component")) {
203        processSection(cda, convert, context, cda.getChild(c, "section"));
204      }
205      oids.addAll(convert.getOids());
206      saveResource(context.getEncounter());
207      makeBinary(sourceFolder, filename, context);
208      makeDocumentReference(cda, convert, doc, context);
209      zipJ.close();
210      zipX.close();
211    } catch (Exception e) {
212      throw new Error("Unable to process " + Utilities.path(sourceFolder, filename) + ": " + e.getMessage(), e);
213    }
214  }
215
216  private void processSection(CDAUtilities cda, Convert convert, Context context, Element section) throws Exception {
217    checkNoSubject(cda, section, "Section");
218
219    // this we do by templateId
220    if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.11") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.5.1"))
221      processProblemsSection(cda, convert, section, context);
222    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.12") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.7.1"))
223      processProcedureSection(cda, convert, section, context);
224    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.3") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.22.1"))
225      processEncountersSection(cda, convert, section, context);
226    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.6.1"))
227      processAllergiesSection(cda, convert, section, context);
228    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.2.1") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.6"))
229      processImmunizationsSection(cda, convert, section, context);
230    else if (cda.hasTemplateId(section, "1.3.6.1.4.1.19376.1.5.3.1.3.1"))
231      processReasonForEncounter(cda, convert, section, context);
232    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.3.1") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.14"))
233      processResultsSection(cda, convert, section, context);
234    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.4.1") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.16"))
235      processVitalSignsSection(cda, convert, section, context);
236    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.1.1") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.8"))
237      processMedicationsSection(cda, convert, section, context);
238    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.17") || cda.hasTemplateId(section, "2.16.840.1.113883.3.88.11.83.126"))
239      processSocialHistorySection(cda, convert, section, context);
240    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.1.9"))
241      scanSection("Payers", section);
242    else
243      throw new Exception("Unprocessed section " + cda.getChild(section, "title").getTextContent());
244  }
245
246  private void checkNoSubject(CDAUtilities cda, Element act, String path) throws Exception {
247    if (cda.getChild(act, "subject") != null)
248      throw new Exception("The conversion program cannot accept a subject at the location " + path);
249  }
250
251  private void scanSection(String name, Element child) {
252    Map<String, Integer> section;
253    if (sections.containsKey(name))
254      section = sections.get(name);
255    else {
256      section = new HashMap<>();
257      sections.put(name, section);
258    }
259    iterateChildren(section, "/", child);
260  }
261
262  private void iterateChildren(Map<String, Integer> section, String path, Element element) {
263    Element child = XMLUtil.getFirstChild(element);
264    while (child != null) {
265      String pathC = path + child.getNodeName() + attributes(child);
266      if (section.containsKey(pathC))
267        section.put(pathC, section.get(pathC) + 1);
268      else
269        section.put(pathC, 1);
270      iterateChildren(section, pathC + "/", child);
271      child = XMLUtil.getNextSibling(child);
272    }
273  }
274
275  private String attributes(Element child) {
276    String s = ",";
277    if (child.hasAttribute("inversionInd"))
278      s += "inversionInd:" + child.getAttribute("inversionInd") + ",";
279    if (child.hasAttribute("negationInd"))
280      s += "negationInd:" + child.getAttribute("negationInd") + ",";
281    if (child.hasAttribute("nullFlavor"))
282      s += "nullFlavor:" + child.getAttribute("nullFlavor") + ",";
283    if (child.hasAttribute("xsi:type"))
284      s += "type:" + child.getAttribute("xsi:type") + ",";
285    s = s.substring(0, s.length() - 1);
286
287    if (child.getNodeName().equals("statusCode"))
288      return "[code:" + child.getAttribute("code") + "]";
289    if (child.getNodeName().equals("temnplateId"))
290      return "[id:" + child.getAttribute("root") + "]";
291    else if (child.hasAttribute("moodCode"))
292      return "[" + child.getAttribute("classCode") + "," + child.getAttribute("moodCode") + s + "]";
293    else if (child.hasAttribute("classCode"))
294      return "[" + child.getAttribute("classCode") + s + "]";
295    else if (child.hasAttribute("typeCode"))
296      return "[" + child.getAttribute("typeCode") + s + "]";
297    else if (Utilities.noString(s))
298      return "";
299    else
300      return "[" + s.substring(1) + "]";
301  }
302
303  private void saveResource(Resource resource) throws Exception {
304    saveResource(resource, null);
305  }
306
307  private void saveResource(Resource resource, String extraType) throws Exception {
308    if (!WANT_SAVE)
309      return;
310
311    DomainResource dr = null;
312    if (resource instanceof DomainResource) {
313      dr = (DomainResource) resource;
314      if (!dr.hasText()) {
315        NarrativeGenerator generator = new NarrativeGenerator("", "", context);
316        generator.generate(dr);
317      }
318    }
319    XmlParser xparser = new XmlParser();
320    xparser.setOutputStyle(OutputStyle.PRETTY);
321    JsonParser jparser = new JsonParser();
322    jparser.setOutputStyle(OutputStyle.PRETTY);
323
324    ByteArrayOutputStream ba = new ByteArrayOutputStream();
325    xparser.compose(ba, resource);
326    ba.close();
327    byte[] srcX = ba.toByteArray();
328    ba = new ByteArrayOutputStream();
329    jparser.compose(ba, resource);
330    ba.close();
331    byte[] srcJ = ba.toByteArray();
332
333    String rn = resource.getResourceType().toString();
334    if (extraType != null)
335      rn = rn + extraType;
336    zipX.addBytes(resource.getId() + ".xml", srcX, false);
337    zipJ.addBytes(resource.getId() + ".json", srcJ, false);
338    if (!zipsX.containsKey(rn)) {
339      zipsX.put(rn, new ZipGenerator(Utilities.path(destFolder, "xml/type", rn + ".xml.zip")));
340      zipsJ.put(rn, new ZipGenerator(Utilities.path(destFolder, "json/type", rn + ".json.zip")));
341      stats.put(rn, new Stats());
342    }
343
344    zipsJ.get(rn).addBytes(resource.getId() + ".json", srcJ, false);
345    zipsX.get(rn).addBytes(resource.getId() + ".xml", srcX, false);
346    Stats ss = stats.get(rn);
347    ss.setInstances(ss.getInstances() + 1);
348
349    String profile = resource.getUserString("profile");
350    validate(srcX, profile, resource, ss);
351  }
352
353  private void validate(byte[] src, String url, Resource resource, Stats stats) throws Exception {
354    if (!WANT_VALIDATE)
355      return;
356    if (url == null)
357      url = "http://hl7.org/fhir/StructureDefinition/" + resource.getResourceType().toString();
358    StructureDefinition def = context.fetchResource(StructureDefinition.class, url);
359    if (def == null)
360      throw new Exception("Unable to find Structure Definition " + url);
361
362//              validator.reset();
363//              validator.setProfile(def);
364//              validator.setSource(src);
365//              validator.process();
366    List<ValidationMessage> msgs = null; // validator.getOutputs();
367    boolean ok = false;
368    boolean first = true;
369    for (ValidationMessage m : msgs) {
370      if (m.getLevel() == IssueSeverity.ERROR && !msgOk(m.getMessage())) {
371        if (first) {
372          System.out.println("  validate " + resource.getId() + ".xml against " + url);
373          first = false;
374        }
375        System.out.println("    " + m.getLevel().toCode() + ": " + m.getMessage() + " @ " + m.getLocation());
376        if (m.getLevel() == IssueSeverity.WARNING) {
377          stats.setWarnings(stats.getWarnings() + 1);
378          warnings++;
379        }
380        if (m.getLevel() == IssueSeverity.ERROR || m.getLevel() == IssueSeverity.FATAL) {
381          stats.setErrors(stats.getErrors() + 1);
382          errors++;
383        }
384      }
385
386      ok = ok && !(m.getLevel() == IssueSeverity.ERROR || m.getLevel() == IssueSeverity.FATAL);
387    }
388  }
389
390  private boolean msgOk(String message) {
391    return message.equals("Invalid Resource target type. Found Observation, but expected one of (DiagnosticReport)");
392  }
393
394  private void checkGenerateIdentifier(List<Identifier> ids, DomainResource resource) {
395    if (ids.isEmpty())
396      ids.add(new Identifier().setSystem(DEFAULT_ID_SPACE).setValue(resource.getClass().getName().toLowerCase() + "-" + resource.getId()));
397  }
398
399  private void makeSubject(CDAUtilities cda, Convert convert, Element doc, Context context, String id) throws Exception {
400    Element rt = cda.getChild(doc, "recordTarget");
401    scanSection("Patient", rt);
402    Element pr = cda.getChild(rt, "patientRole");
403    Element p = cda.getChild(pr, "patient");
404
405    Patient pat = new Patient();
406    pat.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/patient-daf-dafpatient");
407    StringBuilder b = new StringBuilder();
408
409    pat.setId(id);
410    for (Element e : cda.getChildren(p, "name")) {
411      HumanName name = convert.makeNameFromEN(e);
412      pat.getName().add(name);
413      b.append(NarrativeGenerator.displayHumanName(name));
414      b.append(" ");
415    }
416    b.append("(");
417    for (Element e : cda.getChildren(pr, "id")) {
418      Identifier identifier = convert.makeIdentifierFromII(e);
419      pat.getIdentifier().add(identifier);
420      b.append(identifier.getValue());
421      b.append(", ");
422    }
423
424    for (Element e : cda.getChildren(pr, "addr"))
425      pat.getAddress().add(makeDefaultAddress(convert.makeAddressFromAD(e)));
426    for (Element e : cda.getChildren(pr, "telecom"))
427      pat.getTelecom().add(convert.makeContactFromTEL(e));
428    pat.setGender(convert.makeGenderFromCD(cda.getChild(p, "administrativeGenderCode")));
429    b.append(pat.getGender().getDisplay());
430    b.append(", ");
431    pat.setBirthDateElement(convert.makeDateFromTS(cda.getChild(p, "birthTime")));
432    b.append("DOB: ");
433    b.append(pat.getBirthDateElement().toHumanDisplay());
434    b.append(")");
435    pat.setMaritalStatus(convert.makeCodeableConceptFromCD(cda.getChild(p, "maritalStatusCode")));
436
437    pat.addExtension(Factory.newExtension(CcdaExtensions.DAF_NAME_RACE, convert.makeCodeableConceptFromCD(cda.getChild(p, "raceCode")), false));
438    pat.addExtension(Factory.newExtension(CcdaExtensions.DAF_NAME_ETHNICITY, convert.makeCodeableConceptFromCD(cda.getChild(p, "ethnicGroupCode")), false));
439
440    pat.addExtension(Factory.newExtension(CcdaExtensions.NAME_RELIGION, convert.makeCodeableConceptFromCD(cda.getChild(p, "religiousAffiliationCode")), false));
441    pat.addExtension(Factory.newExtension(CcdaExtensions.NAME_BIRTHPLACE, convert.makeAddressFromAD(cda.getChild(p, new String[]{"birthplace", "place", "addr"})), false));
442
443    Element g = cda.getChild(p, "guardian");
444    if (g != null) {
445      Patient.ContactComponent guardian = new Patient.ContactComponent();
446      pat.getContact().add(guardian);
447      guardian.getRelationship().add(Factory.newCodeableConcept("GUARD", "urn:oid:2.16.840.1.113883.5.110", "guardian"));
448      for (Element e : cda.getChildren(g, "addr"))
449        if (guardian.getAddress() == null)
450          guardian.setAddress(makeDefaultAddress(convert.makeAddressFromAD(e)));
451      for (Element e : cda.getChildren(g, "telecom"))
452        guardian.getTelecom().add(convert.makeContactFromTEL(e));
453      g = cda.getChild(g, "guardianPerson");
454      for (Element e : cda.getChildren(g, "name"))
455        if (guardian.getName() == null)
456          guardian.setName(convert.makeNameFromEN(e));
457    }
458
459    Element l = cda.getChild(p, "languageCommunication");
460    CodeableConcept cc = new CodeableConcept();
461    Coding c = new Coding();
462    c.setSystem(ResourceUtilities.FHIR_LANGUAGE);
463    c.setCode(patchLanguage(cda.getChild(l, "languageCode").getAttribute("code")));
464    cc.getCoding().add(c);
465    pat.addCommunication().setLanguage(cc);
466
467    Element prv = cda.getChild(pr, "providerOrganization");
468    if (prv != null)
469      pat.setManagingOrganization(new Reference().setReference("Organization/" + processOrganization(prv, cda, convert, context).getId()));
470
471    context.setSubjectRef(new Reference().setDisplay(b.toString()).setReference("Patient/" + pat.getId()));
472    saveResource(pat);
473  }
474
475  private Organization processOrganization(Element oo, CDAUtilities cda, Convert convert, Context context) throws Exception {
476    Organization org = new Organization();
477    org.setId(context.getBaseId() + "-organization-" + context.getOrgId());
478    org.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/org-daf-daforganization");
479    context.setOrgId(context.getOrgId() + 1);
480    for (Element e : cda.getChildren(oo, "id"))
481      org.getIdentifier().add(convert.makeIdentifierFromII(e));
482    for (Element e : cda.getChildren(oo, "addr"))
483      org.getAddress().add(makeDefaultAddress(convert.makeAddressFromAD(e)));
484    for (Element e : cda.getChildren(oo, "telecom")) {
485      ContactPoint cp = convert.makeContactFromTEL(e);
486      if (Utilities.noString(cp.getValue()))
487        cp.setValue("1 (555) 555 5555");
488      org.getTelecom().add(cp);
489    }
490    for (Element e : cda.getChildren(oo, "name"))
491      org.setName(e.getTextContent());
492    saveResource(org);
493    return org;
494  }
495
496  private Address makeDefaultAddress(Address ad) {
497    if (ad == null || ad.isEmpty()) {
498      ad = new Address();
499      ad.addLine("21 Doar road");
500      ad.setCity("Erewhon");
501      ad.setState("CA");
502      ad.setPostalCode("31233");
503    }
504    return ad;
505  }
506
507  private String patchLanguage(String lang) {
508    if (lang.equals("spa"))
509      return "es";
510    if (lang.equals("eng"))
511      return "en";
512    return lang;
513  }
514
515  ///legalAuthenticator/assignedEntity: 2979
516  ///legalAuthenticator/assignedEntity/addr: 2979
517  ///legalAuthenticator/assignedEntity/assignedPerson: 2979
518  ///legalAuthenticator/assignedEntity/id: 2979
519  ///legalAuthenticator/assignedEntity/representedOrganization: 2979
520  ///legalAuthenticator/assignedEntity/representedOrganization/addr: 2979
521  ///legalAuthenticator/assignedEntity/representedOrganization/id: 2979
522  ///legalAuthenticator/assignedEntity/representedOrganization/name: 2979
523  ///legalAuthenticator/assignedEntity/representedOrganization/telecom: 2979
524  ///legalAuthenticator/assignedEntity/telecom: 2979
525
526  private Practitioner makeAuthor(CDAUtilities cda, Convert convert, Element doc, Context context, String id) throws Exception {
527    Element a = cda.getChild(doc, "author");
528    scanSection("Author", a);
529    Practitioner author = processPerformer(cda, convert, context, a, "assignedAuthor", "assignedPerson");
530    context.setAuthorRef(new Reference().setDisplay(author.getUserString("display")).setReference("Practitioner/" + author.getId()));
531    return author;
532  }
533
534  private Practitioner makePerformer(CDAUtilities cda, Convert convert, Context context, Element eperf, String roleName, String entityName) throws Exception {
535    Element ae = cda.getChild(eperf, roleName);
536    Element ap = cda.getChild(ae, entityName);
537
538    StringBuilder b = new StringBuilder();
539
540    Practitioner perf = new Practitioner();
541    perf.setId("performer-" + perfCount);
542    perf.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/pract-daf-dafpract");
543    perfCount++;
544    for (Element e : cda.getChildren(ae, "id")) {
545      Identifier id = convert.makeIdentifierFromII(e);
546      perf.getIdentifier().add(id);
547    }
548
549    for (Element e : cda.getChildren(ap, "name")) {
550      HumanName name = convert.makeNameFromEN(e);
551      perf.addName(name);
552      b.append(NarrativeGenerator.displayHumanName(name));
553      b.append(" ");
554    }
555    for (Element e : cda.getChildren(ae, "addr"))
556      perf.getAddress().add(makeDefaultAddress(convert.makeAddressFromAD(e)));
557    boolean first = true;
558    for (Element e : cda.getChildren(ae, "telecom")) {
559      ContactPoint contact = convert.makeContactFromTEL(e);
560      perf.getTelecom().add(contact);
561      if (!Utilities.noString(contact.getValue())) {
562        if (first) {
563          b.append("(");
564          first = false;
565        } else
566          b.append(" ");
567        b.append(NarrativeGenerator.displayContactPoint(contact));
568      }
569    }
570    if (!first)
571      b.append(")");
572
573//              Element e = cda.getChild(ae, "representedOrganization");
574//              if (e != null)
575//                      perf.addRole().setOrganization(new Reference().setReference("Organization/"+processOrganization(e, cda, convert, context).getId()));
576    perf.setUserData("display", b.toString());
577    return perf;
578  }
579
580  ///serviceEvent/performer/functionCode: 9036
581  private Encounter makeEncounter(CDAUtilities cda, Convert convert, Element doc, Context context, String id) throws Exception {
582    Element co = cda.getChild(doc, "componentOf");
583    Element ee = cda.getChild(co, "encompassingEncounter");
584    scanSection("Encounter", co);
585    Element of = cda.getChild(doc, "documentationOf");
586    Element se = cda.getChild(of, "serviceEvent");
587    scanSection("Encounter", of);
588
589    Encounter enc = new Encounter();
590    enc.setId(id);
591    enc.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/encounter-daf-dafencounter");
592    context.setEncounter(enc);
593    enc.setSubject(context.getSubjectRef());
594
595    for (Element e : cda.getChildren(ee, "id"))
596      enc.getIdentifier().add(convert.makeIdentifierFromII(e));
597    checkGenerateIdentifier(enc.getIdentifier(), enc);
598
599    Period p1 = convert.makePeriodFromIVL(cda.getChild(ee, "effectiveTime"));
600    //          Period p2 = convert.makePeriodFromIVL(cda.getChild(se, "effectiveTime")); // well, what is this?
601    //          if (!Base.compareDeep(p1, p2, false))
602    //                  throw new Error("episode time mismatch: "+NarrativeGenerator.displayPeriod(p1)+" & "+NarrativeGenerator.displayPeriod(p2));
603    enc.setPeriod(p1);
604    if (p1.hasEnd())
605      enc.setStatus(EncounterStatus.FINISHED);
606    else
607      enc.setStatus(EncounterStatus.INPROGRESS);
608    enc.setClass_(context.getEncClass());
609
610    Element dd = cda.getChild(ee, "dischargeDispositionCode");
611    if (dd != null) {
612      enc.setHospitalization(new EncounterHospitalizationComponent());
613      enc.getHospitalization().setDischargeDisposition(convert.makeCodeableConceptFromCD(dd));
614    }
615    for (Element e : cda.getChildren(se, "performer")) {
616      Practitioner p = processPerformer(cda, convert, context, e, "assignedEntity", "assignedPerson");
617      Reference ref = new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display"));
618      if (ref != null)
619        enc.addParticipant().setIndividual(ref);
620    }
621    return enc;
622  }
623
624  private Practitioner processPerformer(CDAUtilities cda, Convert convert, Context context, Element e, String roleName, String entityName) throws Exception {
625    Practitioner perf = makePerformer(cda, convert, context, e, roleName, entityName);
626    if (perf == null)
627      return null;
628
629    Reference ref = null;
630    for (Identifier identifier : perf.getIdentifier()) {
631      String key = keyFor(identifier);
632      if (practitionerCache.containsKey(key))
633        return practitionerCache.get(key);
634    }
635
636    saveResource(perf);
637    for (Identifier identifier : perf.getIdentifier()) {
638      String key = "Practitioner-" + keyFor(identifier);
639      practitionerCache.put(key, perf);
640    }
641    return perf;
642  }
643
644  private String keyFor(Identifier identifier) {
645    return identifier.getSystem() + "||" + identifier.getValue();
646  }
647
648  private void buildNarrative(DomainResource resource, Element child) {
649    if (!Utilities.noString(child.getTextContent())) {
650      XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
651      String s = child.getTextContent().trim();
652      if (Utilities.noString(s))
653        div.addText("No Narrative provided in the source CDA document");
654      else
655        div.addText(s);
656      resource.setText(new Narrative().setStatus(NarrativeStatus.ADDITIONAL).setDiv(div));
657    }
658  }
659
660  private void processProcedureSection(CDAUtilities cda, Convert convert, Element sect, Context context) throws Exception {
661    scanSection("Procedures", sect);
662    ListResource list = new ListResource();
663    list.setId(context.getBaseId() + "-list-procedures");
664    // list.setUserData("profile", "") none?
665    list.setSubject(context.getSubjectRef());
666    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(sect, "code")), null));
667    list.setTitle(cda.getChild(sect, "title").getTextContent());
668    list.setStatus(ListStatus.CURRENT);
669    list.setMode(ListMode.SNAPSHOT);
670    list.setDateElement(context.getNow());
671    list.setSource(context.getAuthorRef());
672    buildNarrative(list, cda.getChild(sect, "text"));
673
674    int i = 0;
675    for (Element c : cda.getChildren(sect, "entry")) {
676      Element p = cda.getChild(c, "procedure");
677      Procedure proc = new Procedure();
678      proc.setId(context.getBaseId() + "-procedure-" + i);
679      proc.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/procedure-daf-dafprocedure");
680      i++;
681      proc.setSubject(context.getSubjectRef());
682      proc.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
683      list.addEntry().setItem(new Reference().setReference("Procedure/" + proc.getId()));
684      proc.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(p, "code")), null));
685      recordProcedureCode(proc.getCode());
686      for (Element e : cda.getChildren(p, "id"))
687        proc.getIdentifier().add(convert.makeIdentifierFromII(e));
688
689      proc.setStatus(determineProcedureStatus(cda.getChild(p, "statusCode")));
690      buildNarrative(proc, cda.getChild(p, "text"));
691      proc.setPerformed(convert.makeDateTimeFromTS(cda.getChild(p, "effectiveTime")));
692
693      for (Element e : cda.getChildren(p, "performer")) {
694        ProcedurePerformerComponent part = proc.addPerformer();
695        Practitioner pp = processPerformer(cda, convert, context, e, "assignedEntity", "assignedPerson");
696        Reference ref = new Reference().setReference("Practitioner/" + pp.getId()).setDisplay(pp.getUserString("display"));
697        part.setActor(ref);
698      }
699      saveResource(proc);
700    }
701    saveResource(list);
702  }
703
704  private CodeableConcept inspectCode(CodeableConcept cc, Coding def) {
705    if (cc != null) {
706      for (Coding c : cc.getCoding()) {
707        if ("http://snomed.info/sct".equals(c.getSystem())) {
708          if ("ASSERTION".equals(c.getCode()))
709            c.setSystem("http://hl7.org/fhir/v3/ActCode");
710        }
711        if ("http://hl7.org/fhir/v3/ActCode".equals(c.getSystem()) && "ASSERTION".equals(c.getCode())) {
712          if (def == null)
713            throw new Error("need a default code");
714          c.setSystem(def.getSystem());
715          c.setVersion(def.getVersion());
716          c.setCode(def.getCode());
717          c.setDisplay(def.getDisplay());
718        }
719      }
720    }
721    return cc;
722  }
723
724  private ProcedureStatus determineProcedureStatus(Element child) {
725    if ("completed".equals(child.getAttribute("code")))
726      return ProcedureStatus.COMPLETED;
727    throw new Error("not done yet: " + child.getAttribute("code"));
728  }
729
730  private void processReasonForEncounter(CDAUtilities cda, Convert convert, Element sect, Context context) throws Exception {
731    scanSection("Reason", sect);
732    context.getEncounter().addReason().setText(cda.getChild(sect, "text").getTextContent());
733  }
734
735  private void processProblemsSection(CDAUtilities cda, Convert convert, Element sect, Context context) throws Exception {
736    scanSection("Problems", sect);
737    ListResource list = new ListResource();
738    list.setId(context.getBaseId() + "-list-problems");
739    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafproblemlist");
740    list.setSubject(context.getSubjectRef());
741    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(sect, "code")), null));
742    list.setTitle(cda.getChild(sect, "title").getTextContent());
743    list.setStatus(ListStatus.CURRENT);
744    list.setMode(ListMode.SNAPSHOT);
745    list.setDateElement(context.getNow());
746    list.setSource(context.getAuthorRef());
747    buildNarrative(list, cda.getChild(sect, "text"));
748
749    int i = 0;
750    for (Element c : cda.getChildren(sect, "entry")) {
751      Element pca = cda.getChild(c, "act"); // problem concern act
752      Condition cond = new Condition();
753      cond.setId(context.getBaseId() + "-problem-" + i);
754      cond.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/condition-daf-dafcondition");
755      i++;
756      cond.setSubject(context.getSubjectRef());
757      cond.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
758      cond.setVerificationStatus(getVerificationStatusFromAct(cda.getChild(pca, "statusCode")));
759
760      cond.setAssertedDateElement(convert.makeDateTimeFromTS(cda.getChild(cda.getChild(pca, "effectiveTime"), "low")));
761
762      boolean found = false;
763      for (Element e : cda.getChildren(pca, "id")) {
764        Identifier id = convert.makeIdentifierFromII(e);
765        cond.getIdentifier().add(id);
766      }
767      if (!found) {
768        list.addEntry().setItem(new Reference().setReference("Condition/" + cond.getId()));
769        for (Element e : cda.getChildren(pca, "performer")) {
770          if (cond.hasAsserter())
771            throw new Error("additional asserter discovered");
772          Practitioner p = processPerformer(cda, convert, context, e, "assignedEntity", "assignedPerson");
773          Reference ref = new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display"));
774          cond.setAsserter(ref);
775        }
776        Element po = cda.getChild(cda.getChild(pca, "entryRelationship"), "observation"); // problem observation
777        cond.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(po, "value")), null));
778        recordConditionCode(cond.getCode());
779        cond.setOnset(convert.makeDateTimeFromTS(cda.getChild(cda.getChild(po, "effectiveTime"), "low")));
780        Element pso = cda.getChild(cda.getChild(po, "entryRelationship"), "observation"); // problem status observation
781        String status = cda.getChild(pso, "value").getAttribute("code");
782        if (status.equals("55561003"))
783          cond.setAbatement(new BooleanType("false"));
784        else
785          throw new Error("unknown status code " + status);
786        saveResource(cond);
787      }
788    }
789    saveResource(list);
790  }
791
792  private ConditionVerificationStatus getVerificationStatusFromAct(Element child) {
793    String s = child.getAttribute("code");
794    if (!"active".equals(s))
795      System.out.println(s);
796    return ConditionVerificationStatus.CONFIRMED;
797  }
798
799  private void processAllergiesSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
800    scanSection("Allergies", section);
801    ListResource list = new ListResource();
802    list.setId(context.getBaseId() + "-list-allergies");
803    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafallergylist");
804    list.setSubject(context.getSubjectRef());
805    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
806    list.setTitle(cda.getChild(section, "title").getTextContent());
807    list.setStatus(ListStatus.CURRENT);
808    list.setDateElement(context.getNow());
809    list.setSource(context.getAuthorRef());
810    list.setMode(ListMode.SNAPSHOT);
811    buildNarrative(list, cda.getChild(section, "text"));
812
813    int i = 0;
814    for (Element c : cda.getChildren(section, "entry")) {
815      Element apa = cda.getChild(c, "act"); // allergy problem act
816      AllergyIntolerance ai = new AllergyIntolerance();
817      ai.setId(context.getBaseId() + "-allergy-" + i);
818      ai.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/allergyintolerance-daf-dafallergyintolerance");
819      i++;
820      ai.setPatient(context.getSubjectRef());
821
822      ai.setAssertedDateElement(convert.makeDateTimeFromTS(cda.getChild(cda.getChild(apa, "effectiveTime"), "low")));
823      boolean found = false;
824      for (Element e : cda.getChildren(apa, "id")) {
825        Identifier id = convert.makeIdentifierFromII(e);
826        ai.getIdentifier().add(id);
827      }
828      if (!found) {
829        list.addEntry().setItem(new Reference().setReference("AllergyIntolerance/" + ai.getId()));
830
831        Element ao = cda.getChild(cda.getChild(apa, "entryRelationship"), "observation"); // allergy observation
832        if (!cda.getChild(ao, "value").getAttribute("code").equals("419511003"))
833          throw new Error("unexpected code");
834        // nothing....
835
836        // no allergy status observation
837        List<Element> reactions = cda.getChildren(ao, "entryRelationship");
838        Element pe = cda.getChild(cda.getChild(cda.getChild(ao, "participant"), "participantRole"), "playingEntity");
839        Element pec = cda.getChild(pe, "code");
840        if (pec == null || !Utilities.noString(pec.getAttribute("nullFlavor"))) {
841          String n = cda.getChild(pe, "name").getTextContent();
842          //                            if (n.contains("No Known Drug Allergies") && reactions.isEmpty())
843          //                                    ai.setSubstance(new CodeableConcept().setText(n)); // todo: what do with this?
844          //                            else
845          ai.setCode(new CodeableConcept().setText(n));
846        } else
847          ai.setCode(inspectCode(convert.makeCodeableConceptFromCD(pec), null));
848        recordAllergyCode(ai.getCode());
849        if (!reactions.isEmpty()) {
850          AllergyIntoleranceReactionComponent aie = ai.addReaction();
851          for (Element er : reactions) {
852            Element ro = cda.getChild(er, "observation");
853            aie.addManifestation(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(ro, "value")), null));
854          }
855        }
856
857        saveResource(ai);
858      }
859    }
860    saveResource(list);
861  }
862
863  private void processVitalSignsSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
864    scanSection("Vital Signs", section);
865    ListResource list = new ListResource();
866    list.setId(context.getBaseId() + "-list-vitalsigns");
867    //. list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafproblemlist"); no list
868    list.setSubject(context.getSubjectRef());
869    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
870    list.setTitle(cda.getChild(section, "title").getTextContent());
871    list.setStatus(ListStatus.CURRENT);
872    list.setMode(ListMode.SNAPSHOT);
873    list.setDateElement(context.getNow());
874    list.setSource(context.getAuthorRef());
875    buildNarrative(list, cda.getChild(section, "text"));
876
877    int i = 0;
878    for (Element c : cda.getChildren(section, "entry")) {
879      Element org = cda.getChild(c, "organizer"); // problem concern act
880      for (Element oc : cda.getChildren(org, "component")) {
881        Element o = cda.getChild(oc, "observation"); // problem concern act
882        Observation obs = new Observation();
883        obs.setId(context.getBaseId() + "-vitals-" + i);
884        obs.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-vitalsigns-dafvitalsigns");
885        i++;
886        obs.setSubject(context.getSubjectRef());
887        obs.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
888        obs.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(o, "code")), null));
889
890        boolean found = false;
891        for (Element e : cda.getChildren(o, "id")) {
892          Identifier id = convert.makeIdentifierFromII(e);
893          obs.getIdentifier().add(id);
894        }
895
896        if (!found) {
897          list.addEntry().setItem(new Reference().setReference("Observation/" + obs.getId()));
898          obs.setStatus(ObservationStatus.FINAL);
899          obs.setEffective(convert.makeDateTimeFromTS(cda.getChild(o, "effectiveTime")));
900          String v = cda.getChild(o, "value").getAttribute("value");
901          if (!Utilities.isDecimal(v, true)) {
902            obs.setDataAbsentReason(inspectCode(new CodeableConcept().setText(v), null));
903          } else
904            obs.setValue(convert.makeQuantityFromPQ(cda.getChild(o, "value")));
905          saveResource(obs, "-vs");
906        }
907      }
908    }
909    saveResource(list, "-vs");
910  }
911
912  private void processResultsSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
913    scanSection("Results", section);
914
915    ListResource list = new ListResource();
916    list.setId(context.getBaseId() + "-list-results");
917    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafresultlist");
918    list.setSubject(context.getSubjectRef());
919    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
920    list.setTitle(cda.getChild(section, "title").getTextContent());
921    list.setStatus(ListStatus.CURRENT);
922    list.setMode(ListMode.SNAPSHOT);
923    list.setDateElement(context.getNow());
924    list.setSource(context.getAuthorRef());
925    buildNarrative(list, cda.getChild(section, "text"));
926
927    context.setObsId(0);
928    for (Element c : cda.getChildren(section, "entry")) {
929      Element org = cda.getChild(c, "organizer");
930      if (org != null) {
931        Observation panel = new Observation();
932        panel.setId(context.getBaseId() + "-results-" + context.getObsId());
933        panel.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-results-dafresultobspanel");
934        context.setObsId(context.getObsId() + 1);
935        panel.setSubject(context.getSubjectRef());
936        panel.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
937        panel.setStatus(ObservationStatus.FINAL);
938        boolean found = false;
939        for (Element e : cda.getChildren(org, "id")) {
940          Identifier id = convert.makeIdentifierFromII(e);
941          panel.getIdentifier().add(id);
942        }
943        if (!found) {
944          list.addEntry().setItem(new Reference().setReference("Observation/" + panel.getId()));
945
946          panel.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(org, "code")), null));
947          for (Element comp : cda.getChildren(org, "component")) {
948            Observation obs = processObservation(cda, convert, context, cda.getChild(comp, "observation"));
949            panel.addRelated().setType(ObservationRelationshipType.HASMEMBER).setTarget(new Reference().setReference("Observation/" + obs.getId()));
950            if (!panel.hasEffective())
951              panel.setEffective(obs.getEffective());
952            else {
953              if (!Base.compareDeep(panel.getEffective(), obs.getEffective(), false)) {
954                Period p = panel.getEffective() instanceof Period ? panel.getEffectivePeriod() : new Period().setStartElement(panel.getEffectiveDateTimeType()).setEndElement(panel.getEffectiveDateTimeType());
955                if (p.getStartElement().after(obs.getEffectiveDateTimeType()))
956                  p.setStartElement(obs.getEffectiveDateTimeType());
957                if (p.getEndElement().before(obs.getEffectiveDateTimeType()))
958                  p.setEndElement(obs.getEffectiveDateTimeType());
959                panel.setEffective(p);
960              }
961            }
962
963          }
964          saveResource(panel, "-res");
965        }
966      }
967      Element o = cda.getChild(c, "observation");
968      if (o != null) {
969        Observation obs = processObservation(cda, convert, context, o);
970        list.addEntry().setItem(new Reference().setReference("Observation/" + obs.getId()));
971      }
972    }
973    saveResource(list, "-res");
974  }
975
976  private Observation processObservation(CDAUtilities cda, Convert convert, Context context, Element o) throws Exception {
977    Observation obs = new Observation();
978    obs.setId(context.getBaseId() + "-results-" + context.getObsId());
979    context.setObsId(context.getObsId() + 1);
980    obs.setSubject(context.getSubjectRef());
981    obs.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
982    obs.setStatus(ObservationStatus.FINAL);
983    obs.setEffective(convert.makeDateTimeFromTS(cda.getChild(o, "effectiveTime")));
984    obs.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(o, "code")), null));
985    obs.setInterpretation(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(o, "interpretationCode")), null));
986    Element rr = cda.getChild(o, "referenceRange");
987    if (rr != null)
988      obs.addReferenceRange().setText(cda.getChild(cda.getChild(rr, "observationRange"), "text").getTextContent());
989
990    Element v = cda.getChild(o, "value");
991    String type = v.getAttribute("xsi:type");
992    if ("ST".equals(type)) {
993      obs.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-results-dafresultobsother");
994      obs.setValue(new StringType(v.getTextContent()));
995    } else if ("CD".equals(type)) {
996      obs.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-results-dafresultobscode");
997      obs.setValue(inspectCode(convert.makeCodeableConceptFromCD(v), null));
998    } else if ("PQ".equals(type)) {
999      obs.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-results-dafresultobsquantity");
1000      String va = cda.getChild(o, "value").getAttribute("value");
1001      if (!Utilities.isDecimal(va, true)) {
1002        obs.setDataAbsentReason(inspectCode(new CodeableConcept().setText(va), null));
1003      } else
1004        obs.setValue(convert.makeQuantityFromPQ(cda.getChild(o, "value"), null));
1005    } else
1006      throw new Exception("Unknown type '" + type + "'");
1007
1008    for (Element e : cda.getChildren(o, "id")) {
1009      Identifier id = convert.makeIdentifierFromII(e);
1010      obs.getIdentifier().add(id);
1011    }
1012    saveResource(obs, "-gen");
1013    return obs;
1014  }
1015
1016  private void processSocialHistorySection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
1017    scanSection("Social History", section);
1018    int i = 0;
1019    for (Element c : cda.getChildren(section, "entry")) {
1020      Element o = cda.getChild(c, "observation");
1021      Observation obs = new Observation();
1022      obs.setId(context.getBaseId() + "-smoking-" + (i == 0 ? "" : Integer.toString(i)));
1023      obs.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/observation-daf-smokingstatus-dafsmokingstatus");
1024      i++;
1025      obs.setSubject(context.getSubjectRef());
1026      obs.setContext(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
1027      obs.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(o, "code")), new Coding().setSystem("http://loinc.org").setCode("72166-2")));
1028
1029      boolean found = false;
1030      for (Element e : cda.getChildren(o, "id")) {
1031        Identifier id = convert.makeIdentifierFromII(e);
1032        obs.getIdentifier().add(convert.makeIdentifierFromII(e));
1033      }
1034      if (!found) {
1035        obs.setStatus(ObservationStatus.FINAL);
1036        obs.setEffective(convert.makeDateTimeFromTS(cda.getChild(o, "effectiveTime")));
1037        obs.setValue(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(o, "value")), null));
1038        saveResource(obs, "-sh");
1039      }
1040    }
1041  }
1042
1043  private void processMedicationsSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
1044    scanSection("Medications", section);
1045    ListResource list = new ListResource();
1046    list.setId(context.getBaseId() + "-list-medications");
1047    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafmedicationlist");
1048    list.setSubject(context.getSubjectRef());
1049    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
1050    list.setTitle(cda.getChild(section, "title").getTextContent());
1051    list.setStatus(ListStatus.CURRENT);
1052    list.setMode(ListMode.SNAPSHOT);
1053    list.setDateElement(context.getNow());
1054    list.setSource(context.getAuthorRef());
1055    buildNarrative(list, cda.getChild(section, "text"));
1056
1057    int i = 0;
1058    for (Element c : cda.getChildren(section, "entry")) {
1059      Element sa = cda.getChild(c, "substanceAdministration"); // allergy problem act
1060      MedicationStatement ms = new MedicationStatement();
1061      ms.setId(context.getBaseId() + "-medication-" + i);
1062      ms.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/medicationstatement-daf-dafmedicationstatement");
1063      i++;
1064      ms.setSubject(context.getSubjectRef());
1065
1066      boolean found = false;
1067      for (Element e : cda.getChildren(sa, "id")) {
1068        Identifier id = convert.makeIdentifierFromII(e);
1069        ms.getIdentifier().add(id);
1070      }
1071      if (!found) {
1072        ms.setStatus(MedicationStatementStatus.COMPLETED);
1073        list.addEntry().setItem(new Reference().setReference("MedicationStatement/" + ms.getId()));
1074
1075        Element mm = cda.getChild(cda.getChild(cda.getChild(sa, "consumable"), "manufacturedProduct"), "manufacturedMaterial"); // allergy observation
1076        ms.setMedication(new Reference().setReference("#med"));
1077        Medication med = new Medication();
1078        med.setId("med");
1079        med.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(mm, "code")), null));
1080        ms.getContained().add(med);
1081        Dosage dosage = ms.addDosage();
1082        Element qty = cda.getChild(sa, "doseQuantity"); // allergy observation
1083        try {
1084          if (cda.getChild(qty, "low") != null) {
1085            // todo: this is not correct?
1086            dosage.getExtension().add(new Extension().setUrl("http://healthintersections.com.au/fhir/extensions/medication-statement-range").setValue(convert.makeRangeFromIVLPQ(qty)));
1087          } else {
1088            dosage.setDose(convert.makeQuantityFromPQ(qty));
1089          }
1090        } catch (Exception e) {
1091          System.out.println("  invalid dose quantity '" + qty.getAttribute("value") + " " + qty.getAttribute("unit") + "' (" + e.getClass().getName() + ") in " + context.getBaseId());
1092        }
1093        dosage.setRoute(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(sa, "routeCode")), null));
1094        Type t = convert.makeSomethingFromGTS(cda.getChildren(sa, "effectiveTime"));
1095        if (t instanceof Timing) {
1096          dosage.setTiming((Timing) t);
1097          if (dosage.getTiming().hasRepeat() && dosage.getTiming().getRepeat().hasBounds())
1098            ms.setEffective(dosage.getTiming().getRepeat().getBounds());
1099        } else if (t instanceof Period)
1100          ms.setEffective(t);
1101        else
1102          throw new Exception("Undecided how to handle " + t.getClass().getName());
1103
1104        for (Element e : cda.getChildren(sa, "author")) {
1105          if (ms.hasInformationSource())
1106            throw new Error("additional author discovered");
1107          Practitioner p = processPerformer(cda, convert, context, e, "assignedAuthor", "assignedPerson");
1108          Reference ref = new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display"));
1109          ms.setInformationSource(ref);
1110          ms.setDateAssertedElement(convert.makeDateTimeFromTS(cda.getChild(e, "time")));
1111        }
1112        saveResource(ms);
1113      }
1114    }
1115    saveResource(list);
1116  }
1117
1118  private void processEncountersSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
1119    scanSection("Encounters", section);
1120    ListResource list = new ListResource();
1121    list.setId(context.getBaseId() + "-list-encounters");
1122    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafencounterlist");
1123    list.setSubject(context.getSubjectRef());
1124    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
1125    list.setTitle(cda.getChild(section, "title").getTextContent());
1126    list.setStatus(ListStatus.CURRENT);
1127    list.setMode(ListMode.SNAPSHOT);
1128    list.setDateElement(context.getNow());
1129    list.setSource(context.getAuthorRef());
1130    buildNarrative(list, cda.getChild(section, "text"));
1131
1132    int i = 0;
1133    for (Element c : cda.getChildren(section, "entry")) {
1134      Element ee = cda.getChild(c, "encounter"); // allergy problem act
1135      Encounter enc = new Encounter();
1136      enc.setId(context.getBaseId() + "-encounter-" + i);
1137      enc.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/encounter-daf-dafencounter");
1138      i++;
1139      enc.setSubject(context.getSubjectRef());
1140      list.addEntry().setItem(new Reference().setReference("Encounter/" + enc.getId()));
1141
1142      for (Element e : cda.getChildren(ee, "id"))
1143        enc.getIdentifier().add(convert.makeIdentifierFromII(e));
1144      checkGenerateIdentifier(enc.getIdentifier(), enc);
1145
1146
1147      enc.setPeriod(convert.makePeriodFromIVL(cda.getChild(ee, "effectiveTime")));
1148      if (enc.getPeriod().hasEnd())
1149        enc.setStatus(EncounterStatus.FINISHED);
1150      else
1151        enc.setStatus(EncounterStatus.INPROGRESS);
1152
1153      if (cda.getChild(ee, "text") != null)
1154        enc.setClass_(convertTextToCoding(cda.getChild(ee, "text").getTextContent().trim()));
1155      else
1156        enc.setClass_(null); // todo: fix this
1157
1158      CodeableConcept type = inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(ee, "code")), null);
1159      enc.addType(type);
1160
1161      for (Element e : cda.getChildren(ee, "performer")) {
1162        Practitioner p = processPerformer(cda, convert, context, e, "assignedEntity", "assignedPerson");
1163        Reference ref = new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display"));
1164        enc.addParticipant().setIndividual(ref).setPeriod(convert.makePeriodFromIVL(cda.getChild(e, "time")));
1165      }
1166      enc.addLocation().setLocation(new Reference().setReference("#loc"));
1167      Location loc = new Location();
1168      loc.setId("loc");
1169      Element pr = cda.getChild(cda.getChild(ee, "participant"), "participantRole");
1170      loc.setName(cda.getChild(cda.getChild(pr, "playingEntity"), "name").getTextContent());
1171      loc.setType(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(pr, "code")), null));
1172      enc.getContained().add(loc);
1173      saveResource(enc);
1174    }
1175    saveResource(list);
1176  }
1177
1178  private Coding convertTextToCoding(String v) {
1179    v = v.toLowerCase();
1180    if (v.equals("inpatient"))
1181      return new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("IMP");
1182    if (v.equals("emergency department") || v.equals("emergency department admit decision"))
1183      return new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("EMER");
1184    if (v.equals("x-ray exam"))
1185      return new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("AMB");
1186    if (v.equals("outpatient"))
1187      return new Coding().setSystem("http://hl7.org/fhir/v3/ActCode").setCode("AMB");
1188    throw new Error("unknown encounter type " + v);
1189  }
1190
1191  private void processImmunizationsSection(CDAUtilities cda, Convert convert, Element section, Context context) throws Exception {
1192    scanSection("Immunizations", section);
1193    ListResource list = new ListResource();
1194    list.setId(context.getBaseId() + "-list-immunizations");
1195    list.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/list-daf-dafimmunizationlist");
1196    list.setSubject(context.getSubjectRef());
1197    list.setCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")), null));
1198    list.setTitle(cda.getChild(section, "title").getTextContent());
1199    list.setStatus(ListStatus.CURRENT);
1200    list.setMode(ListMode.SNAPSHOT);
1201    list.setDateElement(context.getNow());
1202    list.setSource(context.getAuthorRef());
1203    buildNarrative(list, cda.getChild(section, "text"));
1204
1205    int i = 0;
1206    for (Element c : cda.getChildren(section, "entry")) {
1207      Element sa = cda.getChild(c, "substanceAdministration"); // allergy problem act
1208      Immunization imm = new Immunization();
1209      imm.setId(context.getBaseId() + "-immunization-" + i);
1210      imm.setUserData("profile", "http://hl7.org/fhir/StructureDefinition/immunization-daf-dafimmunization");
1211      i++;
1212      imm.setPatient(context.getSubjectRef());
1213      imm.setEncounter(new Reference().setReference("Encounter/" + context.getEncounter().getId()));
1214      imm.setNotGiven("true".equals(sa.getAttribute("negationInd")));
1215      imm.setStatus(convertImmunizationStatus(cda.getChild(sa, "statusCode")));
1216      boolean found = false;
1217      for (Element e : cda.getChildren(sa, "id")) {
1218        Identifier id = convert.makeIdentifierFromII(e);
1219        imm.getIdentifier().add(id);
1220      }
1221      if (!found) {
1222        list.addEntry().setItem(new Reference().setReference("Immunization/" + imm.getId()));
1223
1224        imm.setDateElement(convert.makeDateTimeFromTS(cda.getChild(cda.getChild(sa, "effectiveTime"), "low")));
1225        if (imm.getNotGiven()) {
1226          Element reason = cda.getChild(cda.getChildByAttribute(sa, "entryRelationship", "typeCode", "RSON"), "observation");
1227          imm.setExplanation(new ImmunizationExplanationComponent());
1228          imm.getExplanation().addReasonNotGiven(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(reason, "code")), null));
1229        }
1230        Element mm = cda.getChild(cda.getChild(cda.getChild(sa, "consumable"), "manufacturedProduct"), "manufacturedMaterial");
1231        imm.setVaccineCode(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(mm, "code")), null));
1232        imm.setRoute(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(sa, "routeCode")), null));
1233        if (cda.getChild(mm, "lotNumberText") != null)
1234          imm.setLotNumber(cda.getChild(mm, "lotNumberText").getTextContent());
1235        Element mr = cda.getChild(cda.getChild(cda.getChild(sa, "consumable"), "manufacturedProduct"), "manufacturerOrganization");
1236        if (mr != null)
1237          imm.setManufacturer(new Reference().setDisplay(cda.getChild(mr, "name").getTextContent()));
1238
1239        // the problem with this is that you can't have just a dose sequence number
1240        //                      Element subject = cda.getChild(cda.getChildByAttribute(sa, "entryRelationship", "typeCode", "SUBJ"), "observation");
1241        //                      if (subject != null)
1242        //                              imm.addVaccinationProtocol().setDoseSequence(Integer.parseInt(cda.getChild(subject, "value").getAttribute("value")));
1243
1244        boolean hasprf = false;
1245        for (Element e : cda.getChildren(sa, "performer")) {
1246          if (imm.hasPractitioner())
1247            throw new Error("additional performer discovered");
1248          Practitioner p = processPerformer(cda, convert, context, e, "assignedEntity", "assignedPerson");
1249          Reference ref = new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display"));
1250          imm.addPractitioner().setActor(ref).setRole(new org.hl7.fhir.dstu3.model.CodeableConcept().addCoding(new Coding().setSystem("http://hl7.org/fhir/v2/0443").setCode("AP")));
1251          hasprf = true;
1252        }
1253        imm.setPrimarySource(hasprf);
1254        saveResource(imm);
1255      }
1256    }
1257    saveResource(list);
1258  }
1259
1260  private ImmunizationStatus convertImmunizationStatus(Element child) {
1261    String s = child.getAttribute("code");
1262    if (s.equals("completed"))
1263      return ImmunizationStatus.COMPLETED;
1264    throw new Error("Unexpected status " + s);
1265  }
1266
1267  //  /informationRecipient: 2979
1268  //  /informationRecipient/intendedRecipient: 2979
1269  //  /informationRecipient/intendedRecipient/addr: 2979
1270  //  /informationRecipient/intendedRecipient/informationRecipient: 2979
1271  //  /informationRecipient/intendedRecipient/informationRecipient/name: 2979
1272  //  /informationRecipient/intendedRecipient/receivedOrganization: 2979
1273  //  /informationRecipient/intendedRecipient/receivedOrganization/addr: 2979
1274  //  /informationRecipient/intendedRecipient/receivedOrganization/id: 2979
1275  //  /informationRecipient/intendedRecipient/receivedOrganization/name: 2979
1276  //  /informationRecipient/intendedRecipient/receivedOrganization/telecom: 2979
1277
1278  private void makeBinary(String sourceFolder, String filename, Context context) throws Exception {
1279    Binary binary = new Binary();
1280    binary.setId(context.getBaseId() + "-binary");
1281    binary.setContentType("application/hl7-v3+xml");
1282    binary.setContent(IOUtils.toByteArray(new FileInputStream(Utilities.path(sourceFolder, filename))));
1283    saveResource(binary);
1284  }
1285
1286  private void makeDocumentReference(CDAUtilities cda, Convert convert, Element doc, Context context) throws Exception {
1287    scanSection("document", doc);
1288    DocumentReference ref = new DocumentReference();
1289    ref.setId(context.getBaseId() + "-document");
1290    ref.setMasterIdentifier(convert.makeIdentifierFromII(cda.getChild(doc, "id")));
1291    ref.setSubject(context.getSubjectRef());
1292    ref.setType(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(doc, "code")), null));
1293    ref.addAuthor(context.getAuthorRef());
1294    ref.setCreatedElement(convert.makeDateTimeFromTS(cda.getChild(doc, "effectiveTime")));
1295    ref.setIndexedElement(InstantType.now());
1296    ref.setStatus(DocumentReferenceStatus.CURRENT);
1297    ref.addSecurityLabel(inspectCode(convert.makeCodeableConceptFromCD(cda.getChild(doc, "confidentialityCode")), null));
1298    DocumentReferenceContentComponent cnt = ref.addContent();
1299    cnt.getAttachment().setContentType("application/hl7-v3+xml").setUrl("Binary/" + context.getBaseId()).setLanguage(convertLanguage(cda.getChild(doc, "language")));
1300    //          for (Element ti : cda.getChildren(doc, "templateId"))
1301    //                  cnt.addFormat().setSystem("urn:oid:1.3.6.1.4.1.19376.1.2.3").setCode(value)("urn:oid:"+ti.getAttribute("root"));
1302    ref.setContext(new DocumentReferenceContextComponent());
1303    ref.getContext().setPeriod(convert.makePeriodFromIVL(cda.getChild(cda.getChild(doc, "serviceEvent"), "effectiveTime")));
1304    for (CodeableConcept cc : context.getEncounter().getType())
1305      ref.getContext().addEvent(cc);
1306    ref.setDescription(cda.getChild(doc, "title").getTextContent());
1307    ref.setCustodian(new Reference().setReference("Organization/" + processOrganization(cda.getDescendent(doc, "custodian/assignedCustodian/representedCustodianOrganization"), cda, convert, context).getId()));
1308    Practitioner p = processPerformer(cda, convert, context, cda.getChild(doc, "legalAuthenticator"), "assignedEntity", "assignedPerson");
1309    ref.setAuthenticator(new Reference().setReference("Practitioner/" + p.getId()).setDisplay(p.getUserString("display")));
1310    saveResource(ref);
1311  }
1312
1313  private String convertLanguage(Element child) {
1314    if (child == null)
1315      return null;
1316    return child.getAttribute("code");
1317  }
1318
1319  private CodeableConcept makeClassCode(CodeableConcept type, DocumentReference ref) throws Exception {
1320    CodeableConcept res = new CodeableConcept();
1321    String cs = type.getCoding().get(0).getCode();
1322    if (cs.equals("18842-5") || cs.equals("34133-9"))
1323      return type;
1324    else if (cs.equals("34111-5")) {
1325      ref.getFormatCommentsPre().add("The underlying CDA document has the code '34111-5: Evaluation and Management Note' which is incorrect (wrong display/code combination). The type has been preserved even though it's wrong");
1326      res.addCoding().setSystem("http://loinc.org").setCode("34109-9").setDisplay("Evaluation and management note");
1327    }
1328    //          else if (cs.equals("34111-5") || cs.equals("5666"))
1329    //            res.addCoding().setSystem("http://loinc.org").setCode("LP173418-7").setDisplay("Note");
1330    else
1331      throw new Exception("Uncategorised document type code: " + cs + ": " + type.getCoding().get(0).getDisplay());
1332    return res;
1333
1334  }
1335
1336  private void recordProcedureCode(CodeableConcept code) {
1337    for (Coding c : code.getCoding()) {
1338      count(c, procCodes);
1339    }
1340  }
1341
1342  private void count(Coding c, Map<String, Integer> map) {
1343    String s = c.getSystem() + "::" + c.getCode();
1344    if (map.containsKey(s))
1345      map.put(s, map.get(s) + 1);
1346    else
1347      map.put(s, 1);
1348  }
1349
1350  private void recordConditionCode(CodeableConcept code) {
1351    for (Coding c : code.getCoding()) {
1352      count(c, condCodes);
1353    }
1354  }
1355
1356  private void recordAllergyCode(CodeableConcept code) {
1357    for (Coding c : code.getCoding()) {
1358      count(c, allergyCodes);
1359    }
1360  }
1361
1362  private void dumpCodes() {
1363    dump("Procedure Codes", procCodes);
1364    dump("Condition Codes", condCodes);
1365    dump("Allergy Codes", allergyCodes);
1366  }
1367
1368  private void dump(String string, Map<String, Integer> map) {
1369    System.out.println(string);
1370    System.out.println();
1371    for (String s : map.keySet()) {
1372      System.out.println(s + ": " + map.get(s));
1373    }
1374    System.out.println();
1375    System.out.println();
1376  }
1377
1378}