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); }
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); }
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, }); }
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); }
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,
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)); }
public virtual Model.Observation MergeObservation(ILookupTemplate <IFhirTemplate> config, Model.Observation observation, IObservationGroup grp) { return(_fhirTemplateProcessor.MergeObservation(config, grp, observation)); }
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)); }
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)); }
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);
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); }