public TObservation MergeObservation(ILookupTemplate <IFhirTemplate> lookup, IObservationGroup observationGroup, TObservation existingObservation)
        {
            EnsureArg.IsNotNull(observationGroup, nameof(observationGroup));

            var(template, processor) = GetTemplateAndProcessor(observationGroup.Name, lookup);
            return(processor.MergeObservation(template, observationGroup, existingObservation));
        }
        public void GivenValidTemplate_WhenCreateObservationGroups_ThenCorrectProcessorInvoked_Test()
        {
            var template = Substitute.For <IFhirTemplate>();
            var lookup   = Substitute.For <ILookupTemplate <IFhirTemplate> >();

            lookup.GetTemplate("a").Returns(template);
            var measurementGroup = Substitute.For <IMeasurementGroup>();

            measurementGroup.MeasureType.Returns("a");

            var data = new IObservationGroup[] { };
            var p1   = Substitute.For <IFhirTemplateProcessor <IFhirTemplate, string> >();

            p1.SupportedTemplateType.Returns(template.GetType());
            p1.CreateObservationGroups(null, null).ReturnsForAnyArgs(data);

            var processor = new TestFhirLookupTemplateProcessor(p1);
            var result    = processor.CreateObservationGroups(lookup, measurementGroup);

            Assert.Equal(data, result);

            _ = measurementGroup.Received(1).MeasureType;
            lookup.Received(1).GetTemplate("a");
            template.Received(1).GetType();

            p1.Received(1).CreateObservationGroups(template, measurementGroup);
        }
Esempio n. 3
0
        protected static (string Identifer, string System) GenerateObservationId(IObservationGroup observationGroup, string deviceId, string patientId)
        {
            EnsureArg.IsNotNull(observationGroup, nameof(observationGroup));
            EnsureArg.IsNotNullOrWhiteSpace(deviceId, nameof(deviceId));
            EnsureArg.IsNotNullOrWhiteSpace(patientId, nameof(patientId));

            var value = $"{patientId}.{deviceId}.{observationGroup.Name}.{observationGroup.GetIdSegment()}";

            return(value, ServiceSystem);
        }
Esempio n. 4
0
        protected static Model.Identifier GenerateObservationIdentifier(IObservationGroup grp, IDictionary <ResourceType, string> ids)
        {
            EnsureArg.IsNotNull(grp, nameof(grp));
            var identity = FhirImportService.GenerateObservationId(grp, ids[ResourceType.Device], ids[ResourceType.Patient]);

            return(new Model.Identifier
            {
                System = identity.System,
                Value = identity.Identifer,
            });
        }
Esempio n. 5
0
        protected static (string Identifer, string System) GenerateObservationId(IObservationGroup observationGroup, string deviceId, string patientId)
        {
            EnsureArg.IsNotNull(observationGroup, nameof(observationGroup));
            EnsureArg.IsNotNullOrWhiteSpace(deviceId, nameof(deviceId));
            EnsureArg.IsNotNullOrWhiteSpace(patientId, nameof(patientId));

            var startToken = observationGroup.Boundary.Start.ToString(DateFormat, CultureInfo.InvariantCulture.DateTimeFormat);
            var endToken   = observationGroup.Boundary.End.ToString(DateFormat, CultureInfo.InvariantCulture.DateTimeFormat);
            var value      = $"{patientId}.{deviceId}.{observationGroup.Name}.{startToken}.{endToken}";

            return(value, ServiceSystem);
        }
Esempio n. 6
0
        public virtual Model.Observation GenerateObservation(ILookupTemplate <IFhirTemplate> config, IObservationGroup grp, Model.Identifier observationId, IDictionary <ResourceType, string> ids)
        {
            EnsureArg.IsNotNull(grp, nameof(grp));
            EnsureArg.IsNotNull(observationId, nameof(observationId));
            EnsureArg.IsNotNull(ids, nameof(ids));

            var patientId = Ensure.String.IsNotNullOrWhiteSpace(ids[ResourceType.Patient], nameof(ResourceType.Patient));
            var deviceId  = Ensure.String.IsNotNullOrWhiteSpace(ids[ResourceType.Device], nameof(ResourceType.Device));

            var observation = _fhirTemplateProcessor.CreateObservation(config, grp);

            observation.Subject    = patientId.ToReference <Model.Patient>();
            observation.Device     = deviceId.ToReference <Model.Device>();
            observation.Identifier = new List <Model.Identifier> {
                observationId
            };

            if (ids.TryGetValue(ResourceType.Encounter, out string encounterId))
            {
                observation.Encounter = encounterId.ToReference <Model.Encounter>();
            }

            return(observation);
        }
 public ResponseObservation(IObservationGroup group, IResponse <string> response)
     : this(
         (group ?? throw new ArgumentNullException(nameof(group))).Id,
Esempio n. 8
0
        private static (string Identifer, string System) GenerateObservationId(IObservationGroup observationGroup, string deviceId, string patientId)
        {
            var value = $"{patientId}.{deviceId}.{observationGroup.Name}.{observationGroup.GetIdSegment()}";

            return(value, FhirImportService.ServiceSystem);
        }
 public virtual string TestMergeObservationImpl(CodeValueFhirTemplate template, IObservationGroup observationGroup, string existingObservation)
 {
     return(null);
 }
        protected override Observation MergeObservationImpl(CodeValueFhirTemplate template, IObservationGroup grp, Observation existingObservation)
        {
            EnsureArg.IsNotNull(grp, nameof(grp));
            EnsureArg.IsNotNull(existingObservation, nameof(existingObservation));

            existingObservation.Status = ObservationStatus.Amended;

            existingObservation.Category = null;
            if (template?.Category?.Count > 0)
            {
                existingObservation.Category = ResolveCategory(template.Category);
            }

            var values = grp.GetValues();

            (DateTime start, DateTime end)observationPeriod = GetObservationPeriod(existingObservation);

            // Update observation value
            if (!string.IsNullOrWhiteSpace(template?.Value?.ValueName) && values.TryGetValue(template?.Value?.ValueName, out var obValues))
            {
                existingObservation.Value = _valueProcessor.MergeValue(template.Value, CreateMergeData(grp.Boundary, observationPeriod, obValues), existingObservation.Value);
            }

            // Update observation component values
            if (template?.Components?.Count > 0)
            {
                if (existingObservation.Component == null)
                {
                    existingObservation.Component = new List <Observation.ComponentComponent>(template.Components.Count);
                }

                foreach (var component in template.Components)
                {
                    if (values.TryGetValue(component.Value.ValueName, out var compValues))
                    {
                        var foundComponent = existingObservation.Component
                                             .Where(c => c.Code.Coding.Any(code => code.Code == component.Value.ValueName && code.System == FhirImportService.ServiceSystem))
                                             .FirstOrDefault();

                        if (foundComponent == null)
                        {
                            existingObservation.Component.Add(
                                new Observation.ComponentComponent
                            {
                                Code  = ResolveCode(component.Value.ValueName, component.Codes),
                                Value = _valueProcessor.CreateValue(component.Value, CreateMergeData(grp.Boundary, observationPeriod, compValues)),
                            });
                        }
                        else
                        {
                            foundComponent.Value = _valueProcessor.MergeValue(component.Value, CreateMergeData(grp.Boundary, observationPeriod, compValues), foundComponent.Value);
                        }
                    }
                }
            }

            // Update observation effective period if merge values exist outside the current period.
            if (grp.Boundary.Start < observationPeriod.start)
            {
                observationPeriod.start = grp.Boundary.Start;
            }

            if (grp.Boundary.End > observationPeriod.end)
            {
                observationPeriod.end = grp.Boundary.End;
            }

            existingObservation.Effective = observationPeriod.ToPeriod();

            return(existingObservation);
        }
        protected override Observation CreateObservationImpl(CodeValueFhirTemplate template, IObservationGroup grp)
        {
            EnsureArg.IsNotNull(template, nameof(template));
            EnsureArg.IsNotNull(grp, nameof(grp));

            var observation = new Observation
            {
                Status    = ObservationStatus.Final,
                Code      = ResolveCode(grp.Name, template.Codes),
                Issued    = DateTimeOffset.UtcNow,
                Effective = grp.Boundary.ToPeriod(),
            };

            if (template?.Category?.Count > 0)
            {
                observation.Category = ResolveCategory(template.Category);
            }

            var values = grp.GetValues();

            if (!string.IsNullOrWhiteSpace(template?.Value?.ValueName) && values.TryGetValue(template?.Value?.ValueName, out var obValues))
            {
                observation.Value = _valueProcessor.CreateValue(template.Value, CreateMergeData(grp.Boundary, grp.Boundary, obValues));
            }

            if (template?.Components?.Count > 0)
            {
                observation.Component = new List <Observation.ComponentComponent>(template.Components.Count);

                foreach (var component in template.Components)
                {
                    if (values.TryGetValue(component.Value.ValueName, out var compValues))
                    {
                        observation.Component.Add(
                            new Observation.ComponentComponent
                        {
                            Code  = ResolveCode(component.Value.ValueName, component.Codes),
                            Value = _valueProcessor.CreateValue(component.Value, CreateMergeData(grp.Boundary, grp.Boundary, compValues)),
                        });
                    }
                }
            }

            return(observation);
        }
 public virtual string TestCreateObseravtionImpl(CodeValueFhirTemplate template, IObservationGroup observationGroup)
 {
     return(null);
 }
 protected abstract TObservation CreateObservationImpl(TTemplate template, IObservationGroup observationGroup);
 public TObservation CreateObservation(IFhirTemplate template, IObservationGroup observationGroup)
 {
     return(CreateObservationImpl(CastTemplate(template), observationGroup));
 }
Esempio n. 15
0
 public virtual Model.Observation MergeObservation(ILookupTemplate <IFhirTemplate> config, Model.Observation observation, IObservationGroup grp)
 {
     return(_fhirTemplateProcessor.MergeObservation(config, grp, observation));
 }
Esempio n. 16
0
        public virtual async Task <string> SaveObservationAsync(ILookupTemplate <IFhirTemplate> config, IObservationGroup observationGroup, IDictionary <ResourceType, string> ids)
        {
            var identifier = GenerateObservationIdentifier(observationGroup, ids);
            var cacheKey   = $"{identifier.System}|{identifier.Value}";

            if (!_observationCache.TryGetValue(cacheKey, out Model.Observation existingObservation))
            {
                existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
            }

            Model.Observation result;
            if (existingObservation == null)
            {
                var newObservation = GenerateObservation(config, observationGroup, identifier, ids);
                result = await _client.CreateAsync <Model.Observation>(newObservation).ConfigureAwait(false);
            }
            else
            {
                var policyResult = await Policy <Model.Observation>
                                   .Handle <FhirOperationException>(ex => ex.Status == System.Net.HttpStatusCode.Conflict)
                                   .FallbackAsync(async ct =>
                {
                    var refreshObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
                    var mergedObservation  = MergeObservation(config, refreshObservation, observationGroup);
                    return(await _client.UpdateAsync(mergedObservation, versionAware: true).ConfigureAwait(false));
                })
                                   .ExecuteAndCaptureAsync(async() =>
                {
                    var mergedObservation = MergeObservation(config, existingObservation, observationGroup);
                    return(await _client.UpdateAsync(mergedObservation, versionAware: true).ConfigureAwait(false));
                }).ConfigureAwait(false);

                result = policyResult.Result;
            }

            _observationCache.CreateEntry(cacheKey)
            .SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddHours(1))
            .SetSize(1)
            .SetValue(result)
            .Dispose();

            return(result.Id);
        }
 protected override string CreateObservationImpl(CodeValueFhirTemplate template, IObservationGroup observationGroup)
 {
     return(TestCreateObseravtionImpl(template, observationGroup));
 }
Esempio n. 18
0
 public Observation(IObservationGroup group)
     : this((group ?? throw new ArgumentNullException(nameof(group))).Id)
 protected override string MergeObservationImpl(CodeValueFhirTemplate template, IObservationGroup observationGroup, string existingObservation)
 {
     return(TestMergeObservationImpl(template, observationGroup, existingObservation));
 }
Esempio n. 20
0
        public virtual async Task <string> SaveObservationAsync(ILookupTemplate <IFhirTemplate> config, IObservationGroup observationGroup, IDictionary <ResourceType, string> ids)
        {
            var identifier = GenerateObservationIdentifier(observationGroup, ids);
            var cacheKey   = $"{identifier.System}|{identifier.Value}";

            if (!_observationCache.TryGetValue(cacheKey, out Model.Observation existingObservation))
            {
                existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
            }

            Model.Observation result;
            if (existingObservation == null)
            {
                var newObservation = GenerateObservation(config, observationGroup, identifier, ids);
                result = await _client.CreateAsync(newObservation).ConfigureAwait(false);

                _logger.LogMetric(IomtMetrics.FhirResourceSaved(ResourceType.Observation, ResourceOperation.Created), 1);
            }
            else
            {
                var policyResult = await Policy <Model.Observation>
                                   .Handle <FhirOperationException>(ex => ex.Status == System.Net.HttpStatusCode.Conflict || ex.Status == System.Net.HttpStatusCode.PreconditionFailed)
                                   .RetryAsync(2, async(polyRes, attempt) =>
                {
                    existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
                })
                                   .ExecuteAndCaptureAsync(async() =>
                {
                    var mergedObservation = MergeObservation(config, existingObservation, observationGroup);
                    return(await _client.UpdateAsync(mergedObservation, versionAware: true).ConfigureAwait(false));
                }).ConfigureAwait(false);

                var exception = policyResult.FinalException;

                if (exception != null)
                {
                    throw exception;
                }

                result = policyResult.Result;
                _logger.LogMetric(IomtMetrics.FhirResourceSaved(ResourceType.Observation, ResourceOperation.Updated), 1);
            }

            _observationCache.CreateEntry(cacheKey)
            .SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddHours(1))
            .SetSize(1)
            .SetValue(result)
            .Dispose();

            return(result.Id);
        }
 public AttitudeObservation(
     IObservationGroup group,
     ITelloState state)
     : this(
         (group ?? throw new ArgumentNullException(nameof(group))).Id,
 public TObservation MergeObservation(IFhirTemplate template, IObservationGroup observationGroup, TObservation existingObservation)
 {
     return(MergeObservationImpl(CastTemplate(template), observationGroup, existingObservation));
 }
 public static (string Identifer, string System) TestGenerateObservationId(IObservationGroup observationGroup, string deviceId, string patientId)
 {
     return(GenerateObservationId(observationGroup, deviceId, patientId));
 }
 protected abstract TObservation MergeObservationImpl(TTemplate template, IObservationGroup observationGroup, TObservation existingObservation);
Esempio n. 25
0
        public virtual async Task <string> SaveObservationAsync(ILookupTemplate <IFhirTemplate> config, IObservationGroup observationGroup, IDictionary <ResourceType, string> ids)
        {
            var identifier = GenerateObservationIdentifier(observationGroup, ids);
            var cacheKey   = $"{identifier.System}|{identifier.Value}";

            if (!_observationCache.TryGetValue(cacheKey, out Model.Observation existingObservation))
            {
                existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);

                // Discovered an issue where FHIR Service is only matching on first 128 characters of the identifier.  This is a temporary measure to prevent merging of different observations until a fix is available.
                if (existingObservation != null && !existingObservation.Identifier.Exists(i => i.IsExactly(identifier)))
                {
                    throw new NotSupportedException("FHIR Service returned matching observation but expected identifier was not present.");
                }
            }

            var policyResult = await Policy <(Model.Observation observation, ResourceOperation operationType)>
                               .Handle <FhirException>(ex => ex.StatusCode == System.Net.HttpStatusCode.Conflict || ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed)
                               .RetryAsync(2, async(polyRes, attempt) =>
            {
                // 409 Conflict or 412 Precondition Failed can occur if the Observation.meta.versionId does not match the update request.
                // This can happen if 2 independent processes are updating the same Observation simultaneously.
                // or
                // The update operation failed because the Observation no longer exists.
                // This can happen if a cached Observation was deleted from the FHIR Server.

                _logger.LogTrace("A conflict or precondition caused an Observation update to fail. Getting the most recent Observation.");

                // Attempt to get the most recent version of the Observation.
                existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);

                // If the Observation no longer exists on the FHIR Server, it was most likely deleted.
                if (existingObservation == null)
                {
                    _logger.LogTrace("A cached version of an Observation was deleted. Creating a new Observation.");

                    // Remove the Observation from the cache (this version no longer exists on the FHIR Server.
                    _observationCache.Remove(cacheKey);
                }
            })
                               .ExecuteAndCaptureAsync(async() =>
            {
                if (existingObservation == null)
                {
                    var newObservation = GenerateObservation(config, observationGroup, identifier, ids);
                    return(await _fhirService.CreateResourceAsync(newObservation).ConfigureAwait(false), ResourceOperation.Created);
                }

                // Merge the new data with the existing Observation.
                var mergedObservation = MergeObservation(config, existingObservation, observationGroup);

                // Check to see if there are any changes after merging and update the Status to amended if changed.
                if (!existingObservation.AmendIfChanged(mergedObservation))
                {
                    // There are no changes to the Observation - Do not update.
                    return(existingObservation, ResourceOperation.NoOperation);
                }

                // Update the Observation. Some failures will be handled in the RetryAsync block above.
                return(await _fhirService.UpdateResourceAsync(mergedObservation).ConfigureAwait(false), ResourceOperation.Updated);
            }).ConfigureAwait(false);

            var exception = policyResult.FinalException;

            if (exception != null)
            {
                throw exception;
            }

            var observation = policyResult.Result.observation;

            _logger.LogMetric(IomtMetrics.FhirResourceSaved(ResourceType.Observation, policyResult.Result.operationType), 1);

            _observationCache.CreateEntry(cacheKey)
            .SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddHours(1))
            .SetSize(1)
            .SetValue(observation)
            .Dispose();

            return(observation.Id);
        }