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