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