001package org.hl7.fhir.convertors.misc.ccda;
002
003
004
005
006
007/*
008  Copyright (c) 2011+, HL7, Inc.
009  All rights reserved.
010
011  Redistribution and use in source and binary forms, with or without modification,
012  are permitted provided that the following conditions are met:
013
014 * Redistributions of source code must retain the above copyright notice, this
015     list of conditions and the following disclaimer.
016 * Redistributions in binary form must reproduce the above copyright notice,
017     this list of conditions and the following disclaimer in the documentation
018     and/or other materials provided with the distribution.
019 * Neither the name of HL7 nor the names of its contributors may be used to
020     endorse or promote products derived from this software without specific
021     prior written permission.
022
023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
026  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
027  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
028  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
029  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
030  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
031  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
032  POSSIBILITY OF SUCH DAMAGE.
033
034 */
035
036
037import org.fhir.ucum.UcumService;
038import org.hl7.fhir.convertors.misc.CDAUtilities;
039import org.hl7.fhir.convertors.misc.Convert;
040import org.hl7.fhir.dstu3.context.IWorkerContext;
041import org.hl7.fhir.dstu3.model.*;
042import org.hl7.fhir.dstu3.model.AllergyIntolerance.*;
043import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
044import org.hl7.fhir.dstu3.model.Composition.CompositionAttestationMode;
045import org.hl7.fhir.dstu3.model.Composition.CompositionAttesterComponent;
046import org.hl7.fhir.dstu3.model.Composition.DocumentConfidentiality;
047import org.hl7.fhir.dstu3.model.Composition.SectionComponent;
048import org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent;
049import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
050import org.hl7.fhir.dstu3.model.Observation.ObservationRelatedComponent;
051import org.hl7.fhir.dstu3.model.Observation.ObservationRelationshipType;
052import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
053import org.hl7.fhir.dstu3.model.Procedure.ProcedurePerformerComponent;
054import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
055import org.hl7.fhir.dstu3.utils.ToolingExtensions;
056import org.w3c.dom.Element;
057
058import java.io.InputStream;
059import java.util.HashMap;
060import java.util.List;
061import java.util.Map;
062import java.util.UUID;
063
064/**
065 * Advance Directives Section 42348-3 :
066 * Allergies, Adverse Reactions, Alerts Section 48765-2 :     List(AlleryIntolerance)        processAdverseReactionsSection
067 * Anesthesia Section 59774-0 :
068 * Assessment Section 51848-0 :
069 * Assessment and Plan Section 51487-2 :
070 * Chief Complaint Section 10154-3 :
071 * Chief Complaint and Reason for Visit Section 46239-0 :
072 * Complications 55109-3:
073 * DICOM Object Catalog Section - DCM 121181 :
074 * Discharge Diet Section 42344-2 :
075 * Encounters Section 46240-8:
076 * Family History Section 10157-6 :
077 * Findings Section 18782-3 :
078 * Functional Status Section 47420-5 :
079 * General Status Section 10210-3 :
080 * History of Past Illness Section 11348-0 :
081 * History of Present Illness Section 10164-2 :
082 * Hospital Admission Diagnosis Section 46241-6 :
083 * Hospital Consultations Section 18841-7 :
084 * Hospital Course Section 8648-8 :
085 * Hospital Discharge Diagnosis Section 11535-2 :
086 * Hospital Discharge Instructions Section :
087 * Hospital Discharge Medications Section (entries optional) 10183-2 :
088 * Hospital Discharge Physical Section 10184-0 :
089 * Hospital Discharge Studies Summary Section 11493-4 :
090 * Immunizations Section 11369-6 :
091 * Interventions Section 62387-6 :
092 * Medical Equipment Section 46264-8 :
093 * Medical (General) History Section 11329-0 :
094 * Medications Section 10160-0 :
095 * Medications Administered Section 29549-3 :
096 * Objective Section 61149-1 :
097 * Operative Note Fluid Section 10216-0 :
098 * Operative Note Surgical Procedure Section 10223-6 :
099 * Payers Section 48768-6 :
100 * Physical Exam Section 29545-1 :
101 * Plan of Care Section 18776-5 :
102 * Planned Procedure Section 59772-:
103 * Postoperative Diagnosis Section 10218-6 :
104 * Postprocedure Diagnosis Section 59769-0 :
105 * Preoperative Diagnosis Section 10219-4 :
106 * Problem Section 11450-4 :
107 * Procedure Description Section 29554-3:
108 * Procedure Disposition Section 59775-7 :
109 * Procedure Estimated Blood Loss Section 59770-8 :
110 * Procedure Findings Section 59776-5 :
111 * Procedure Implants Section 59771-6 :
112 * Procedure Indications Section 59768-2 :
113 * Procedure Specimens Taken Section 59773-2 :
114 * Procedures Section 47519-4 :                          List (Procedure)                       processProceduresSection
115 * Reason for Referral Section 42349-1 :
116 * Reason for Visit Section 29299-5 :
117 * Results Section 30954-2 :
118 * Review of Systems Section 10187-3 :
119 * Social History Section 29762-2 :                      List (Observation)                     processSocialHistorySection
120 * Subjective Section 61150-9:
121 * Surgical Drains Section 11537-8 :
122 * Vital Signs Section 8716-3 :                          List(Observation)                      processVitalSignsSection
123 * <p>
124 * <p>
125 * MU Sections:
126 * Allergies/Adverse Reactions
127 * Problems
128 * Encounters
129 * Medications
130 * Results
131 * Vital Signs
132 * Procedures
133 * Immunizations
134 * Reason for Referral
135 * Hospital Discharge Instructions
136 * Functional Status
137 * Plan of Care
138 * Hospital Discharge Medication
139 * All of General Header
140 *
141 * @author Grahame
142 */
143public class CCDAConverter {
144
145  protected CDAUtilities cda;
146  protected Element doc;
147  protected Convert convert;
148  protected Bundle feed;
149  protected Composition composition;
150  protected Map<String, Practitioner> practitionerCache = new HashMap<String, Practitioner>();
151  protected Integer refCounter = 0;
152  protected UcumService ucumSvc;
153  protected IWorkerContext context;
154
155  public CCDAConverter(UcumService ucumSvc, IWorkerContext context) {
156    super();
157    this.ucumSvc = ucumSvc;
158    this.context = context;
159  }
160
161  public Bundle convert(InputStream stream) throws Exception {
162
163    cda = new CDAUtilities(stream);
164    doc = cda.getElement();
165    cda.checkTemplateId(doc, "2.16.840.1.113883.10.20.22.1.1");
166    convert = new Convert(cda, ucumSvc, "Z");
167
168    // check it's a CDA/CCD
169    feed = new Bundle();
170    feed.setMeta(new Meta().setLastUpdatedElement(InstantType.now()));
171    feed.setId(makeUUIDReference());
172    feed.getMeta().getTag().add(new Coding()); // todo-bundle  ("http://hl7.org/fhir/tag", "http://hl7.org/fhir/tag/document", "Document"));
173
174    // process the header
175    makeDocument();
176    composition.setSubject(Factory.makeReference(makeSubject()));
177    for (Element e : cda.getChildren(doc, "author"))
178      composition.getAuthor().add(Factory.makeReference(makeAuthor(e)));
179    // todo: data enterer & informant goes in provenance
180    composition.setCustodian(Factory.makeReference(makeOrganization(
181      cda.getDescendent(doc, "custodian/assignedCustodian/representedCustodianOrganization"), "Custodian")));
182    // todo: informationRecipient
183    for (Element e : cda.getChildren(doc, "legalAuthenticator"))
184      composition.getAttester().add(makeAttester(e, CompositionAttestationMode.LEGAL, "Legal Authenticator"));
185    for (Element e : cda.getChildren(doc, "authenticator"))
186      composition.getAttester().add(makeAttester(e, CompositionAttestationMode.PROFESSIONAL, "Authenticator"));
187
188    // process the contents
189    // we do this by section - keep the original section order
190    Element body = cda.getDescendent(doc, "component/structuredBody");
191    processComponentSections(composition.getSection(), body);
192    return feed;
193  }
194
195  protected String addReference(DomainResource r, String title, String id) throws Exception {
196    if (r.getText() == null)
197      r.setText(new Narrative());
198    if (r.getText().getDiv() == null) {
199      r.getText().setStatus(NarrativeStatus.GENERATED);
200      new NarrativeGenerator("", "", context).generate(r);
201    }
202    r.setMeta(new Meta().setLastUpdatedElement(InstantType.now()));
203    r.setId(id);
204    feed.getEntry().add(new BundleEntryComponent().setResource(r));
205    return id;
206  }
207
208  protected void makeDocument() throws Exception {
209    composition = (Composition) ResourceFactory.createResource("Composition");
210    addReference(composition, "Composition", makeUUIDReference());
211
212    Element title = cda.getChild(doc, "title");
213    composition.setTitle(title.getTextContent());
214
215    if (cda.getChild(doc, "setId") != null) {
216      feed.setId(convert.makeURIfromII(cda.getChild(doc, "id")));
217      composition.setIdentifier(convert.makeIdentifierFromII(cda.getChild(doc, "setId")));
218    } else
219      composition.setIdentifier(convert.makeIdentifierFromII(cda.getChild(doc, "id"))); // well, we fall back to id
220
221    composition.setDateElement(convert.makeDateTimeFromTS(cda.getChild(doc, "effectiveTime")));
222    composition.setType(convert.makeCodeableConceptFromCD(cda.getChild(doc, "code")));
223    composition.setConfidentiality(convertConfidentiality(cda.getChild(doc, "confidentialityCode")));
224    if (cda.getChild(doc, "confidentialityCode") != null)
225      composition.setLanguage(cda.getChild(doc, "confidentialityCode").getAttribute("value")); // todo - fix streaming for this
226
227    Element ee = cda.getChild(doc, "componentOf");
228    if (ee != null)
229      ee = cda.getChild(ee, "encompassingEncounter");
230    if (ee != null) {
231      Encounter visit = new Encounter();
232      for (Element e : cda.getChildren(ee, "id"))
233        visit.getIdentifier().add(convert.makeIdentifierFromII(e));
234      visit.setPeriod(convert.makePeriodFromIVL(cda.getChild(ee, "effectiveTime")));
235      composition.getEvent().add(new Composition.CompositionEventComponent());
236      composition.getEvent().get(0).getCode().add(convert.makeCodeableConceptFromCD(cda.getChild(ee, "code")));
237      composition.getEvent().get(0).setPeriod(visit.getPeriod());
238      composition.getEvent().get(0).getDetail().add(Factory.makeReference(addReference(visit, "Encounter", makeUUIDReference())));
239    }
240
241    // main todo: fill out the narrative, but before we can do that, we have to convert everything else
242  }
243
244  protected DocumentConfidentiality convertConfidentiality(Element child) throws org.hl7.fhir.exceptions.FHIRException {
245    // TODO Auto-generated method stub
246    return DocumentConfidentiality.fromCode(child.getAttribute("code"));
247  }
248
249  protected String makeSubject() throws Exception {
250    Element rt = cda.getChild(doc, "recordTarget");
251    Element pr = cda.getChild(rt, "patientRole");
252    Element p = cda.getChild(pr, "patient");
253
254    Patient pat = (Patient) ResourceFactory.createResource("Patient");
255    for (Element e : cda.getChildren(pr, "id"))
256      pat.getIdentifier().add(convert.makeIdentifierFromII(e));
257
258    for (Element e : cda.getChildren(pr, "addr"))
259      pat.getAddress().add(convert.makeAddressFromAD(e));
260    for (Element e : cda.getChildren(pr, "telecom"))
261      pat.getTelecom().add(convert.makeContactFromTEL(e));
262    for (Element e : cda.getChildren(p, "name"))
263      pat.getName().add(convert.makeNameFromEN(e));
264    pat.setGender(convert.makeGenderFromCD(cda.getChild(p, "administrativeGenderCode")));
265    pat.setBirthDateElement(convert.makeDateFromTS(cda.getChild(p, "birthTime")));
266    pat.setMaritalStatus(convert.makeCodeableConceptFromCD(cda.getChild(p, "maritalStatusCode")));
267    pat.getExtension().add(Factory.newExtension(CcdaExtensions.NAME_RELIGION, convert.makeCodeableConceptFromCD(cda.getChild(p, "religiousAffiliationCode")), false));
268    pat.getExtension().add(Factory.newExtension(CcdaExtensions.DAF_NAME_RACE, convert.makeCodeableConceptFromCD(cda.getChild(p, "raceCode")), false));
269    pat.getExtension().add(Factory.newExtension(CcdaExtensions.DAF_NAME_ETHNICITY, convert.makeCodeableConceptFromCD(cda.getChild(p, "ethnicGroupCode")), false));
270    pat.getExtension().add(Factory.newExtension(CcdaExtensions.NAME_BIRTHPLACE, convert.makeAddressFromAD(cda.getChild(p, new String[]{"birthplace", "place", "addr"})), false));
271
272    Patient.ContactComponent guardian = new Patient.ContactComponent();
273    pat.getContact().add(guardian);
274    guardian.getRelationship().add(Factory.newCodeableConcept("GUARD", "urn:oid:2.16.840.1.113883.5.110", "guardian"));
275    Element g = cda.getChild(p, "guardian");
276    for (Element e : cda.getChildren(g, "addr"))
277      if (guardian.getAddress() == null)
278        guardian.setAddress(convert.makeAddressFromAD(e));
279    for (Element e : cda.getChildren(g, "telecom"))
280      guardian.getTelecom().add(convert.makeContactFromTEL(e));
281    g = cda.getChild(g, "guardianPerson");
282    for (Element e : cda.getChildren(g, "name"))
283      if (guardian.getName() == null)
284        guardian.setName(convert.makeNameFromEN(e));
285
286    Element l = cda.getChild(p, "languageCommunication");
287    CodeableConcept cc = new CodeableConcept();
288    Coding c = new Coding();
289    c.setCode(cda.getChild(l, "languageCode").getAttribute("code"));
290    cc.getCoding().add(c);
291    pat.addCommunication().setLanguage(cc);
292
293    // todo: this got broken.... lang.setMode(convert.makeCodeableConceptFromCD(cda.getChild(l, "modeCode")));
294    cc.getExtension().add(Factory.newExtension(CcdaExtensions.NAME_LANG_PROF, convert.makeCodeableConceptFromCD(cda.getChild(l, "modeCode")), false));
295    pat.getExtension().add(Factory.newExtension(CcdaExtensions.NAME_RELIGION, convert.makeCodeableConceptFromCD(cda.getChild(p, "religiousAffiliationCode")), false));
296    pat.setManagingOrganization(Factory.makeReference(makeOrganization(cda.getChild(pr, "providerOrganization"), "Provider")));
297    return addReference(pat, "Subject", makeUUIDReference());
298  }
299
300  protected String makeOrganization(Element org, String name) throws Exception {
301    Organization o = new Organization();
302    for (Element e : cda.getChildren(org, "id"))
303      o.getIdentifier().add(convert.makeIdentifierFromII(e));
304    for (Element e : cda.getChildren(org, "name"))
305      o.setName(e.getTextContent());
306    for (Element e : cda.getChildren(org, "addr"))
307      o.getAddress().add(convert.makeAddressFromAD(e));
308    for (Element e : cda.getChildren(org, "telecom"))
309      o.getTelecom().add(convert.makeContactFromTEL(e));
310
311    return addReference(o, name, makeUUIDReference());
312  }
313
314  protected String makeAuthor(Element auth) throws Exception {
315    Element aa = cda.getChild(auth, "assignedAuthor");
316    Element ap = cda.getChild(aa, "assignedPerson");
317
318    Practitioner pr = (Practitioner) ResourceFactory.createResource("Practitioner");
319    for (Element e : cda.getChildren(aa, "id"))
320      pr.getIdentifier().add(convert.makeIdentifierFromII(e));
321    for (Element e : cda.getChildren(aa, "addr"))
322      if (pr.getAddress() == null)
323        pr.getAddress().add(convert.makeAddressFromAD(e));
324    for (Element e : cda.getChildren(aa, "telecom"))
325      pr.getTelecom().add(convert.makeContactFromTEL(e));
326    for (Element e : cda.getChildren(ap, "name"))
327      if (pr.getName() != null)
328        pr.addName(convert.makeNameFromEN(e));
329
330    return addReference(pr, "Author", makeUUIDReference());
331  }
332
333  protected String makeUUIDReference() {
334    return "urn:uuid:" + UUID.randomUUID().toString().toLowerCase();
335  }
336
337  protected CompositionAttesterComponent makeAttester(Element a1, CompositionAttestationMode mode, String title) throws Exception {
338    Practitioner pr = (Practitioner) ResourceFactory.createResource("Practitioner");
339    Element ass = cda.getChild(a1, "assignedEntity");
340    for (Element e : cda.getChildren(ass, "id"))
341      pr.getIdentifier().add(convert.makeIdentifierFromII(e));
342    for (Element e : cda.getChildren(ass, "addr"))
343      if (pr.getAddress() == null) // just take the first
344        pr.getAddress().add(convert.makeAddressFromAD(e));
345    for (Element e : cda.getChildren(ass, "telecom"))
346      pr.getTelecom().add(convert.makeContactFromTEL(e));
347    Element ap = cda.getChild(ass, "assignedPerson");
348    for (Element e : cda.getChildren(ap, "name"))
349      if (pr.getName() == null) // just take the first
350        pr.addName(convert.makeNameFromEN(e));
351
352
353    CompositionAttesterComponent att = new CompositionAttesterComponent();
354    att.addMode(mode);
355    att.setTimeElement(convert.makeDateTimeFromTS(cda.getChild(a1, "time")));
356    att.setParty(Factory.makeReference(addReference(pr, title, makeUUIDReference())));
357    return att;
358  }
359
360  protected void processComponentSections(List<SectionComponent> sections, Element container) throws Exception {
361    for (Element c : cda.getChildren(container, "component")) {
362      SectionComponent s = processSection(cda.getChild(c, "section"));
363      if (s != null)
364        sections.add(s);
365    }
366
367  }
368
369  protected SectionComponent processSection(Element section) throws Exception {
370    checkNoSubject(section, "Section");
371    // this we do by templateId
372    if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.6") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.6.1"))
373      return processAdverseReactionsSection(section);
374    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.7") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.7.1"))
375      return processProceduresSection(section);
376    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.17"))
377      return processSocialHistorySection(section);
378    else if (cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.4") || cda.hasTemplateId(section, "2.16.840.1.113883.10.20.22.2.4.1"))
379      return processVitalSignsSection(section);
380    else
381      // todo: error?
382      return null;
383  }
384
385  protected void checkNoSubject(Element act, String path) throws Exception {
386    if (cda.getChild(act, "subject") != null)
387      throw new Exception("The conversion program cannot accept a nullFlavor at the location " + path);
388  }
389
390  protected SectionComponent processProceduresSection(Element section) throws Exception {
391    ListResource list = new ListResource();
392    for (Element entry : cda.getChildren(section, "entry")) {
393      Element procedure = cda.getlastChild(entry);
394
395      if (cda.hasTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.14")) {
396        processProcedure(list, procedure, ProcedureType.Procedure);
397      } else if (cda.hasTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.13")) {
398        processProcedure(list, procedure, ProcedureType.Observation);
399      } else if (cda.hasTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.12")) {
400        processProcedure(list, procedure, ProcedureType.Act);
401      } else
402        throw new Exception("Unhandled Section template ids: " + cda.showTemplateIds(procedure));
403    }
404
405    // todo: text
406    SectionComponent s = new Composition.SectionComponent();
407    s.setCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")));
408    // todo: check subject
409    s.addEntry(Factory.makeReference(addReference(list, "Procedures", makeUUIDReference())));
410    return s;
411
412  }
413
414  protected void processProcedure(ListResource list, Element procedure, ProcedureType type) throws Exception {
415    switch (type) {
416      case Procedure:
417        cda.checkTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.14");
418        break;
419      case Observation:
420        cda.checkTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.13");
421        break;
422      case Act:
423        cda.checkTemplateId(procedure, "2.16.840.1.113883.10.20.22.4.12");
424    }
425    checkNoNegationOrNullFlavor(procedure, "Procedure (" + type + ")");
426    checkNoSubject(procedure, "Procedure (" + type + ")");
427
428    Procedure p = new Procedure();
429    addItemToList(list, p);
430
431    // moodCode is either INT or EVN. INT is not handled yet. INT is deprecated anyway
432    if (procedure.getAttribute("moodCode").equals("INT"))
433      p.getModifierExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-planned", Factory.newBoolean(true), false));
434
435    // SHALL contain at least one [1..*] id (CONF:7655).
436    for (Element e : cda.getChildren(procedure, "id"))
437      p.getIdentifier().add(convert.makeIdentifierFromII(e));
438
439    // SHALL contain exactly one [1..1] code (CONF:7656).
440    // This code @code in a procedure activity SHOULD be selected from LOINC or SNOMED CT and MAY be selected from CPT-4, ICD9 Procedures, ICD10 Procedures
441    p.setCode(convert.makeCodeableConceptFromCD(cda.getChild(procedure, "code")));
442
443    // SHALL contain exactly one [1..1] statusCode/@code, which SHALL be selected from ValueSet 2.16.840.1.113883.11.20.9.22 ProcedureAct
444    // completed | active | aborted | cancelled - not in FHIR
445    p.getModifierExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-status", Factory.newCode(cda.getStatus(procedure)), false));
446
447    // SHOULD contain zero or one [0..1] effectiveTime (CONF:7662).
448    p.setPerformed(convert.makePeriodFromIVL(cda.getChild(procedure, "effectiveTime")));
449
450    // MAY contain zero or one [0..1] priorityCode/@code, which SHALL be selected from ValueSet 2.16.840.1.113883.1.11.16866 ActPriority DYNAMIC (CONF:7668)
451    p.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-priority", convert.makeCodeableConceptFromCD(cda.getChild(procedure, "priorityCode")), false));
452
453    // MAY contain zero or one [0..1] methodCode (CONF:7670).
454    p.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-method", convert.makeCodeableConceptFromCD(cda.getChild(procedure, "methodCode")), false));
455
456    if (type == ProcedureType.Observation) {
457      // for Procedure-Observation:
458      // 9.     SHALL contain exactly one [1..1] value (CONF:16846).
459      // don't know what this is. It's not the actual result of the procedure (that goes in results "This section records ... procedure observations"), and there seems to be no value. The example as <value xsi:type="CD"/> which is not valid
460      // so we ignore this for now
461    }
462
463    //  SHOULD contain zero or more [0..*] targetSiteCode/@code, which SHALL be selected from ValueSet 2.16.840.1.113883.3.88.12.3221.8.9 Body site DYNAMIC (CONF:7683).
464    for (Element e : cda.getChildren(procedure, "targetSiteCode"))
465      p.addBodySite(convert.makeCodeableConceptFromCD(e));
466
467    //  MAY contain zero or more [0..*] specimen (CONF:7697).
468    // todo: add these as extensions when specimens are done.
469
470    //  SHOULD contain zero or more [0..*] performer (CONF:7718) such that it
471    for (Element e : cda.getChildren(procedure, "performer")) {
472      ProcedurePerformerComponent pp = new ProcedurePerformerComponent();
473      p.getPerformer().add(pp);
474      pp.setActor(makeReferenceToPractitionerForAssignedEntity(e, p));
475    }
476
477    for (Element participant : cda.getChildren(procedure, "participant")) {
478      Element participantRole = cda.getlastChild(participant);
479      if (type == ProcedureType.Procedure && cda.hasTemplateId(participantRole, "2.16.840.1.113883.10.20.22.4.37")) {
480        //   MAY contain zero or more [0..*] participant (CONF:7751) such that it  SHALL contain exactly one [1..1] @typeCode="DEV" Device
481        // implanted devices
482        p.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/implanted-devices", Factory.makeReference(processDevice(participantRole, p)), false));
483      } else if (cda.hasTemplateId(participantRole, "2.16.840.1.113883.10.20.22.4.32")) {
484        // MAY contain zero or more [0..*] participant (CONF:7765) such that it SHALL contain exactly one [1..1] Service Delivery Location (templateId:2.16.840.1.113883.10.20.22.4.32) (CONF:7767)
485        p.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/location", Factory.makeReference(processSDLocation(participantRole, p)), false));
486      }
487    }
488
489    for (Element e : cda.getChildren(procedure, "entryRelationship")) {
490      Element a /* act*/ = cda.getlastChild(e);
491      if (a.getLocalName().equals("encounter")) {
492        // MAY contain zero or more [0..*] entryRelationship (CONF:7768) such that it SHALL contain exactly one encounter which SHALL contain exactly one [1..1] id (CONF:7773).
493        // todo - and process as a full encounter while we're at it
494      } else if (cda.hasTemplateId(a, "2.16.840.1.113883.10.20.22.4.20")) {
495        //  MAY contain zero or one [0..1] entryRelationship (CONF:7775) such that it SHALL contain exactly one [1..1] Instructions (templateId:2.16.840.1.113883.10.20.22.4.20) (CONF:7778).
496        // had code for type, plus text for instructions
497        Extension n = Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-instructions", null, true);
498        n.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-instructions-type", convert.makeCodeableConceptFromCD(cda.getChild(a, "code")), false));
499        n.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/procedure-instructions-text", convert.makeStringFromED(cda.getChild(a, "text")), false));
500        p.getExtension().add(n);
501      } else if (cda.hasTemplateId(a, "2.16.840.1.113883.10.20.22.4.19")) {
502        // MAY contain zero or more [0..*] entryRelationship (CONF:7779) such that it SHALL contain exactly one [1..1] Indication (templateId:2.16.840.1.113883.10.20.22.4.19) (CONF:7781).
503        p.addReasonCode(processIndication(a));
504      } else if (cda.hasTemplateId(cda.getlastChild(e), "2.16.840.1.113883.10.20.22.4.16")) {
505        //  MAY contain zero or one [0..1] entryRelationship (CONF:7886) such that it SHALL contain exactly one [1..1] Medication Activity (templateId:2.16.840.1.113883.10.20.22.4.16) (CONF:7888).
506        // todo
507      }
508    }
509  }
510
511  protected String processSDLocation(Element participantRole, DomainResource r) throws Exception {
512    Location l = new Location();
513    l.setType(convert.makeCodeableConceptFromCD(cda.getChild(participantRole, "code")));
514    for (Element id : cda.getChildren(participantRole, "id")) {
515      if (l.getIdentifier() == null)
516        l.getIdentifier().add(convert.makeIdentifierFromII(id));
517    }
518    for (Element addr : cda.getChildren(participantRole, "addr")) {
519      if (l.getAddress() == null)
520        l.setAddress(convert.makeAddressFromAD(addr));
521    }
522
523    for (Element telecom : cda.getChildren(participantRole, "telecom")) {
524      l.getTelecom().add(convert.makeContactFromTEL(telecom));
525    }
526
527
528    Element place = cda.getChild(participantRole, "playingDevice");
529    if (cda.getChild(place, "name") != null)
530      l.setName(cda.getChild(place, "name").getTextContent());
531
532    String id = nextRef();
533    l.setId(id);
534    r.getContained().add(l);
535    return "#" + id;
536  }
537
538  protected String processDevice(Element participantRole, DomainResource r) throws Exception {
539    Device d = new Device();
540    for (Element id : cda.getChildren(participantRole, "id")) {
541      // todo: check for UDIs, how?
542      d.getIdentifier().add(convert.makeIdentifierFromII(id));
543    }
544    Element device = cda.getChild(participantRole, "playingDevice");
545    // todo: if (cda.getChild(device, "code") != null)
546    d.setType(convert.makeCodeableConceptFromCD(cda.getChild(device, "code")));
547
548    // CCDA has an id - this is manufacturer? We just call it the name, but what to do about this?
549    Element org = cda.getChild(participantRole, "scopingEntity");
550    d.setManufacturer(convert.makeURIfromII(cda.getChild(org, "id")));
551
552    String id = nextRef();
553    d.setId(id);
554    r.getContained().add(d);
555    return "#" + id;
556  }
557
558  protected CodeableConcept processIndication(Element obs) throws Exception {
559    Element v = cda.getChild(obs, "value");
560    if (v == null) {
561      // have to find it by ID
562      Element o = cda.getById(cda.getChild(obs, "id"), "value");
563      if (o != null)
564        v = cda.getChild(obs, "value");
565    }
566    if (v != null)
567      return convert.makeCodeableConceptFromCD(v);
568    else
569      return null;
570  }
571
572  protected Reference makeReferenceToPractitionerForAssignedEntity(Element assignedEntity, DomainResource r) throws Exception {
573
574    Reference ref = null;
575    // do we have this by id?
576    String uri = getIdForEntity(assignedEntity);
577    Practitioner p = null;
578    if (uri != null) {
579      ref = Factory.makeReference(uri);
580      p = practitionerCache.get(uri);
581    }
582    if (p == null) {
583      p = new Practitioner();
584      if (uri == null) {
585        // make a contained practitioner
586        String n = nextRef();
587        p.setId(n);
588        r.getContained().add(p);
589        ref = Factory.makeReference("#" + n);
590      } else {
591        // add this to feed
592        ref = Factory.makeReference(addReference(p, "Practitioner", uri));
593      }
594    }
595    // ref and p are both sorted. now we fill out p as much as we can (remembering it might already be populated)
596//              p.addRole().setCode(convert.makeCodeableConceptFromCD(cda.getChild(assignedEntity, "code")));
597    for (Element e : cda.getChildren(assignedEntity, "id"))
598      addToIdList(p.getIdentifier(), convert.makeIdentifierFromII(e));
599    for (Element e : cda.getChildren(assignedEntity, "addr"))
600      if (p.getAddress() == null)
601        p.getAddress().add(convert.makeAddressFromAD(e));
602    for (Element e : cda.getChildren(assignedEntity, "telecom"))
603      addToContactList(p.getTelecom(), convert.makeContactFromTEL(e));
604    for (Element e : cda.getChildren(cda.getChild(assignedEntity, "assignedPerson"), "name"))
605      if (p.getName() == null)
606        p.addName(convert.makeNameFromEN(e));
607    // todo:
608    //  representedOrganization
609    return ref;
610  }
611
612  protected void addToContactList(List<ContactPoint> list, ContactPoint c) throws Exception {
613    for (ContactPoint item : list) {
614      if (Comparison.matches(item, c, null))
615        Comparison.merge(item, c);
616    }
617    list.add(c);
618  }
619
620  protected void addToIdList(List<Identifier> list, Identifier id) throws Exception {
621    for (Identifier item : list) {
622      if (Comparison.matches(item, id, null))
623        Comparison.merge(item, id);
624    }
625    list.add(id);
626  }
627
628  protected void addToCodeableList(List<CodeableConcept> list, CodeableConcept code) throws Exception {
629    for (CodeableConcept item : list) {
630      if (Comparison.matches(item, code, null))
631        Comparison.merge(item, code);
632    }
633    list.add(code);
634  }
635
636  protected String getIdForEntity(Element assignedEntity) throws Exception {
637    Element id = cda.getChild(assignedEntity, "id"); // for now, just grab the first
638    if (id == null)
639      return null;
640    if (id.getAttribute("extension") == null) {
641      if (convert.isGuid(id.getAttribute("root")))
642        return "urn:uuid:" + id.getAttribute("root");
643      else
644        return "urn:oid:" + id.getAttribute("root");
645    } else
646      return "ii:" + id.getAttribute("root") + "::" + id.getAttribute("extension");
647  }
648
649  protected SectionComponent processAdverseReactionsSection(Element section) throws Exception {
650    ListResource list = new ListResource();
651    for (Element entry : cda.getChildren(section, "entry")) {
652      Element concern = cda.getChild(entry, "act");
653      if (cda.hasTemplateId(concern, "2.16.840.1.113883.10.20.22.4.30")) {
654        processAllergyProblemAct(list, concern);
655      } else
656        throw new Exception("Unhandled Section template ids: " + cda.showTemplateIds(concern));
657    }
658
659
660    // todo: text
661    SectionComponent s = new Composition.SectionComponent();
662    s.setCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")));
663    // todo: check subject
664    s.addEntry(Factory.makeReference(addReference(list, "Allergies, Adverse Reactions, Alerts", makeUUIDReference())));
665    return s;
666  }
667
668  protected void processAllergyProblemAct(ListResource list, Element concern) throws Exception {
669    cda.checkTemplateId(concern, "2.16.840.1.113883.10.20.22.4.30");
670    // Allergy Problem Act - this is a concern - we treat the concern as information about it's place in the list
671    checkNoNegationOrNullFlavor(concern, "Allergy Problem Act");
672    checkNoSubject(concern, "Allergy Problem Act");
673
674    // SHALL contain at least one [1..*] entryRelationship (CONF:7509) such that it
675    // SHALL contain exactly one [1..1] Allergy - intolerance Observation
676    for (Element entry : cda.getChildren(concern, "entryRelationship")) {
677      Element obs = cda.getChild(entry, "observation");
678      cda.checkTemplateId(obs, "2.16.840.1.113883.10.20.22.4.7");
679      checkNoNegationOrNullFlavor(obs, "Allergy - intolerance Observation");
680      checkNoSubject(obs, "Allergy Problem Act");
681
682      AllergyIntolerance ai = new AllergyIntolerance();
683      ListEntryComponent item = addItemToList(list, ai);
684
685      // this first section comes from the concern, and is processed once for each observation in the concern group
686      // SHALL contain at least one [1..*] id (CONF:7472).
687      for (Element e : cda.getChildren(concern, "id"))
688        ai.getIdentifier().add(convert.makeIdentifierFromII(e));
689
690      // SHALL contain exactly one [1..1] statusCode, which SHALL be selected from ValueSet 2.16.840.1.113883.3.88.12.80.68 HITSPProblemStatus DYNAMIC (CONF:7485)
691      // the status code is about the concern (e.g. the entry in the list)
692      // possible values: active, suspended, aborted, completed, with an effective time
693      String s = cda.getStatus(concern);
694      item.setFlag(Factory.newCodeableConcept(s, "http://hl7.org/fhir/v3/ActStatus", s));
695      if (s.equals("aborted")) // only on this condition?
696        item.setDeleted(true);
697
698      // SHALL contain exactly one [1..1] effectiveTime (CONF:7498)
699      Period p = convert.makePeriodFromIVL(cda.getChild(concern, "effectiveTime"));
700      item.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/list-period", p, false));
701      if (p.getEnd() != null)
702        item.setDate(p.getEnd());
703      else
704        item.setDate(p.getStart());
705
706      //ok, now process the actual observation
707      // SHALL contain at least one [1..*] id (CONF:7382)
708      for (Element e : cda.getChildren(obs, "id"))
709        ai.getIdentifier().add(convert.makeIdentifierFromII(e));
710
711
712      // SHALL contain exactly one [1..1] effectiveTime (CONF:7387)
713      ai.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/allergyintolerance-period", convert.makePeriodFromIVL(cda.getChild(obs, "effectiveTime")), false));
714
715      // SHALL contain exactly one [1..1] value with @xsi:type="CD" (CONF:7390)
716      CodeableConcept type = convert.makeCodeableConceptFromCD(cda.getChild(obs, "value"));
717      // This value SHALL contain @code, which SHALL be selected from ValueSet 2.16.840.1.113883.3.88.12.3221.6.2 Allergy/Adverse Event Type
718      String ss = type.getCoding().get(0).getCode();
719      if (ss.equals("416098002") || ss.equals("414285001"))
720        ai.setType(AllergyIntoleranceType.ALLERGY);
721      else if (ss.equals("59037007") || ss.equals("235719002"))
722        ai.setType(AllergyIntoleranceType.INTOLERANCE);
723      ai.getExtension().add(Factory.newExtension("http://www.healthintersections.com.au/fhir/extensions/allergy-category", type, false));
724
725      // SHOULD contain zero or one [0..1] participant (CONF:7402) such that it
726      // ......This playingEntity SHALL contain exactly one [1..1] code
727      ai.setCode(convert.makeCodeableConceptFromCD(cda.getDescendent(obs, "participant/participantRole/playingEntity/code")));
728
729      //  MAY contain zero or one [0..1] entryRelationship (CONF:7440) such that it SHALL contain exactly one [1..1]  Alert Status Observation
730      //  SHOULD contain zero or more [0..*] entryRelationship (CONF:7447) such that it SHALL contain exactly one [1..1] Reaction Observation (templateId:2.16.840.1.113883.10.20.22.4.9) (CONF:7450).
731      for (Element e : cda.getChildren(obs, "entryRelationship")) {
732        Element child = cda.getChild(e, "observation");
733        if (cda.hasTemplateId(child, "2.16.840.1.113883.10.20.22.4.28") && ai.getClinicalStatus() == null) {
734          // SHALL contain exactly one [1..1] value with @xsi:type="CE", where the @code SHALL be selected from ValueSet Problem Status Value Set 2.16.840.1.113883.3.88.12.80.68 DYNAMIC (CONF:7322).
735          // 55561003  SNOMED CT  Active
736          // 73425007  SNOMED CT  Inactive
737          // 413322009  SNOMED CT  Resolved
738          String sc = cda.getChild(child, "value").getAttribute("code");
739          if (sc.equals("55561003")) {
740            ai.setClinicalStatus(AllergyIntoleranceClinicalStatus.ACTIVE);
741            ai.setVerificationStatus(AllergyIntoleranceVerificationStatus.CONFIRMED);
742          } else
743            ai.setClinicalStatus(AllergyIntoleranceClinicalStatus.RESOLVED);
744        } else if (cda.hasTemplateId(child, "2.16.840.1.113883.10.20.22.4.9")) {
745          ai.getReaction().add(processAdverseReactionObservation(child));
746        }
747      }
748
749      //  SHOULD contain zero or one [0..1] entryRelationship (CONF:9961) such that it SHALL contain exactly one [1..1] Severity Observation (templateId:2.16.840.1.113883.10.20.22.4.8) (CONF:9963).
750      ai.setCriticality(readCriticality(cda.getSeverity(obs)));
751    }
752  }
753
754  // this is going to be a contained resource, so we aren't going to generate any narrative
755  protected AllergyIntoleranceReactionComponent processAdverseReactionObservation(Element reaction) throws Exception {
756    checkNoNegationOrNullFlavor(reaction, "Adverse Reaction Observation");
757    checkNoSubject(reaction, "Adverse Reaction Observation");
758
759    // This clinical statement represents an undesired symptom, finding, etc., due to an administered or exposed substance. A reaction can be defined with respect to its       severity, and can have been treated by one or more interventions.
760    AllergyIntoleranceReactionComponent ar = new AllergyIntoleranceReactionComponent();
761
762    // SHALL contain exactly one [1..1] id (CONF:7329).
763    for (Element e : cda.getChildren(reaction, "id"))
764      ToolingExtensions.addIdentifier(ar, convert.makeIdentifierFromII(e));
765
766    // SHALL contain exactly one [1..1] code (CONF:7327). The value set for this code element has not been specified.
767    // todo: what the heck is this?
768
769    // SHOULD contain zero or one [0..1] text (CONF:7330).
770    // todo: so what is this? how can we know whether to ignore it?
771
772    // 8.  SHOULD contain zero or one [0..1] effectiveTime (CONF:7332).
773    //          a.  This effectiveTime SHOULD contain zero or one [0..1] low (CONF:7333).
774    //          b.  This effectiveTime SHOULD contain zero or one [0..1] high (CONF:7334).
775    // !this is a problem because FHIR just has a date, not a period.
776    ar.setOnsetElement(convert.makeDateTimeFromIVL(cda.getChild(reaction, "effectiveTime")));
777
778    // SHALL contain exactly one [1..1] value with @xsi:type="CD", where the @code SHALL be selected from ValueSet 2.16.840.1.113883.3.88.12.3221.7.4 Problem   DYNAMIC (CONF:7335).
779    ar.getManifestation().add(convert.makeCodeableConceptFromCD(cda.getChild(reaction, "value")));
780
781    // SHOULD contain zero or one [0..1] entryRelationship (CONF:7580) such that it SHALL contain exactly one [1..1] Severity Observation  (templateId:2.16.840.1.113883.10.20.22.4.8) (CONF:7582).
782    ar.setSeverity(readSeverity(cda.getSeverity(reaction)));
783
784    // MAY contain zero or more [0..*] entryRelationship (CONF:7337) such that it SHALL contain exactly one [1..1] Procedure Activity Procedure (templateId:2.16.840.1.113883.10.20.22.4.14) (CONF:7339).
785    // i.  This procedure activity is intended to contain information about procedures that were performed in response to an allergy reaction
786    // todo: process these into an procedure and add as an extension
787
788    // MAY contain zero or more [0..*] entryRelationship (CONF:7340) such that it SHALL contain exactly one [1..1] Medication Activity (templateId:2.16.840.1.113883.10.20.22.4.16) (CONF:7342).
789    // i.  This medication activity is intended to contain information about medications that were administered in response to an allergy reaction. (CONF:7584).
790    // todo: process these into an medication statement and add as an extension
791
792    return ar;
793  }
794
795  protected SectionComponent processSocialHistorySection(Element section) throws Exception {
796    ListResource list = new ListResource();
797    for (Element entry : cda.getChildren(section, "entry")) {
798      Element observation = cda.getlastChild(entry);
799
800      if (cda.hasTemplateId(observation, "2.16.840.1.113883.10.20.22.4.38")) {
801        processSocialObservation(list, observation, SocialHistoryType.SocialHistory);
802      } else if (cda.hasTemplateId(observation, "2.16.840.1.113883.10.20.15.3.8")) {
803        processSocialObservation(list, observation, SocialHistoryType.Pregnancy);
804      } else if (cda.hasTemplateId(observation, "2.16.840.1.113883.10.20.22.4.78")) {
805        processSocialObservation(list, observation, SocialHistoryType.SmokingStatus);
806      } else if (cda.hasTemplateId(observation, "2.16.840.1.113883.10.20.22.4.85")) {
807        processSocialObservation(list, observation, SocialHistoryType.TobaccoUse);
808      } else
809        throw new Exception("Unhandled Section template ids: " + cda.showTemplateIds(observation));
810    }
811
812    // todo: text
813    SectionComponent s = new Composition.SectionComponent();
814    s.setCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")));
815    // todo: check subject
816    s.addEntry(Factory.makeReference(addReference(list, "Procedures", makeUUIDReference())));
817    return s;
818
819  }
820
821  protected void processSocialObservation(ListResource list, Element so, SocialHistoryType type) throws Exception {
822    Observation obs = new Observation();
823    addItemToList(list, obs);
824
825    switch (type) {
826      case SocialHistory:
827        cda.checkTemplateId(so, "2.16.840.1.113883.10.20.22.4.38");
828        // SHALL contain exactly one [1..1] code (CONF:8558/).
829        obs.setCode(convert.makeCodeableConceptFromCD(cda.getChild(so, "code")));
830        break;
831      case Pregnancy:
832        cda.checkTemplateId(so, "2.16.840.1.113883.10.20.15.3.8");
833        // SHALL contain exactly one [1..1] code (CONF:8558/), which SHALL be an assertion
834        obs.setCode(Factory.newCodeableConcept("11449-6", "http://loinc.org", "Pregnancy Status"));
835        break;
836      case SmokingStatus:
837        cda.checkTemplateId(so, "2.16.840.1.113883.10.20.22.4.78");
838        // SHALL contain exactly one [1..1] code (CONF:8558/), which SHALL be an assertion
839        obs.setCode(Factory.newCodeableConcept("72166-2", "http://loinc.org", "Tobacco Smoking Status"));
840        break;
841      case TobaccoUse:
842        cda.checkTemplateId(so, "2.16.840.1.113883.10.20.22.4.12");
843        // SHALL contain exactly one [1..1] code (CONF:8558/), which SHALL be an assertion
844        obs.setCode(Factory.newCodeableConcept("11367-0", "http://loinc.org", "History of Tobacco Use"));
845    }
846
847    // SHALL contain at least one [1..*] id (8551).
848    for (Element e : cda.getChildren(so, "id"))
849      obs.getIdentifier().add(convert.makeIdentifierFromII(e));
850
851
852    // SHALL contain exactly one [1..1] statusCode (CONF:8553/455/14809).
853    // a.       This statusCode SHALL contain exactly one [1..1] @code="completed" Completed (CodeSystem: ActStatus 2.16.840.1.113883.5.14 STATIC) (CONF:19117).
854    obs.setStatus(ObservationStatus.FINAL);
855
856    // SHOULD contain zero or one [0..1] effectiveTime (CONF:2018/14814).
857    // for smoking status/tobacco: low only. in R2, this is just value. So we treat low only as just a value
858    Element et = cda.getChild(so, "effectiveTime");
859    if (et != null) {
860      if (cda.getChild(et, "low") != null)
861        obs.setEffective(convert.makeDateTimeFromTS(cda.getChild(et, "low")));
862      else
863        obs.setEffective(convert.makeDateTimeFromIVL(et));
864    }
865
866    //  SHOULD contain zero or one [0..1] value (CONF:8559).
867    // a.       Observation/value can be any data type.
868    for (Element e : cda.getChildren(so, "value"))
869      if (obs.getValue() == null) { // only one in FHIR
870        // special case for pregnancy:
871        if (type == SocialHistoryType.Pregnancy && "true".equals(e.getAttribute("negationInd"))) {
872          obs.setValue(Factory.newCodeableConcept("60001007", "http://snomed.info/sct", "Not pregnant"));
873        } else {
874          // negationInd is not described. but it might well be used. For now, we blow up
875          checkNoNegation(so, "Social Observation (" + type + ")");
876
877          if (so.hasAttribute("nullFlavor"))
878            obs.setValue(convert.makeCodeableConceptFromNullFlavor(so.getAttribute("nullFlavor")));
879          else if (e.hasAttribute("nullFlavor") && !"OTH".equals(e.getAttribute("nullFlavor")))
880            obs.setValue(convert.makeCodeableConceptFromNullFlavor(e.getAttribute("nullFlavor")));
881          else
882            obs.setValue(convert.makeTypeFromANY(e));
883        }
884      } else
885        throw new Exception("too many values on Social Observation");
886
887    if (type == SocialHistoryType.Pregnancy) {
888      for (Element e : cda.getChildren(so, "entyRelationship")) {
889        Element dd = cda.getChild(e, "observation");
890        checkNoNegationOrNullFlavor(dd, "Estimated Date of Delivery");
891        //  MAY contain zero or one [0..1] entryRelationship (CONF:458) such that it
892        //    SHALL contain exactly one [1..1] @typeCode="REFR" Refers to (CodeSystem: HL7ActRelationshipType 2.16.840.1.113883.5.1002 STATIC) (CONF:459).
893        //      SHALL contain exactly one [1..1] Estimated Date of Delivery (templateId:2.16.840.1.113883.10.20.15.3.1) (CONF:15584).
894        Observation co = new Observation();
895        String id = nextRef();
896        co.setId(id);
897        obs.getContained().add(co);
898        ObservationRelatedComponent or = new ObservationRelatedComponent();
899        obs.getRelated().add(or);
900        or.setType(ObservationRelationshipType.HASMEMBER);
901        or.setTarget(Factory.makeReference("#" + id));
902        co.setCode(Factory.newCodeableConcept("11778-8", "http://loinc.org", "Delivery date Estimated"));
903        co.setValue(convert.makeDateTimeFromTS(cda.getChild(dd, "value"))); // not legal, see gForge http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_item_id=3125&start=0
904      }
905    }
906  }
907
908  protected void checkNoNegation(Element act, String path) throws Exception {
909    if ("true".equals(act.getAttribute("negationInd")))
910      throw new Exception("The conversion program cannot accept a negationInd at the location " + path);
911  }
912
913  protected void checkNoNegationOrNullFlavor(Element act, String path) throws Exception {
914    if (act.hasAttribute("nullFlavor"))
915      throw new Exception("The conversion program cannot accept a nullFlavor at the location " + path);
916    if ("true".equals(act.getAttribute("negationInd")))
917      throw new Exception("The conversion program cannot accept a negationInd at the location " + path);
918  }
919
920  protected ListEntryComponent addItemToList(ListResource list, DomainResource ai)
921    throws Exception {
922    list.getContained().add(ai);
923    String n = nextRef();
924    ai.setId(n);
925    ListEntryComponent item = new ListResource.ListEntryComponent();
926    list.getEntry().add(item);
927    item.setItem(Factory.makeReference("#" + n));
928    return item;
929  }
930
931  protected String nextRef() {
932    refCounter++;
933    String n = refCounter.toString();
934    return n;
935  }
936
937  protected AllergyIntoleranceCriticality readCriticality(String severity) {
938    if ("255604002".equals(severity)) // Mild
939      return AllergyIntoleranceCriticality.LOW;
940    if ("371923003".equals(severity)) //  Mild to moderate
941      return AllergyIntoleranceCriticality.LOW;
942    if ("6736007".equals(severity)) // Moderate
943      return AllergyIntoleranceCriticality.LOW;
944    if ("371924009".equals(severity)) // Moderate to severe
945      return AllergyIntoleranceCriticality.HIGH;
946    if ("24484000".equals(severity)) // Severe
947      return AllergyIntoleranceCriticality.HIGH;
948    if ("399166001".equals(severity)) // Fatal
949      return AllergyIntoleranceCriticality.HIGH;
950    return null;
951  }
952
953  protected AllergyIntoleranceSeverity readSeverity(String severity) {
954    if ("255604002".equals(severity)) // Mild
955      return AllergyIntoleranceSeverity.MILD;
956    if ("371923003".equals(severity)) //  Mild to moderate
957      return AllergyIntoleranceSeverity.MODERATE;
958    if ("6736007".equals(severity)) // Moderate
959      return AllergyIntoleranceSeverity.MODERATE;
960    if ("371924009".equals(severity)) // Moderate to severe
961      return AllergyIntoleranceSeverity.SEVERE;
962    if ("24484000".equals(severity)) // Severe
963      return AllergyIntoleranceSeverity.SEVERE;
964    if ("399166001".equals(severity)) // Fatal
965      return AllergyIntoleranceSeverity.SEVERE;
966    return null;
967  }
968
969  protected SectionComponent processVitalSignsSection(Element section) throws Exception {
970    ListResource list = new ListResource();
971    for (Element entry : cda.getChildren(section, "entry")) {
972      Element organizer = cda.getlastChild(entry);
973
974      if (cda.hasTemplateId(organizer, "2.16.840.1.113883.10.20.22.4.26")) {
975        processVitalSignsOrganizer(list, organizer);
976      } else
977        throw new Exception("Unhandled Section template ids: " + cda.showTemplateIds(organizer));
978    }
979
980    // todo: text
981    SectionComponent s = new Composition.SectionComponent();
982    s.setCode(convert.makeCodeableConceptFromCD(cda.getChild(section, "code")));
983    // todo: check subject
984    s.addEntry(Factory.makeReference(addReference(list, "Vital Signs", makeUUIDReference())));
985    return s;
986
987  }
988
989  protected void processVitalSignsOrganizer(ListResource list, Element organizer) throws Exception {
990
991    cda.checkTemplateId(organizer, "2.16.840.1.113883.10.20.22.4.26");
992    checkNoNegationOrNullFlavor(organizer, "Vital Signs Organizer");
993    checkNoSubject(organizer, "Vital Signs Organizer");
994    // moodCode is EVN.
995
996    Observation obs = new Observation();
997    addItemToList(list, obs);
998
999    // SHALL contain at least one [1..*] id (CONF:7282).
1000    for (Element e : cda.getChildren(organizer, "id"))
1001      obs.getIdentifier().add(convert.makeIdentifierFromII(e));
1002
1003    // SHALL contain exactly one [1..1] code (CONF:19176).
1004    //  This code SHALL contain exactly one [1..1] @code="46680005" Vital signs (CodeSystem: SNOMED-CT 2.16.840.1.113883.6.96 STATIC) (CONF:19177).
1005    obs.setCode(convert.makeCodeableConceptFromCD(cda.getChild(organizer, "code")));
1006
1007
1008    // SHALL contain exactly one [1..1] effectiveTime (CONF:7288).
1009    obs.setEffective(convert.makeMatchingTypeFromIVL(cda.getChild(organizer, "effectiveTime")));
1010
1011    // SHALL contain at least one [1..*] component (CONF:7285) such that it
1012    // SHALL contain exactly one [1..1] Vital Sign Observation (templateId:2.16.840.1.113883.10.20.22.4.27) (CONF:15946).
1013    for (Element e : cda.getChildren(organizer, "component")) {
1014      ObservationRelatedComponent ro = new ObservationRelatedComponent();
1015      ro.setType(ObservationRelationshipType.HASMEMBER);
1016      ro.setTarget(Factory.makeReference("#" + processVitalSignsObservation(e, list)));
1017    }
1018  }
1019
1020  protected String processVitalSignsObservation(Element comp, ListResource list) throws Exception {
1021    Element observation = cda.getChild(comp, "observation");
1022    cda.checkTemplateId(observation, "2.16.840.1.113883.10.20.22.4.27");
1023    checkNoNegationOrNullFlavor(observation, "Vital Signs Observation");
1024    checkNoSubject(observation, "Vital Signs Observation");
1025
1026    Observation obs = new Observation();
1027
1028    //  SHALL contain at least one [1..*] id (CONF:7300).
1029    for (Element e : cda.getChildren(observation, "id"))
1030      obs.getIdentifier().add(convert.makeIdentifierFromII(e));
1031
1032    // SHALL contain exactly one [1..1] code, which SHOULD be selected from ValueSet Vital Sign Result Value Set 2.16.840.1.113883.3.88.12.80.62 DYNAMIC (CONF:7301).
1033    obs.setCode(convert.makeCodeableConceptFromCD(cda.getChild(observation, "code"))); // all loinc codes
1034
1035    // SHOULD contain zero or one [0..1] text (CONF:7302).
1036    // The text, if present, SHOULD contain zero or one [0..1] reference (CONF:15943).
1037    // The reference, if present, SHOULD contain zero or one [0..1] @value (CONF:15944).
1038    // This reference/@value SHALL begin with a '#' and SHALL point to its corresponding narrative (using the approach defined in CDA Release 2, section 4.3.5.1) (CONF:15945).
1039    // todo: put this in narrative if it exists?
1040
1041
1042    // SHALL contain exactly one [1..1] statusCode (CONF:7303). This statusCode SHALL contain exactly one [1..1] @code="completed" Completed (CodeSystem: ActStatus 2.16.840.1.113883.5.14 STATIC) (CONF:19119).
1043    // ignore
1044
1045    // SHALL contain exactly one [1..1] effectiveTime (CONF:7304).
1046    obs.setEffective(convert.makeMatchingTypeFromIVL(cda.getChild(observation, "effectiveTime")));
1047
1048    //  SHALL contain exactly one [1..1] value with @xsi:type="PQ" (CONF:7305).
1049    obs.setValue(convert.makeQuantityFromPQ(cda.getChild(observation, "value")));
1050
1051    // MAY contain zero or one [0..1] interpretationCode (CONF:7307).
1052    obs.setInterpretation(convert.makeCodeableConceptFromCD(cda.getChild(observation, "interpretationCode")));
1053
1054    //  MAY contain zero or one [0..1] methodCode (CONF:7308).
1055    obs.setMethod(convert.makeCodeableConceptFromCD(cda.getChild(observation, "methodCode")));
1056
1057    // MAY contain zero or one [0..1] targetSiteCode (CONF:7309).
1058    obs.setBodySite(convert.makeCodeableConceptFromCD(cda.getChild(observation, "targetSiteCode")));
1059
1060    // MAY contain zero or one [0..1] author (CONF:7310).
1061    if (cda.getChild(observation, "author") != null)
1062      obs.getPerformer().add(makeReferenceToPractitionerForAssignedEntity(cda.getChild(observation, "author"), composition));
1063
1064    // make a contained practitioner
1065    String n = nextRef();
1066    obs.setId(n);
1067    list.getContained().add(obs);
1068    return n;
1069  }
1070
1071  public enum SocialHistoryType {
1072    SocialHistory, Pregnancy, SmokingStatus, TobaccoUse
1073
1074  }
1075
1076
1077  public enum ProcedureType {
1078    Observation, Procedure, Act
1079
1080  }
1081}