internal string ConvertTimeToUtc(string effectiveTime) { if (!string.IsNullOrEmpty(effectiveTime)) { try { return(XdsMetadataHelper.GetUtcTime(effectiveTime)); } catch (Exception ex) { throw new Exception("Error parsing effective time in CDA document: " + ex.Message); } } else { return(""); } }
public XdsMetadata( XmlDocument cdaDocument, string repositoryId, // FormatCodes formatCode, string formatCode, string formatCodeName, HealthcareFacilityTypeCodes healthcareFacilityTypeCode, PracticeSettingTypes practiceSetting, int?size, string hash, bool isUpdateMetadata, string uuidOfDocumentToReplace) { this.formatCode = formatCode; this.formatCodeName = formatCodeName; this.healthcareFacilityTypeCode = healthcareFacilityTypeCode; this.practiceSetting = practiceSetting; this.isUpdateMetadata = isUpdateMetadata; this.uuidOfDocumentToReplace = uuidOfDocumentToReplace; if (isUpdateMetadata) { this.repositoryId = repositoryId; this.size = size.Value; this.hash = hash; } var xnm = new XmlNamespaceManager(cdaDocument.NameTable); xnm.AddNamespace("cda", "urn:hl7-org:v3"); xnm.AddNamespace("ext", "http://ns.electronichealth.net.au/Ci/Cda/Extensions/3.0"); // Document Type documentTypeCode = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:code/@code", xnm)); var classCode = GetClassCodeEnum(documentTypeCode); documentTypeCodeSystemName = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:code/@codeSystemName", xnm)); documentTypeDisplayName = classCode.GetAttributeValue <CodedValueAttribute, string>(a => a.AlternateName); // 14/10 Updated Spec says we should use the AlternateName for both Type and Class Code documentClassCodeDisplayName = classCode.GetAttributeValue <CodedValueAttribute, string>(a => a.AlternateName); documentTypeCode_cl07 = documentTypeCode; documentTypeDisplayName_cl07 = documentTypeDisplayName; documentTypeCodeSystemName_cl07 = documentTypeCodeSystemName; // The exception is for Advance Care Information = which has Advance Care Planning Document/Goals of Care Document subtypes if (documentTypeCode == "100.16975") { // Get document desc and code from the body documentTypeCode_cl07 = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry/cda:act/cda:reference/cda:externalDocument/cda:code/@code", xnm)); documentTypeDisplayName_cl07 = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section/cda:entry/cda:act/cda:reference/cda:externalDocument/cda:code/@displayName", xnm)); if (documentTypeCode_cl07 == "" || documentTypeDisplayName_cl07 == "") { throw new ArgumentException("The Advance Care Information does not reference an externalDocument."); } } // Get time stuff effectiveTime = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:effectiveTime/@value", xnm)); effectiveTime = ConvertTimeToUtc(effectiveTime); // Set service start and stop time if (documentTypeCode == "51852-2") { // If document is Specialist Letter serviceStartTime = effectiveTime; serviceStopTime = effectiveTime; } else if (documentTypeCode == "100.16764") { // If document is PCEHR Prescription Record var authorTime = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:author/cda:time/@value", xnm)); serviceStartTime = ConvertTimeToUtc(authorTime); serviceStopTime = serviceStartTime; } else if (documentTypeCode == "100.16765") { // If document is PCEHR Dispense Record var supplyTime = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:component/cda:structuredBody/" + "cda:component/cda:section[cda:code/@code='102.16210' and cda:code/@codeSystem='1.2.36.1.2001.1001.101']/cda:entry/" + "cda:substanceAdministration/cda:entryRelationship/cda:supply/cda:effectiveTime/@value", xnm)); serviceStartTime = ConvertTimeToUtc(supplyTime); serviceStopTime = serviceStartTime; } else if (documentTypeCode == "100.32001") { // R5: If document is Pathology Report - Could contain multiple records // Need to get LATEST Date - if multiple returned XmlNodeList nList = cdaDocument.SelectNodes("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component" + "/cda:section[cda:code/@code='101.20018']/cda:component" + "/cda:section[cda:code/@code='102.16144']/cda:entry/cda:observation" + "/cda:entryRelationship/cda:observation[cda:code/@code='102.16156']" + "/cda:effectiveTime/@value", xnm); var firstCollectionDateTime = ""; var lastCollectionDateTime = ""; if (nList.Count == 1) { firstCollectionDateTime = nList[0].Value; lastCollectionDateTime = nList[0].Value; } else if (nList.Count > 1) { string[] sDates = new string[nList.Count]; for (int i = 0; i < nList.Count; i++) { sDates[i] = nList[i].Value; } Array.Sort(sDates); //Get last entry firstCollectionDateTime = sDates[0]; lastCollectionDateTime = sDates[nList.Count - 1]; } serviceStartTime = ConvertTimeToUtc(firstCollectionDateTime); serviceStopTime = ConvertTimeToUtc(lastCollectionDateTime); } else if (documentTypeCode == "100.16957") { // R5: If document is Diagnostic Imaging - Could contain multiple records // Need to get LATEST Date - if multiple returned XmlNodeList nList = cdaDocument.SelectNodes("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component" + "/cda:section/cda:component/cda:section/cda:entry/cda:observation" + "/cda:entryRelationship/cda:act[cda:code/@code='102.16511']/cda:entryRelationship" + "/cda:observation/cda:effectiveTime/@value", xnm); var firstImagingDateTime = ""; var lastImagingDateTime = ""; if (nList.Count == 1) { firstImagingDateTime = nList[0].Value; lastImagingDateTime = nList[0].Value; } else if (nList.Count > 1) { string[] sDates = new string[nList.Count]; for (int i = 0; i < nList.Count; i++) { sDates[i] = nList[i].Value; } Array.Sort(sDates); //Get last entry firstImagingDateTime = sDates[0]; lastImagingDateTime = sDates[nList.Count - 1]; } serviceStartTime = ConvertTimeToUtc(firstImagingDateTime); serviceStopTime = ConvertTimeToUtc(lastImagingDateTime); } else if (documentTypeCode == "100.16975") { // Advance Care Planning Document var authorTime = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section[cda:code/@code='101.16973']/cda:entry/cda:act/cda:author/cda:time/@value", xnm)); serviceStartTime = ConvertTimeToUtc(authorTime); serviceStopTime = serviceStartTime; } else { // For other document types set service start and stop time to encompassingEncounter/effectiveTime if available string startTime1 = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:componentOf/cda:encompassingEncounter/cda:effectiveTime/@value", xnm)); string startTime2 = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:componentOf/cda:encompassingEncounter/cda:effectiveTime/cda:low/@value", xnm)); string stopTime = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:componentOf/cda:encompassingEncounter/cda:effectiveTime/cda:high/@value", xnm)); serviceStartTime = !string.IsNullOrEmpty(startTime1) ? startTime1 : !string.IsNullOrEmpty(startTime2) ? startTime2 : effectiveTime; serviceStartTime = ConvertTimeToUtc(serviceStartTime); serviceStopTime = !string.IsNullOrEmpty(stopTime) ? stopTime : !string.IsNullOrEmpty(startTime1) ? startTime1 : effectiveTime; serviceStopTime = ConvertTimeToUtc(serviceStopTime); } // As per DEXS-T123, serviceStartTime and serviceStopTime must be at least 8 chars long eg UTC formats: YYYYMMDD, YYYYMMDDhhmm, and YYYYMMDDhhmmss if (serviceStartTime.Length < 8) { throw new ArgumentException("The serviceStartTime retrieved from the CDA document must be at least 8 digits long."); } if (serviceStopTime.Length < 8) { throw new ArgumentException("The serviceStopTime retrieved from the CDA document must be at least 8 digits long."); } // Get Data languageCode = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:languageCode/@code", xnm)); //Added as some documents do not include it. (4/2/15) if (String.IsNullOrEmpty(languageCode)) { languageCode = "en-AU"; } cdaTitle = CheckNullText(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:title", xnm)); documentTitle = !string.IsNullOrEmpty(cdaTitle) ? cdaTitle : documentTypeDisplayName; // Process document ID XdsMetadataHelper.IdType?idType; documentId = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:id/@root", xnm)); documentIdExtension = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:id/@extension", xnm)); cdaDocumentIdOid = XdsMetadataHelper.UuidToOid(documentId, out idType); if (idType == null) { throw new ArgumentException("The CDA document must contain an ID which is either a UUID or an OID."); } else if (idType == XdsMetadataHelper.IdType.Oid && !string.IsNullOrEmpty(documentIdExtension)) { cdaDocumentIdOid = cdaDocumentIdOid + "^" + documentIdExtension; } XmlNode authorNode = null; if (cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:author/cda:assignedAuthor/cda:assignedPerson", xnm) != null) { authorNode = cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:author/cda:assignedAuthor", xnm); } // If document author doesn't exist, and document is pathology if (authorNode == null && documentTypeCode == "100.32001") { authorNode = cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:component/cda:structuredBody/cda:component/cda:section[cda:code/@code='101.20018']/cda:author/cda:assignedAuthor", xnm); } bool authorDevice = false; if (cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:author/cda:assignedAuthor/cda:assignedAuthoringDevice", xnm) != null) { authorNode = authorNode = cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:author/cda:assignedAuthor", xnm); authorDevice = true; } if (authorNode == null && authorDevice == false) { throw new ArgumentException("Unable to determine document author from CDA document"); } // Current Author format string authorOrgIdCurr = CheckNullValue(authorNode.SelectSingleNode("cda:assignedPerson/ext:asEmployment/ext:employerOrganization/cda:asOrganizationPartOf/cda:wholeOrganization/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='HPI-O']/@root", xnm)); string authorOrgNameCurr = CheckNullText(authorNode.SelectSingleNode("cda:assignedPerson/ext:asEmployment/ext:employerOrganization/cda:asOrganizationPartOf/cda:wholeOrganization/cda:name", xnm)); // Check Custodian if Author has no Org [Dont need code - as it would fail PCEHR validation for the NCAP 5 documents] string authorOrgIdCust = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:custodian/cda:assignedCustodian/cda:representedCustodianOrganization/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='HPI-O']/@root", xnm)); string authorOrgNameCust = CheckNullText(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:custodian/cda:assignedCustodian/cda:representedCustodianOrganization/cda:name", xnm)); // Check Health Care Facility in Component Of if Author has no Org string authorOrgIdHCF = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:componentOf/cda:encompassingEncounter/cda:location/cda:healthCareFacility/cda:serviceProviderOrganization/cda:asOrganizationPartOf/cda:wholeOrganization/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='HPI-O']/@root", xnm)); string authorOrgNameHCF = CheckNullText(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:componentOf/cda:encompassingEncounter/cda:location/cda:healthCareFacility/cda:serviceProviderOrganization/cda:asOrganizationPartOf/cda:wholeOrganization/cda:name", xnm)); authorQualifiedOrgId = (authorOrgIdCurr != "" ? authorOrgIdCurr : (authorOrgIdCust != "" ? authorOrgIdCust : authorOrgIdHCF)); if (String.IsNullOrEmpty(authorOrgName)) { authorOrgName = (authorOrgNameCurr != "" ? authorOrgNameCurr : (authorOrgNameCust != "" ? authorOrgNameCust : authorOrgNameHCF)); } // AUTHOR authorQualifiedId = CheckNullValue(authorNode.SelectSingleNode("cda:assignedPerson/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='HPI-I']/@root", xnm)); if (string.IsNullOrEmpty(authorQualifiedId)) { authorQualifiedId = CheckNullValue(authorNode.SelectSingleNode("cda:assignedAuthoringDevice/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='PAI-D']/@root", xnm)); } if (string.IsNullOrEmpty(authorQualifiedId)) { authorQualifiedId = CheckNullValue(cdaDocument.SelectSingleNode("cda:assignedPerson/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='PAI-D']/@root", xnm)); } if (string.IsNullOrEmpty(authorQualifiedId)) { authorQualifiedId = CheckNullValue(authorNode.SelectSingleNode("cda:assignedPerson/ext:asEntityIdentifier[@classCode='IDENT']/ext:id/@root", xnm)); authorQualifiedIdExtension = CheckNullValue(authorNode.SelectSingleNode("cda:assignedPerson/ext:asEntityIdentifier[@classCode='IDENT']/ext:id/@extension", xnm)); } if (string.IsNullOrEmpty(authorQualifiedId)) { throw new ArgumentException("The CDA document must contain an author identifier."); } if (string.IsNullOrEmpty(authorFamily)) { authorFamily = CheckNullText(authorNode.SelectSingleNode("cda:assignedPerson/cda:name/cda:family", xnm)); } authorGiven = CheckNullText(authorNode.SelectSingleNode("cda:assignedPerson/cda:name/cda:given", xnm)); authorPrefix = CheckNullText(authorNode.SelectSingleNode("cda:assignedPerson/cda:name/cda:prefix", xnm)); authorSuffix = CheckNullText(authorNode.SelectSingleNode("cda:assignedPerson/cda:name/cda:suffix", xnm)); // Author specialty authorSpecialty = CheckNullValue(authorNode.SelectSingleNode("cda:code/@displayName", xnm)); // HI numbers (for header) hpioNumber = authorQualifiedOrgId.Replace("1.2.36.1.2001.1003.0.", ""); ihiNumber = CheckNullValue(cdaDocument.SelectSingleNode("/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient/ext:asEntityIdentifier[@classCode='IDENT']/ext:id[@assigningAuthorityName='IHI']/@root", xnm)).Replace("1.2.36.1.2001.1003.0.", ""); // Formatted Ids cxFormattedPatientId = ihiNumber + "^^^&1.2.36.1.2001.1003.0&ISO"; // Correct format (3 hats before HPII) if (string.IsNullOrEmpty(authorQualifiedIdExtension)) { xcnFormattedAuthor = string.Format("^{0}^{1}^^^{2}^^^&{3}&ISO", authorFamily, authorGiven, authorPrefix, authorQualifiedId); } else { xcnFormattedAuthor = string.Format("^{0}^{1}^^^{2}^^^{3}&{4}&ISO", authorFamily, authorGiven, authorPrefix, authorQualifiedId, authorQualifiedIdExtension); } xonFormattedOrganisation = string.Format("{0}^^^^^^^^^{1}", authorOrgName, authorQualifiedOrgId); }