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