public void CalculateProviderResult_WhenAllocationModelThrowsException_ShouldThrowException()
        {
            // Arrange
            CalculationEngine calcEngine = CreateCalculationEngine();

            IAllocationModel mockAllocationModel = Substitute.For <IAllocationModel>();

            mockAllocationModel
            .Execute(Arg.Any <List <ProviderSourceDataset> >(), Arg.Any <ProviderSummary>())
            .Throws(new DivideByZeroException());

            List <CalculationSummaryModel> models = new List <CalculationSummaryModel>
            {
                new CalculationSummaryModel()
                {
                    CalculationType = CalculationType.Number, Name = "Test", Id = "Test"
                }
            };

            ProviderSummary provider = new ProviderSummary();
            List <ProviderSourceDataset> sourceDataset = new List <ProviderSourceDataset>();

            // Act
            Action calculateProviderResultMethod = () =>
            {
                calcEngine.CalculateProviderResults(mockAllocationModel, CreateBuildProject(), models, provider, sourceDataset);
            };

            // Assert
            calculateProviderResultMethod
            .Should()
            .ThrowExactly <DivideByZeroException>();
        }
예제 #2
0
        public async Task <(IEnumerable <CalculationResult>, long)> Execute(string specificationId, string providerId)
        {
            AllocationFactory allocationFactory = new AllocationFactory(_logger, _featureToggles);

            IAllocationModel allocationModel = allocationFactory.CreateAllocationModel(typeof(Calculations).Assembly);

            IEnumerable <Models.Results.ProviderSourceDataset> providerSourceDatasetsResult = await _providerSourceDatasetsRepository.GetProviderSourceDatasetsByProviderIdsAndSpecificationId(new string[] { providerId }, specificationId);

            List <ProviderSourceDataset> providerSourceDatasets = new List <ProviderSourceDataset>(providerSourceDatasetsResult);

            IEnumerable <ProviderSummary> providers = await _providerService.FetchCoreProviderData();

            ProviderSummary provider = providers.FirstOrDefault(p => p.Id == providerId);

            if (provider == null)
            {
                throw new InvalidOperationException("Provider not found");
            }

            Stopwatch sw = Stopwatch.StartNew();

            IEnumerable <CalculationResult> calculationResults = allocationModel.Execute(providerSourceDatasets, provider, null);

            sw.Stop();

            return(calculationResults, sw.ElapsedMilliseconds);
        }
        public void CalculateProviderResult_WhenCalculationsAreNull_ShouldReturnResultWithEmptyCalculations()
        {
            // Arrange
            IAllocationModel mockAllocationModel = Substitute.For <IAllocationModel>();

            mockAllocationModel
            .Execute(
                Arg.Any <Dictionary <string, ProviderSourceDataset> >(),
                Arg.Any <ProviderSummary>(),
                Arg.Any <IEnumerable <CalculationAggregation> >())
            .Returns(new CalculationResultContainer
            {
                FundingLineResults = new List <FundingLineResult>()
            });

            CalculationEngine calculationEngine = CreateCalculationEngine();
            ProviderSummary   providerSummary   = CreateDummyProviderSummary();
            BuildProject      buildProject      = CreateBuildProject();

            // Act
            ProviderResult result = calculationEngine.CalculateProviderResults(
                mockAllocationModel,
                buildProject.SpecificationId,
                null,
                providerSummary,
                new Dictionary <string, ProviderSourceDataset>());

            // Assert
            result.CalculationResults.Should().BeNull();
            result.Provider.Should().Be(providerSummary);
            result.SpecificationId.Should().BeEquivalentTo(buildProject.SpecificationId);
            result.Id.Should().BeEquivalentTo(GenerateId(providerSummary.Id, buildProject.SpecificationId));
        }
        public void GenerateAllocations_GivenModelExecuteThrowsException_ThrowsException()
        {
            //Arrange
            BuildProject buildProject = CreateBuildProject();

            buildProject.Build.Assembly = MockData.GetMockAssembly();

            IEnumerable <ProviderSummary> providers = new[]
            {
                new ProviderSummary {
                    Id = ProviderId, Name = ProviderName
                }
            };

            IEnumerable <ProviderSourceDataset> datasets = new[]
            {
                new ProviderSourceDataset()
            };

            Func <string, string, Task <IEnumerable <ProviderSourceDataset> > > func = (s, p) =>
            {
                return(Task.FromResult(datasets));
            };

            IEnumerable <CalculationResult> calculationResults = new[]
            {
                new CalculationResult
                {
                    Calculation = new Reference {
                        Id = CalculationId
                    }
                }
            };

            IAllocationModel allocationModel = Substitute.For <IAllocationModel>();

            allocationModel
            .When(x => x.Execute(Arg.Any <List <ProviderSourceDataset> >(), Arg.Any <ProviderSummary>()))
            .Do(x => { throw new Exception(); });

            IAllocationFactory allocationFactory = Substitute.For <IAllocationFactory>();

            allocationFactory
            .CreateAllocationModel(Arg.Any <Assembly>())
            .Returns(allocationModel);

            ILogger logger = CreateLogger();

            CalculationEngine calculationEngine = CreateCalculationEngine(allocationFactory, logger: logger);

            //Act
            Func <Task> test = () => calculationEngine.GenerateAllocations(buildProject, providers, func);

            //Assert
            test
            .Should()
            .ThrowExactly <Exception>();
        }
        static IAllocationFactory CreateAllocationFactory(IAllocationModel allocationModel)
        {
            IAllocationFactory allocationFactory = Substitute.For <IAllocationFactory>();

            allocationFactory
            .CreateAllocationModel(Arg.Any <Assembly>())
            .Returns(allocationModel);

            return(allocationFactory);
        }
        public async Task PreviewCalculationResult_GivenProviderSummaryNotFound_ReturnsNotFound()
        {
            IAllocationModel allocationModel = Substitute.For <IAllocationModel>();

            _calculationEngine
            .GenerateAllocationModel(Arg.Any <Assembly>())
            .Returns(allocationModel);

            IEnumerable <string> dataDefinitionRelationshipIds = new List <string>();

            SpecificationSummary specificationSummary = new SpecificationSummary
            {
                DataDefinitionRelationshipIds = dataDefinitionRelationshipIds,
                ProviderVersionId             = providerVersionId
            };

            _specificationsApiClient
            .GetSpecificationSummaryById(Arg.Is(specificationId))
            .Returns(new ApiResponse <SpecificationSummary>(HttpStatusCode.OK, specificationSummary));

            _providersApiClient
            .GetProviderByIdFromProviderVersion(Arg.Is(providerVersionId), Arg.Is(providerId))
            .Returns(new ApiResponse <ProviderVersionSearchResult>(HttpStatusCode.OK, (ProviderVersionSearchResult)null));

            PreviewCalculationRequest previewCalculationRequest = new PreviewCalculationRequest
            {
                AssemblyContent = MockData.GetMockAssembly()
            };

            IActionResult actionResult =
                await _calculationEnginePreviewService.PreviewCalculationResult(specificationId, providerId, previewCalculationRequest);

            actionResult
            .Should()
            .BeOfType <NotFoundResult>();
        }
        async public Task GenerateAllocations_GivenBuildProject_Runs()
        {
            //Arrange
            BuildProject buildProject = CreateBuildProject();

            buildProject.Build.Assembly = MockData.GetMockAssembly();

            IEnumerable <ProviderSummary> providers = new[]
            {
                new ProviderSummary {
                    Id = ProviderId, Name = ProviderName
                }
            };

            IEnumerable <ProviderSourceDataset> datasets = new[]
            {
                new ProviderSourceDataset()
            };

            Func <string, string, Task <IEnumerable <ProviderSourceDataset> > > func = (s, p) =>
            {
                return(Task.FromResult(datasets));
            };

            IEnumerable <CalculationResult> calculationResults = new[]
            {
                new CalculationResult
                {
                    Calculation = new Reference {
                        Id = CalculationId
                    },
                }
            };

            IAllocationModel allocationModel = Substitute.For <IAllocationModel>();

            allocationModel
            .Execute(Arg.Any <List <ProviderSourceDataset> >(), Arg.Any <ProviderSummary>())
            .Returns(calculationResults);

            IAllocationFactory allocationFactory = Substitute.For <IAllocationFactory>();

            allocationFactory
            .CreateAllocationModel(Arg.Any <Assembly>())
            .Returns(allocationModel);

            ILogger logger = CreateLogger();

            ICalculationsRepository        calculationsRepository = CreateCalculationsRepository();
            List <CalculationSummaryModel> calculations           = new List <CalculationSummaryModel>()
            {
                new CalculationSummaryModel()
                {
                    Id = CalculationId,
                },
                new CalculationSummaryModel()
                {
                    Id = "calc2",
                },
                new CalculationSummaryModel()
                {
                    Id = "calc3",
                }
            };

            calculationsRepository
            .GetCalculationSummariesForSpecification(Arg.Any <string>())
            .Returns(calculations);

            CalculationEngine calculationEngine = CreateCalculationEngine(allocationFactory, calculationsRepository, logger: logger);

            //Act
            IEnumerable <ProviderResult> results = await calculationEngine.GenerateAllocations(buildProject, providers, func);

            //Assert
            results
            .Count()
            .Should()
            .Be(1);

            results
            .First()
            .CalculationResults
            .Count
            .Should()
            .Be(3);
        }
        public void CalculateProviderResult_WhenCalculationValuesReturnedWithMultipleAllocationLinesAndMixOfValuesAndNulls_ThenAllocationLineValuesAreSetCorrectly()
        {
            // Arrange
            List <Reference> policySpecificationsForFundingCalc = new List <Reference>()
            {
                new Reference("Spec1", "SpecOne"),
                new Reference("Spec2", "SpecTwo")
            };
            List <Reference> policySpecificationsForNumberCalc = new List <Reference>()
            {
                new Reference("Spec1", "SpecOne"),
            };
            Reference allocationLine1 = new Reference("allocationLine", "allocation line for Funding Calc and number calc");
            Reference allocationLine2 = new Reference("allocationLine2", "Second allocation line");
            Reference allocationLine3 = new Reference("allocationLine3", "Allocation line excluded");

            CalculationResult calc1 = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = new Reference("calc1", "Calc 1"),
                AllocationLine           = allocationLine1,
                CalculationSpecification = new Reference("FSpect", "FundingSpecification"),
                PolicySpecifications     = policySpecificationsForFundingCalc,
                Value = 10000
            };
            CalculationResult calc2 = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = new Reference("calc2", "Calc 2"),
                AllocationLine           = allocationLine1,
                CalculationSpecification = new Reference("FSpec2", "FundingSpecification2"),
                PolicySpecifications     = policySpecificationsForNumberCalc,
                Value = 20000
            };
            CalculationResult calc3 = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = new Reference("calc3", "Calc 3"),
                AllocationLine           = allocationLine2,
                CalculationSpecification = new Reference("calc3", "Calc 3"),
                PolicySpecifications     = policySpecificationsForNumberCalc,
                Value = 67
            };
            CalculationResult calc4 = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = new Reference("calc4", "Calc 4"),
                AllocationLine           = allocationLine3,
                CalculationSpecification = new Reference("calc4", "Calc 4"),
                PolicySpecifications     = policySpecificationsForNumberCalc,
                Value = null,
            };

            List <CalculationResult> calculationResults = new List <CalculationResult>()
            {
                calc1,
                calc2,
                calc3,
                calc4,
            };
            IAllocationModel mockAllocationModel = Substitute.For <IAllocationModel>();

            mockAllocationModel
            .Execute(Arg.Any <List <ProviderSourceDataset> >(), Arg.Any <ProviderSummary>())
            .Returns(calculationResults);

            CalculationEngine calculationEngine = CreateCalculationEngine();
            ProviderSummary   providerSummary   = CreateDummyProviderSummary();
            BuildProject      buildProject      = CreateBuildProject();


            IEnumerable <CalculationSummaryModel> calculationSummaryModels = new[]
            {
                new CalculationSummaryModel()
                {
                    Id              = "calc1",
                    Name            = "Calc 1",
                    CalculationType = CalculationType.Funding
                },
                new CalculationSummaryModel()
                {
                    Id              = "calc2",
                    Name            = "Calc 2",
                    CalculationType = CalculationType.Funding
                },
                new CalculationSummaryModel()
                {
                    Id              = "calc3",
                    Name            = "Calc 3",
                    CalculationType = CalculationType.Funding
                },
                new CalculationSummaryModel()
                {
                    Id              = "calc4",
                    Name            = "Calc 4",
                    CalculationType = CalculationType.Funding
                },
            };

            // Act
            var calculateProviderResults = calculationEngine.CalculateProviderResults(mockAllocationModel, buildProject, calculationSummaryModels,
                                                                                      providerSummary, new List <ProviderSourceDataset>());
            ProviderResult result = calculateProviderResults;

            // Assert
            result
            .AllocationLineResults
            .Should()
            .HaveCount(3);

            // Values are summed for allocation line
            result
            .AllocationLineResults[0]
            .Value
            .Should()
            .Be(30000);

            result
            .AllocationLineResults[1]
            .Value
            .Should()
            .Be(67);

            // All calculations for Allocation Line 3 returned nulls - therefore allocation line value is null
            result
            .AllocationLineResults[2]
            .Value
            .Should()
            .Be(null);
        }
        public void CalculateProviderResult_WhenCalculationsAreNotEmpty_ShouldReturnCorrectResult()
        {
            // Arrange
            List <Reference> policySpecificationsForFundingCalc = new List <Reference>()
            {
                new Reference("Spec1", "SpecOne"),
                new Reference("Spec2", "SpecTwo")
            };
            List <Reference> policySpecificationsForNumberCalc = new List <Reference>()
            {
                new Reference("Spec1", "SpecOne"),
            };
            Reference allocationLineReturned = new Reference("allocationLine", "allocation line for Funding Calc and number calc");

            Reference fundingCalcReference = new Reference("CalcF1", "Funding calc 1");
            Reference fundingCalcSpecificationReference = new Reference("FSpect", "FundingSpecification");

            Reference numbercalcReference = new Reference("CalcF2", "Funding calc 2");
            Reference numbercalcSpecificationReference = new Reference("FSpec2", "FundingSpecification2");

            CalculationResult fundingCalcReturned = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = fundingCalcReference,
                AllocationLine           = allocationLineReturned,
                CalculationSpecification = fundingCalcSpecificationReference,
                PolicySpecifications     = policySpecificationsForFundingCalc,
                Value = 10000
            };
            CalculationResult fundingCalcReturned2 = new CalculationResult()
            {
                CalculationType          = CalculationType.Funding,
                Calculation              = numbercalcReference,
                AllocationLine           = allocationLineReturned,
                CalculationSpecification = numbercalcSpecificationReference,
                PolicySpecifications     = policySpecificationsForNumberCalc,
                Value = 20000
            };

            List <CalculationResult> calculationResults = new List <CalculationResult>()
            {
                fundingCalcReturned,
                fundingCalcReturned2
            };
            IAllocationModel mockAllocationModel = Substitute.For <IAllocationModel>();

            mockAllocationModel
            .Execute(Arg.Any <List <ProviderSourceDataset> >(), Arg.Any <ProviderSummary>())
            .Returns(calculationResults);

            CalculationEngine calculationEngine = CreateCalculationEngine();
            ProviderSummary   providerSummary   = CreateDummyProviderSummary();
            BuildProject      buildProject      = CreateBuildProject();

            var nonMatchingCalculationModel = new CalculationSummaryModel()
            {
                Id              = "Non matching calculation",
                Name            = "Non matching calculation",
                CalculationType = CalculationType.Funding
            };
            IEnumerable <CalculationSummaryModel> calculationSummaryModels = new[]
            {
                new CalculationSummaryModel()
                {
                    Id              = fundingCalcReference.Id,
                    Name            = fundingCalcReference.Name,
                    CalculationType = CalculationType.Funding
                },
                new CalculationSummaryModel()
                {
                    Id              = numbercalcReference.Id,
                    Name            = numbercalcReference.Name,
                    CalculationType = CalculationType.Funding
                },
                nonMatchingCalculationModel
            };

            // Act
            var calculateProviderResults = calculationEngine.CalculateProviderResults(mockAllocationModel, buildProject, calculationSummaryModels,
                                                                                      providerSummary, new List <ProviderSourceDataset>());
            ProviderResult result = calculateProviderResults;

            // Assert
            result.Provider.Should().Be(providerSummary);
            result.SpecificationId.Should().BeEquivalentTo(buildProject.SpecificationId);
            result.Id.Should().BeEquivalentTo(GenerateId(providerSummary.Id, buildProject.SpecificationId));
            result.CalculationResults.Should().HaveCount(3);
            result.AllocationLineResults.Should().HaveCount(1);

            AllocationLineResult allocationLine = result.AllocationLineResults[0];

            allocationLine.Value = 30000;

            CalculationResult fundingCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == fundingCalcReference.Id);

            fundingCalcResult.Calculation.Should().BeEquivalentTo(fundingCalcReference);
            fundingCalcResult.CalculationType.Should().BeEquivalentTo(fundingCalcReturned.CalculationType);
            fundingCalcResult.AllocationLine.Should().BeEquivalentTo(allocationLineReturned);
            fundingCalcResult.CalculationSpecification.Should().BeEquivalentTo(fundingCalcSpecificationReference);
            fundingCalcResult.PolicySpecifications.Should().BeEquivalentTo(policySpecificationsForFundingCalc);
            fundingCalcResult.Value.Should().Be(fundingCalcReturned.Value.Value);

            CalculationResult numberCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == numbercalcReference.Id);

            numberCalcResult.Calculation.Should().BeEquivalentTo(numbercalcReference);
            numberCalcResult.CalculationType.Should().BeEquivalentTo(fundingCalcReturned2.CalculationType);
            numberCalcResult.AllocationLine.Should().BeEquivalentTo(allocationLineReturned);
            numberCalcResult.CalculationSpecification.Should().BeEquivalentTo(numbercalcSpecificationReference);
            numberCalcResult.PolicySpecifications.Should().BeEquivalentTo(policySpecificationsForNumberCalc);
            numberCalcResult.Value.Should().Be(fundingCalcReturned2.Value.Value);

            CalculationResult nonMatchingCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == "Non matching calculation");

            nonMatchingCalcResult.Calculation.Should().BeEquivalentTo(new Reference(nonMatchingCalculationModel.Id, nonMatchingCalculationModel.Name));
            nonMatchingCalcResult.CalculationType.Should().BeEquivalentTo(nonMatchingCalculationModel.CalculationType);
            nonMatchingCalcResult.AllocationLine.Should().BeNull();
            nonMatchingCalcResult.CalculationSpecification.Should().BeNull();
            nonMatchingCalcResult.PolicySpecifications.Should().BeNull();
            nonMatchingCalcResult.Value.Should().BeNull();
        }
예제 #10
0
        public ProviderResult CalculateProviderResults(IAllocationModel model, BuildProject buildProject, IEnumerable <CalculationSummaryModel> calculations,
                                                       ProviderSummary provider, IEnumerable <ProviderSourceDataset> providerSourceDatasets, IEnumerable <CalculationAggregation> aggregations = null)
        {
            var stopwatch = new Stopwatch();

            stopwatch.Start();

            IEnumerable <CalculationResult> calculationResults = model.Execute(providerSourceDatasets != null ? providerSourceDatasets.ToList() : new List <ProviderSourceDataset>(), provider, aggregations).ToArray();

            var providerCalResults = calculationResults.ToDictionary(x => x.Calculation?.Id);

            stopwatch.Stop();

            if (providerCalResults.Count > 0)
            {
                _logger.Debug($"{providerCalResults.Count} calcs in {stopwatch.ElapsedMilliseconds}ms ({stopwatch.ElapsedMilliseconds / providerCalResults.Count: 0.0000}ms)");
            }
            else
            {
                _logger.Information("There are no calculations to executed for specification ID {specificationId}", buildProject.SpecificationId);
            }

            ProviderResult providerResult = new ProviderResult
            {
                Provider        = provider,
                SpecificationId = buildProject.SpecificationId
            };

            byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes($"{providerResult.Provider.Id}-{providerResult.SpecificationId}");
            providerResult.Id = Convert.ToBase64String(plainTextBytes);

            List <CalculationResult> results = new List <CalculationResult>();

            if (calculations != null)
            {
                foreach (CalculationSummaryModel calculation in calculations)
                {
                    CalculationResult result = new CalculationResult
                    {
                        Calculation     = calculation.GetReference(),
                        CalculationType = calculation.CalculationType,
                        Version         = calculation.Version
                    };

                    if (providerCalResults.TryGetValue(calculation.Id, out CalculationResult calculationResult))
                    {
                        result.CalculationSpecification = calculationResult.CalculationSpecification;
                        if (calculationResult.AllocationLine != null)
                        {
                            result.AllocationLine = calculationResult.AllocationLine;
                        }

                        result.PolicySpecifications = calculationResult.PolicySpecifications;

                        // The default for the calculation is to return Decimal.MinValue - if this is the case, then subsitute a 0 value as the result, instead of the negative number.
                        if (calculationResult.Value != decimal.MinValue)
                        {
                            result.Value = calculationResult.Value;
                        }
                        else
                        {
                            result.Value = 0;
                        }

                        result.ExceptionType    = calculationResult.ExceptionType;
                        result.ExceptionMessage = calculationResult.ExceptionMessage;
                    }

                    results.Add(result);
                }
            }

            providerResult.CalculationResults = results.ToList();

            providerResult.AllocationLineResults = results.Where(x => x.CalculationType == CalculationType.Funding && x.AllocationLine != null)
                                                   .GroupBy(x => x.AllocationLine).Select(x => new AllocationLineResult
            {
                AllocationLine = x.Key,
                Value          = x.All(v => !v.Value.HasValue) ? (decimal?)null : x.Sum(v => v.Value ?? decimal.Zero)
            }).ToList();

            return(providerResult);
        }
        public async Task PreviewCalculationResult_GivenCachedAggregateValuesExist_CalculateProviderResultsCallReceived()
        {
            IAllocationModel allocationModel = Substitute.For <IAllocationModel>();

            _calculationEngine
            .GenerateAllocationModel(Arg.Any <Assembly>())
            .Returns(allocationModel);

            ProviderVersionSearchResult providerVersionSearchResult = new ProviderVersionSearchResult
            {
                UKPRN = providerId
            };

            IEnumerable <string> dataDefinitionRelationshipIds = new List <string>();

            SpecificationSummary specificationSummary = new SpecificationSummary
            {
                DataDefinitionRelationshipIds = dataDefinitionRelationshipIds,
                ProviderVersionId             = providerVersionId
            };

            _specificationsApiClient
            .GetSpecificationSummaryById(Arg.Is(specificationId))
            .Returns(new ApiResponse <SpecificationSummary>(HttpStatusCode.OK, specificationSummary));

            _providersApiClient
            .GetProviderByIdFromProviderVersion(Arg.Is(providerVersionId), Arg.Is(providerId))
            .Returns(new ApiResponse <ProviderVersionSearchResult>(HttpStatusCode.OK, providerVersionSearchResult));

            CalculationSummaryModel previewCalculationSummaryModel = new CalculationSummaryModel();

            IEnumerable <CalculationSummaryModel> calculationSummaryModels = new List <CalculationSummaryModel>
            {
                new CalculationSummaryModel(),
                new CalculationSummaryModel()
            };

            List <CalculationSummaryModel> expectedCalculationSummaryModels = calculationSummaryModels.ToList();

            expectedCalculationSummaryModels.Add(previewCalculationSummaryModel);

            _calculationsRepository
            .GetCalculationSummariesForSpecification(Arg.Is(specificationId))
            .Returns(calculationSummaryModels);

            Dictionary <string, ProviderSourceDataset> sourceDatasets = new Dictionary <string, ProviderSourceDataset>();

            Dictionary <string, Dictionary <string, ProviderSourceDataset> > providerSourceDatasets = new Dictionary <string, Dictionary <string, ProviderSourceDataset> >
            {
                { providerId, sourceDatasets }
            };

            _providerSourceDatasetsRepository
            .GetProviderSourceDatasetsByProviderIdsAndRelationshipIds(
                Arg.Is(specificationId),
                Arg.Is <IEnumerable <string> >(_ => _ != null && _.Count() == 1 && _.FirstOrDefault() == providerId),
                Arg.Is <IEnumerable <string> >(_ => _ != null && _.SequenceEqual(dataDefinitionRelationshipIds)))
            .Returns(providerSourceDatasets);

            IEnumerable <CalculationAggregation> calculationAggregations = new List <CalculationAggregation>();

            _calculationAggregationService
            .BuildAggregations(Arg.Is <BuildAggregationRequest>(_ => _ != null && _.SpecificationId == specificationId))
            .Returns(calculationAggregations);

            PreviewCalculationRequest previewCalculationRequest = new PreviewCalculationRequest
            {
                AssemblyContent = MockData.GetMockAssembly(),
                PreviewCalculationSummaryModel = previewCalculationSummaryModel
            };

            IActionResult actionResult =
                await _calculationEnginePreviewService.PreviewCalculationResult(specificationId, providerId, previewCalculationRequest);

            _calculationEngine
            .Received(1)
            .CalculateProviderResults(
                Arg.Is(allocationModel),
                specificationId,
                Arg.Is <IEnumerable <CalculationSummaryModel> >(_ => _.SequenceEqual(expectedCalculationSummaryModels)),
                Arg.Is <ProviderSummary>(_ => _.UKPRN == providerId),
                Arg.Is <Dictionary <string, ProviderSourceDataset> >(_ => _.SequenceEqual(sourceDatasets)),
                Arg.Is <IEnumerable <CalculationAggregation> >(_ => _.SequenceEqual(calculationAggregations)));
        }
        private async Task <CalculationResultsModel> CalculateResults(string specificationId, IEnumerable <ProviderSummary> summaries,
                                                                      IEnumerable <CalculationSummaryModel> calculations,
                                                                      IEnumerable <CalculationAggregation> aggregations,
                                                                      IEnumerable <string> dataRelationshipIds,
                                                                      byte[] assemblyForSpecification,
                                                                      GenerateAllocationMessageProperties messageProperties,
                                                                      int providerBatchSize,
                                                                      int index)
        {
            ConcurrentBag <ProviderResult> providerResults = new ConcurrentBag <ProviderResult>();

            Guard.ArgumentNotNull(summaries, nameof(summaries));

            IEnumerable <ProviderSummary> partitionedSummaries = summaries.Skip(index).Take(providerBatchSize);

            IList <string> providerIdList = partitionedSummaries.Select(m => m.Id).ToList();

            Stopwatch providerSourceDatasetsStopwatch = Stopwatch.StartNew();

            _logger.Information($"Fetching provider sources for specification id {messageProperties.SpecificationId}");

            Dictionary <string, Dictionary <string, ProviderSourceDataset> > providerSourceDatasetResult = await _providerSourceDatasetsRepositoryPolicy.ExecuteAsync(
                () => _providerSourceDatasetsRepository.GetProviderSourceDatasetsByProviderIdsAndRelationshipIds(specificationId, providerIdList, dataRelationshipIds));

            providerSourceDatasetsStopwatch.Stop();

            _logger.Information($"Fetched provider sources found for specification id {messageProperties.SpecificationId}");


            _logger.Information($"Calculating results for specification id {messageProperties.SpecificationId}");
            Stopwatch assemblyLoadStopwatch = Stopwatch.StartNew();
            Assembly  assembly = Assembly.Load(assemblyForSpecification);

            assemblyLoadStopwatch.Stop();

            Stopwatch calculationStopwatch = Stopwatch.StartNew();

            List <Task>   allTasks  = new List <Task>();
            SemaphoreSlim throttler = new SemaphoreSlim(_engineSettings.CalculateProviderResultsDegreeOfParallelism);

            IAllocationModel allocationModel = _calculationEngine.GenerateAllocationModel(assembly);

            foreach (ProviderSummary provider in partitionedSummaries)
            {
                await throttler.WaitAsync();

                allTasks.Add(
                    Task.Run(() =>
                {
                    try
                    {
                        if (provider == null)
                        {
                            throw new Exception("Provider summary is null");
                        }

                        if (providerSourceDatasetResult.AnyWithNullCheck())
                        {
                            if (!providerSourceDatasetResult.TryGetValue(provider.Id, out Dictionary <string, ProviderSourceDataset> providerDatasets))
                            {
                                throw new Exception($"Provider source dataset not found for {provider.Id}.");
                            }

                            ProviderResult result = _calculationEngine.CalculateProviderResults(allocationModel, specificationId, calculations, provider, providerDatasets, aggregations);

                            if (result == null)
                            {
                                throw new InvalidOperationException("Null result from Calc Engine CalculateProviderResults");
                            }

                            providerResults.Add(result);
                        }
                    }
                    finally
                    {
                        throttler.Release();
                    }

                    return(Task.CompletedTask);
                }));
            }

            await TaskHelper.WhenAllAndThrow(allTasks.ToArray());

            calculationStopwatch.Stop();

            _logger.Information($"Calculating results complete for specification id {messageProperties.SpecificationId} in {calculationStopwatch.ElapsedMilliseconds}ms");

            return(new CalculationResultsModel
            {
                ProviderResults = providerResults,
                PartitionedSummaries = partitionedSummaries,
                CalculationRunMs = calculationStopwatch.ElapsedMilliseconds,
                AssemblyLoadMs = assemblyLoadStopwatch.ElapsedMilliseconds,
                ProviderSourceDatasetsLookupMs = providerSourceDatasetsStopwatch.ElapsedMilliseconds,
            });
        }
예제 #13
0
        public ProviderResult CalculateProviderResults(
            IAllocationModel model,
            string specificationId,
            IEnumerable <CalculationSummaryModel> calculations,
            ProviderSummary provider,
            IDictionary <string, ProviderSourceDataset> providerSourceDatasets,
            IEnumerable <CalculationAggregation> aggregations = null)
        {
            var stopwatch = new Stopwatch();

            stopwatch.Start();

            CalculationResultContainer calculationResultContainer = model.Execute(providerSourceDatasets, provider, aggregations);

            IEnumerable <CalculationResult> calculationResultItems = calculationResultContainer.CalculationResults;

            stopwatch.Stop();

            IDictionary <string, double> metrics = new Dictionary <string, double>()
            {
                { "calculation-provider-calcsMs", stopwatch.ElapsedMilliseconds },
                { "calculation-provider-calcsTotal", calculations.AnyWithNullCheck() ? calculations.Count() : 0 },
                { "calculation-provider-exceptions", calculationResultItems.AnyWithNullCheck() ? calculationResultItems.Count(c => !string.IsNullOrWhiteSpace(c.ExceptionMessage)) : 0 },
            };

            _telemetry.TrackEvent("CalculationRunProvider",
                                  new Dictionary <string, string>()
            {
                { "specificationId", specificationId },
            },
                                  metrics
                                  );

            if (calculationResultItems.AnyWithNullCheck() && calculationResultItems.Count() > 0)
            {
                _logger.Verbose($"Processed results for {calculationResultItems.Count()} calcs in {stopwatch.ElapsedMilliseconds}ms ({stopwatch.ElapsedMilliseconds / calculationResultItems.Count(): 0.0000}ms)");
            }
            else
            {
                _logger.Information("There are no calculations to executed for specification ID {specificationId}", specificationId);
            }

            byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes($"{provider.Id}-{specificationId}");

            ProviderResult providerResult = new ProviderResult
            {
                Id              = Convert.ToBase64String(plainTextBytes),
                Provider        = provider,
                SpecificationId = specificationId
            };

            if (calculationResultItems.AnyWithNullCheck())
            {
                foreach (CalculationResult calcResult in calculationResultItems)
                {
                    CalculationSummaryModel calculationSummaryModel = calculations.First(c => c.Id == calcResult.Calculation.Id);

                    calcResult.CalculationType     = calculationSummaryModel.CalculationType;
                    calcResult.CalculationDataType = calculationSummaryModel.CalculationValueType.ToCalculationDataType();

                    if (calcResult.CalculationDataType == CalculationDataType.Decimal && Decimal.Equals(decimal.MinValue, calcResult.Value))
                    {
                        // The default for the calculation is to return Decimal.MinValue - if this is the case, then subsitute a 0 value as the result, instead of the negative number.
                        calcResult.Value = 0;
                    }
                }
            }

            //we need a stable sort of results to enable the cache checks by overall SHA hash on the results json
            providerResult.CalculationResults = calculationResultContainer.CalculationResults?.OrderBy(_ => _.Calculation.Id).ToList();
            providerResult.FundingLineResults = calculationResultContainer.FundingLineResults.OrderBy(_ => _.FundingLine.Id).ToList();

            return(providerResult);
        }
        public void CalculateProviderResult_WhenCalculationsAreNotEmpty_ShouldReturnCorrectResult()
        {
            // Arrange

            List <Reference> policySpecificationsForFundingCalc = new List <Reference>()
            {
                new Reference("Spec1", "SpecOne"),
                new Reference("Spec2", "SpecTwo")
            };

            Reference fundingCalcReference = new Reference("CalcF1", "Funding calc 1");

            Reference numbercalcReference = new Reference("CalcF2", "Funding calc 2");

            Reference booleancalcReference = new Reference("CalcF3", "Funding calc 3");

            Reference fundingLineCalcReference = new Reference("FL1", "Funding line calc 1");

            CalculationResult fundingCalcReturned = new CalculationResult()
            {
                Calculation = fundingCalcReference,
                Value       = 10000
            };
            CalculationResult fundingCalcReturned2 = new CalculationResult()
            {
                Calculation = numbercalcReference,
                Value       = 20000
            };
            CalculationResult fundingCalcReturned3 = new CalculationResult()
            {
                Calculation = booleancalcReference,
                Value       = true
            };

            CalculationResultContainer calculationResultContainer = new CalculationResultContainer();

            List <CalculationResult> calculationResults = new List <CalculationResult>()
            {
                fundingCalcReturned,
                fundingCalcReturned2,
                fundingCalcReturned3
            };

            calculationResultContainer.CalculationResults = calculationResults;

            string fundingStreamId = "FS1";

            FundingLineResult fundingLineResult = new FundingLineResult
            {
                Value       = 1000,
                FundingLine = fundingLineCalcReference,
                FundingLineFundingStreamId = fundingStreamId
            };

            List <FundingLineResult> fundingLineResults = new List <FundingLineResult>
            {
                fundingLineResult
            };

            calculationResultContainer.FundingLineResults = fundingLineResults;

            IAllocationModel mockAllocationModel = Substitute.For <IAllocationModel>();

            mockAllocationModel
            .Execute(Arg.Any <Dictionary <string, ProviderSourceDataset> >(), Arg.Any <ProviderSummary>())
            .Returns(calculationResultContainer);

            CalculationEngine calculationEngine = CreateCalculationEngine();
            ProviderSummary   providerSummary   = CreateDummyProviderSummary();
            BuildProject      buildProject      = CreateBuildProject();

            var nonMatchingCalculationModel = new CalculationSummaryModel()
            {
                Id                   = "Non matching calculation",
                Name                 = "Non matching calculation",
                CalculationType      = CalculationType.Template,
                CalculationValueType = CalculationValueType.Number
            };
            IEnumerable <CalculationSummaryModel> calculationSummaryModels = new[]
            {
                new CalculationSummaryModel()
                {
                    Id                   = fundingCalcReference.Id,
                    Name                 = fundingCalcReference.Name,
                    CalculationType      = CalculationType.Template,
                    CalculationValueType = CalculationValueType.Number
                },
                new CalculationSummaryModel()
                {
                    Id                   = numbercalcReference.Id,
                    Name                 = numbercalcReference.Name,
                    CalculationType      = CalculationType.Template,
                    CalculationValueType = CalculationValueType.Number
                },
                new CalculationSummaryModel()
                {
                    Id                   = booleancalcReference.Id,
                    Name                 = booleancalcReference.Name,
                    CalculationType      = CalculationType.Template,
                    CalculationValueType = CalculationValueType.Boolean
                },
                nonMatchingCalculationModel
            };

            // Act
            var calculateProviderResults = calculationEngine.CalculateProviderResults(mockAllocationModel, buildProject.SpecificationId, calculationSummaryModels,
                                                                                      providerSummary, new Dictionary <string, ProviderSourceDataset>());
            ProviderResult result = calculateProviderResults;

            // Assert
            result.Provider.Should().Be(providerSummary);
            result.SpecificationId.Should().BeEquivalentTo(buildProject.SpecificationId);
            result.Id.Should().BeEquivalentTo(GenerateId(providerSummary.Id, buildProject.SpecificationId));
            result.CalculationResults.Should().HaveCount(3);
            result.FundingLineResults.Should().HaveCount(1);

            CalculationResult fundingCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == fundingCalcReference.Id);

            fundingCalcResult.Calculation.Should().BeEquivalentTo(fundingCalcReference);
            fundingCalcResult.CalculationType.Should().BeEquivalentTo(CalculationType.Template);
            fundingCalcResult.Value.Should().Be(fundingCalcReturned.Value);
            fundingCalcResult.CalculationDataType.Should().Be(CalculationDataType.Decimal);

            CalculationResult numberCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == numbercalcReference.Id);

            numberCalcResult.Calculation.Should().BeEquivalentTo(numbercalcReference);
            numberCalcResult.CalculationType.Should().BeEquivalentTo(CalculationType.Template);
            numberCalcResult.Value.Should().Be(fundingCalcReturned2.Value);
            numberCalcResult.CalculationDataType.Should().Be(CalculationDataType.Decimal);

            CalculationResult booleanCalcResult = result.CalculationResults.First(cr => cr.Calculation.Id == booleancalcReference.Id);

            booleanCalcResult.Calculation.Should().BeEquivalentTo(booleancalcReference);
            booleanCalcResult.CalculationType.Should().BeEquivalentTo(CalculationType.Template);
            booleanCalcResult.Value.Should().Be(fundingCalcReturned3.Value);
            booleanCalcResult.CalculationDataType.Should().Be(CalculationDataType.Boolean);

            FundingLineResult fundingLineCalcResult = result.FundingLineResults.First(cr => cr.FundingLine.Id == fundingLineCalcReference.Id);

            fundingLineCalcResult.FundingLine.Should().BeEquivalentTo(fundingLineCalcReference);
            fundingLineCalcResult.Value.Should().Be(fundingLineResult.Value);
            fundingLineCalcResult.FundingLineFundingStreamId.Should().Be(fundingStreamId);
        }
예제 #15
0
        public async Task <IActionResult> PreviewCalculationResult(
            string specificationId,
            string providerId,
            PreviewCalculationRequest previewCalculationRequest)
        {
            Guard.IsNullOrWhiteSpace(specificationId, nameof(specificationId));
            Guard.IsNullOrWhiteSpace(providerId, nameof(providerId));
            Guard.ArgumentNotNull(previewCalculationRequest, nameof(previewCalculationRequest));

            Assembly         assembly        = Assembly.Load(previewCalculationRequest.AssemblyContent);
            IAllocationModel allocationModel = _calculationEngine.GenerateAllocationModel(assembly);

            SpecificationSummary specificationSummary = await GetSpecificationSummary(specificationId);

            ApiResponse <ProviderVersionSearchResult> providerVersionSearchResultApiResponse =
                await _providersApiClientPolicy.ExecuteAsync(() => _providersApiClient.GetProviderByIdFromProviderVersion(
                                                                 specificationSummary.ProviderVersionId,
                                                                 providerId));

            ProviderVersionSearchResult providerVersionSearchResult = providerVersionSearchResultApiResponse.Content;

            if (providerVersionSearchResult == null)
            {
                return(new NotFoundResult());
            }

            ProviderSummary providerSummary = _mapper.Map <ProviderSummary>(providerVersionSearchResult);

            List <CalculationSummaryModel>        calculationSummaries     = new List <CalculationSummaryModel>();
            IEnumerable <CalculationSummaryModel> specCalculationSummaries = await GetCalculationSummaries(specificationId);

            calculationSummaries.AddRange(specCalculationSummaries);
            calculationSummaries.Add(previewCalculationRequest.PreviewCalculationSummaryModel);

            Dictionary <string, Dictionary <string, ProviderSourceDataset> > providerSourceDatasets =
                await _providerSourceDatasetsRepository.GetProviderSourceDatasetsByProviderIdsAndRelationshipIds(
                    specificationId,
                    new[] { providerId },
                    specificationSummary.DataDefinitionRelationshipIds);

            Dictionary <string, ProviderSourceDataset> providerSourceDataset = providerSourceDatasets[providerId];

            BuildAggregationRequest buildAggregationRequest = new BuildAggregationRequest
            {
                SpecificationId = specificationId,
                GenerateCalculationAggregationsOnly = true,
                BatchCount = 100
            };
            IEnumerable <CalculationAggregation> calculationAggregations =
                await _calculationAggregationService.BuildAggregations(buildAggregationRequest);

            ProviderResult providerResult = _calculationEngine.CalculateProviderResults(
                allocationModel,
                specificationId,
                calculationSummaries,
                providerSummary,
                providerSourceDataset,
                calculationAggregations
                );

            return(new OkObjectResult(providerResult));
        }
        private async Task <CalculationResultsModel> CalculateResults(IEnumerable <ProviderSummary> summaries, IEnumerable <CalculationSummaryModel> calculations, IEnumerable <CalculationAggregation> aggregations, BuildProject buildProject,
                                                                      GenerateAllocationMessageProperties messageProperties, int providerBatchSize, int index, Stopwatch providerSourceDatasetsStopwatch, Stopwatch calculationStopwatch)
        {
            ConcurrentBag <ProviderResult> providerResults = new ConcurrentBag <ProviderResult>();

            IEnumerable <ProviderSummary> partitionedSummaries = summaries.Skip(index).Take(providerBatchSize);

            IList <string> providerIdList = partitionedSummaries.Select(m => m.Id).ToList();

            providerSourceDatasetsStopwatch.Start();

            _logger.Information($"Fetching provider sources for specification id {messageProperties.SpecificationId}");

            List <ProviderSourceDataset> providerSourceDatasets = new List <ProviderSourceDataset>(await _providerSourceDatasetsRepositoryPolicy.ExecuteAsync(() => _providerSourceDatasetsRepository.GetProviderSourceDatasetsByProviderIdsAndSpecificationId(providerIdList, messageProperties.SpecificationId)));

            providerSourceDatasetsStopwatch.Stop();

            if (providerSourceDatasets == null)
            {
                _logger.Information($"No provider sources found for specification id {messageProperties.SpecificationId}");

                providerSourceDatasets = new List <ProviderSourceDataset>();
            }

            _logger.Information($"fetched provider sources found for specification id {messageProperties.SpecificationId}");

            calculationStopwatch.Start();

            _logger.Information($"calculating results for specification id {messageProperties.SpecificationId}");

            Assembly assembly = Assembly.Load(buildProject.Build.Assembly);

            Parallel.ForEach(partitionedSummaries, new ParallelOptions {
                MaxDegreeOfParallelism = _engineSettings.CalculateProviderResultsDegreeOfParallelism
            }, provider =>
            {
                IAllocationModel allocationModel = _calculationEngine.GenerateAllocationModel(assembly);

                IEnumerable <ProviderSourceDataset> providerDatasets = providerSourceDatasets.Where(m => m.ProviderId == provider.Id);

                ProviderResult result = _calculationEngine.CalculateProviderResults(allocationModel, buildProject, calculations, provider, providerDatasets, aggregations);

                if (result != null)
                {
                    providerResults.Add(result);
                }
                else
                {
                    throw new InvalidOperationException("Null result from Calc Engine CalculateProviderResults");
                }
            });


            _logger.Information($"calculating results complete for specification id {messageProperties.SpecificationId}");

            calculationStopwatch.Stop();

            return(new CalculationResultsModel
            {
                ProviderResults = providerResults,
                PartitionedSummaries = partitionedSummaries
            });
        }