├── .github └── workflows │ └── release-sdk.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Client ├── Program.cs └── TrendsCalculator.Client.csproj ├── Img&Graphs ├── Result.JPG ├── countGraph.JPG ├── demoModel.JPG ├── globalZGraph.JPG └── globalZModels.JPG ├── LICENSE ├── Library ├── Core │ ├── CategoryDivisionOfModels.cs │ ├── ColumnSegmentation.cs │ ├── GlobalZCalculationCriterias │ │ ├── GlobalZCalculationCustomCriteria.cs │ │ ├── GlobalZCalculationZMeanCriteria.cs │ │ └── IGlobalZCalculationCriteria.cs │ ├── LocalZValueCalculation.cs │ └── SortingCombiningResults.cs ├── Helper │ ├── CalculationHelper.cs │ └── Extention.cs ├── Interfaces │ └── TModel.cs ├── Sorter │ └── GlobalZSorter.cs ├── TrendCalculatorStrategies │ ├── AbstractTrendingCalculator.cs │ ├── CustomTrendingCalculator.cs │ ├── DemandSupplyTrendingCalculator.cs │ ├── TrendCalculationStrategy.cs │ └── ZMeanTrendingCalculator.cs ├── TrendsCalculator.Library.csproj └── TrendsCalculator.cs ├── README.md ├── SECURITY.md ├── Test ├── TestInputModel.cs ├── TrendingCalculatorCustomCriteriaTest.cs ├── TrendingCalculatorDemandSupplyCriteriaTest.cs ├── TrendingCalculatorZMeanCriteriaTest.cs ├── TrendsCalculator.Test.csproj └── TrendsCalculatorTest.cs └── TrendsCalculator.sln /.github/workflows/release-sdk.yml: -------------------------------------------------------------------------------- 1 | name: Release Package 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Setup .NET 11 | uses: actions/setup-dotnet@v1 12 | with: 13 | dotnet-version: 5.0.x 14 | - name: Install dependencies 15 | run: dotnet restore Library/TrendsCalculator.Library.csproj 16 | - name: Build 17 | run: dotnet build Library/TrendsCalculator.Library.csproj --no-restore 18 | - name: Test 19 | run: dotnet test Test/TrendsCalculator.Test.csproj --no-restore --verbosity normal 20 | - name: Pack 21 | run: dotnet pack Library/TrendsCalculator.Library.csproj 22 | - name: Publish 23 | run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using TrendsCalculator.Library; 4 | using TrendsCalculator.Library.Core.Strategy; 5 | using TrendsCalculator.Library.Interfaces; 6 | 7 | namespace TrendsCalculator.Client 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | Console.WriteLine("Hello World!"); 14 | var trendingCalculator = new TrendsCalculator.Library.TrendsCalculator(); 15 | var inputData = DataPreparator.PrepareData(); 16 | var output = trendingCalculator.FindTrendingData(6, 1, inputData); 17 | Console.WriteLine("Sorted per Demand Supply criteria"); 18 | } 19 | 20 | internal class TestInputModel : TDemandSupplyModel 21 | { 22 | public string MovieName { get; set; } 23 | } 24 | 25 | internal static class DataPreparator 26 | { 27 | internal static List PrepareData() 28 | { 29 | return new List() 30 | { 31 | new TestInputModel() 32 | { 33 | SupplyQuantity=1, 34 | CountWithPeriods = new List(){ 23, 34, 56, 67, 78, 89 }, 35 | MovieName = "ABC" 36 | 37 | }, 38 | new TestInputModel() 39 | { 40 | SupplyQuantity=4, 41 | CountWithPeriods = new List(){ 1, 2, 6, 3, 1, 7 }, 42 | MovieName = "XYZ" 43 | }, 44 | new TestInputModel() 45 | { 46 | SupplyQuantity=0, 47 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3, 12 }, 48 | MovieName = "PQR" 49 | } 50 | 51 | }; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Client/TrendsCalculator.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Img&Graphs/Result.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TrendCalculator/3773510bbe54a95615a6008888af9519f15e2cb6/Img&Graphs/Result.JPG -------------------------------------------------------------------------------- /Img&Graphs/countGraph.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TrendCalculator/3773510bbe54a95615a6008888af9519f15e2cb6/Img&Graphs/countGraph.JPG -------------------------------------------------------------------------------- /Img&Graphs/demoModel.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TrendCalculator/3773510bbe54a95615a6008888af9519f15e2cb6/Img&Graphs/demoModel.JPG -------------------------------------------------------------------------------- /Img&Graphs/globalZGraph.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TrendCalculator/3773510bbe54a95615a6008888af9519f15e2cb6/Img&Graphs/globalZGraph.JPG -------------------------------------------------------------------------------- /Img&Graphs/globalZModels.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/TrendCalculator/3773510bbe54a95615a6008888af9519f15e2cb6/Img&Graphs/globalZModels.JPG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Library/Core/CategoryDivisionOfModels.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using TrendsCalculator.Library.Interfaces; 6 | 7 | namespace TrendsCalculator.Library.AlgoComponents 8 | { 9 | internal class CategoryDivisionOfModels where T : TModel 10 | { 11 | public List> GetModelsIntoCategory(List<(T item, double localZ, double globalZ)> trendingModels) 12 | { 13 | var bothGlobalZLocalZ_Positive_1 = new List<(T item, double localZ, double globalZ)>(); 14 | var bothGlobalZLocalZ_Alternate_2 = new List<(T item, double localZ, double globalZ)>(); 15 | var bothGlobalZLocalZ_Negative_3 = new List<(T item, double localZ, double globalZ)>(); 16 | 17 | foreach (var model in trendingModels) 18 | { 19 | if (model.globalZ >= 0 && model.localZ >= 0) 20 | bothGlobalZLocalZ_Positive_1.Add(model); 21 | else 22 | if ((model.globalZ >= 0 && model.localZ < 0) || (model.globalZ < 0 && model.localZ >= 0)) 23 | bothGlobalZLocalZ_Alternate_2.Add(model); 24 | else 25 | bothGlobalZLocalZ_Negative_3.Add(model); 26 | 27 | } 28 | 29 | List> listTrendingModels = new List> 30 | { 31 | bothGlobalZLocalZ_Positive_1, 32 | bothGlobalZLocalZ_Alternate_2, 33 | bothGlobalZLocalZ_Negative_3 34 | }; 35 | 36 | return listTrendingModels; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Library/Core/ColumnSegmentation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using TrendsCalculator.Library.Interfaces; 5 | 6 | namespace TrendsCalculator.Library.AlgoComponents 7 | { 8 | internal class ColumnSegmentation 9 | { 10 | internal List GetHistoricalSegmentColumns(int windowPeriod, int noOfColumns, int numberOfSegmentsOfEachUnit) 11 | { 12 | List historicalSegmentColumns = new List(); 13 | int referenceColumn = 0; 14 | int historicalPeriodUnits = (windowPeriod % 2 == 0) ? windowPeriod / 2 : (windowPeriod / 2) + 1; 15 | for (int j = 1; j <= historicalPeriodUnits; j++) 16 | { 17 | for (int i = 1; i <= numberOfSegmentsOfEachUnit; i++) 18 | { 19 | historicalSegmentColumns.Add(referenceColumn); 20 | referenceColumn += 1; 21 | } 22 | } 23 | return historicalSegmentColumns; 24 | } 25 | 26 | internal List GetTrendingSegmentColumns(int windowPeriod, int noOfColumns, int numberOfSegmentsOfEachUnit) 27 | { 28 | List trendingSegmentColumns = new List(); 29 | int noOfTrendingUnits = 0; 30 | int historicalPeriodUnits = (windowPeriod % 2 == 0) ? windowPeriod / 2 : (windowPeriod / 2) + 1; 31 | if (windowPeriod * numberOfSegmentsOfEachUnit == noOfColumns) 32 | noOfTrendingUnits = windowPeriod - historicalPeriodUnits; 33 | else 34 | noOfTrendingUnits = windowPeriod - historicalPeriodUnits + 1; 35 | int referenceColumn = (historicalPeriodUnits * numberOfSegmentsOfEachUnit); 36 | for (int j = 1; j <= noOfTrendingUnits; j++) 37 | { 38 | for (int i = 1; i <= numberOfSegmentsOfEachUnit; i++) 39 | { 40 | if (referenceColumn < noOfColumns) 41 | { 42 | trendingSegmentColumns.Add(referenceColumn); 43 | referenceColumn += 1; 44 | } 45 | } 46 | } 47 | return trendingSegmentColumns; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Library/Core/GlobalZCalculationCriterias/GlobalZCalculationCustomCriteria.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using TrendsCalculator.Library.Helper; 7 | using TrendsCalculator.Library.Interfaces; 8 | 9 | namespace TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias 10 | { 11 | /// 12 | /// This class calculates the GLobalZ value for each model 13 | /// 14 | /// 15 | internal class GlobalZCalculationCustomCriteria : IGlobalZCalculationCriteria 16 | { 17 | public List<(T item, double localZ, double globalZ)> CalculateGlobalZValue(List<(T item, double localZ, double globalZ)> trendingModels, List historicalSegmentColumns, List trendingSegmentColumns) where T: TModel 18 | { 19 | double mean = (double)0.0; 20 | double standardDeviation = 0.0; 21 | int count = 0; 22 | List values = new List(); 23 | double sumHistorySegmentColumns = 0.0; 24 | 25 | //This for each block calculates the sum of all the values in the history segment for all models to calculate the global mean and standard deviation 26 | foreach (var model in trendingModels) 27 | { 28 | foreach (int column in historicalSegmentColumns) 29 | { 30 | sumHistorySegmentColumns += model.item.CountWithPeriods[column]; 31 | count++; 32 | values.Add(model.item.CountWithPeriods[column]); 33 | } 34 | } 35 | 36 | double countelements = (count * 1.0); 37 | mean = sumHistorySegmentColumns / countelements; 38 | 39 | standardDeviation = CalculationHelper.CalculateStandardDeviation(values, mean); 40 | 41 | //In case we meet the corner case of standard deviation coming out to be zero, we set an approximate of standard deviation to be 1 by symmetrical distribution of data 42 | if (standardDeviation == 0) 43 | standardDeviation = 1.0; 44 | 45 | //This for each block calculates the globalZ values for each value in the trending segment of the models and then takes the mean of them, 46 | //to calculate the GlobalZ value for the model 47 | for(int increment = 0;increment < trendingModels.Count; increment++) 48 | { 49 | double sumTempGlobalZ = 0.0; 50 | foreach (int column in trendingSegmentColumns) 51 | { 52 | sumTempGlobalZ += ((trendingModels[increment].item.CountWithPeriods[column] * 1.0) - mean) / standardDeviation; 53 | } 54 | double globalZ = sumTempGlobalZ / trendingSegmentColumns.Count; 55 | trendingModels[increment] = (trendingModels[increment].item, trendingModels[increment].localZ,globalZ); 56 | } 57 | 58 | return trendingModels; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Library/Core/GlobalZCalculationCriterias/GlobalZCalculationZMeanCriteria.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using TrendsCalculator.Library.Helper; 7 | using TrendsCalculator.Library.Interfaces; 8 | 9 | namespace TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias 10 | { 11 | /// 12 | /// This class calculates the GLobalZ value for each model and also the mean GLobalZ which will help in segregating the trending models from the list 13 | /// 14 | /// 15 | internal class GlobalZCalculationZMeanCriteria : IGlobalZCalculationCriteria 16 | { 17 | public static double MeanGlobalZ; 18 | public List<(T item, double localZ, double globalZ)> CalculateGlobalZValue(List<(T item, double localZ, double globalZ)> trendingModels, List historicalSegmentColumns, List trendingSegmentColumns) where T : TModel 19 | { 20 | //var calculatedGlobalZItems = new List<(T item, double localZ)>(); 21 | MeanGlobalZ = 0.0; 22 | double sumGlobalZ = 0.0; 23 | double mean = (double)0.0; 24 | double standardDeviation = 0.0; 25 | int count = 0; 26 | List values = new List(); 27 | double sumHistorySegmentColumns = 0.0; 28 | 29 | //This for each block calculates the sum of all the values in the history segment for all models to calculate the global mean and standard deviation 30 | foreach (var model in trendingModels) 31 | { 32 | foreach (int column in historicalSegmentColumns) 33 | { 34 | sumHistorySegmentColumns += model.item.CountWithPeriods[column]; 35 | count++; 36 | values.Add(model.item.CountWithPeriods[column]); 37 | } 38 | } 39 | 40 | double countelements = (count * 1.0); 41 | mean = sumHistorySegmentColumns / countelements; 42 | 43 | standardDeviation = CalculationHelper.CalculateStandardDeviation(values, mean); 44 | 45 | //In case we meet the corner case of standard deviation coming out to be zero, we set an approximate of standard deviation to be 1 by symmetrical distribution of data 46 | if (standardDeviation == 0) 47 | standardDeviation = 1.0; 48 | 49 | //This for each block calculates the GlobalZ values for each value in the trending segment of the models and then takes the mean of them, 50 | //to calculate the GlobalZ value for the model as well as the mean of all the GlobalZ values 51 | for (int increment = 0; increment < trendingModels.Count; increment++) 52 | { 53 | double sumTempGlobalZ = 0.0; 54 | foreach (int column in trendingSegmentColumns) 55 | { 56 | sumTempGlobalZ += ((trendingModels[increment].item.CountWithPeriods[column] * 1.0) - mean) / standardDeviation; 57 | } 58 | double globalZ = sumTempGlobalZ / trendingSegmentColumns.Count; 59 | sumGlobalZ += globalZ; 60 | trendingModels[increment] = (trendingModels[increment].item, trendingModels[increment].localZ, globalZ); 61 | } 62 | 63 | MeanGlobalZ = sumGlobalZ / trendingModels.Count; 64 | return trendingModels; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Library/Core/GlobalZCalculationCriterias/IGlobalZCalculationCriteria.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using TrendsCalculator.Library.Interfaces; 6 | 7 | namespace TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias 8 | { 9 | internal interface IGlobalZCalculationCriteria 10 | { 11 | List<(T item, double localZ, double globalZ)> CalculateGlobalZValue(List<(T item, double localZ, double globalZ)> trendingModels, List historicalSegmentColumns, List trendingSegmentColumns) where T : TModel; 12 | } 13 | } -------------------------------------------------------------------------------- /Library/Core/LocalZValueCalculation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.InteropServices; 7 | using TrendsCalculator.Library.Helper; 8 | using TrendsCalculator.Library.Interfaces; 9 | 10 | namespace TrendsCalculator.Library.AlgoComponents 11 | { 12 | /// 13 | /// This class calculates the value of LocalZ for each of the models 14 | /// 15 | /// TInterface to adhere to 16 | internal class LocalZValueCalculation where T : TModel 17 | { 18 | internal List<(T item,double localZ, double globalZ)> CalculateLocalZValue(List trendingModels, List historicalSegmentColumns, List trendingSegmentColumns) 19 | { 20 | var calculatedLocalZItems = new List<(T item, double localZ, double globalZ)>(); 21 | //The ForEach block calculates the sum of the entries in the history segment of countWithPeriods of each model, 22 | //to aid in calculation of mean and standard deviation for each model 23 | foreach (T model in trendingModels) 24 | { 25 | double sumHistorySegmentColumn = 0.0; 26 | List historicalSegmentValues = new List(); 27 | foreach (int column in historicalSegmentColumns) 28 | { 29 | sumHistorySegmentColumn += model.CountWithPeriods[column]; 30 | historicalSegmentValues.Add(model.CountWithPeriods[column]); 31 | 32 | } 33 | 34 | double mean = (sumHistorySegmentColumn * 1.0) / historicalSegmentColumns.Count; 35 | 36 | double standardDeviation = CalculationHelper.CalculateStandardDeviation(historicalSegmentValues, mean); 37 | 38 | //In case we meet the corner case of standard deviation coming out to be zero, we set an approximate of standard deviation to be 1 by symmetrical distribution of data 39 | if (standardDeviation == 0) 40 | standardDeviation = 1.0; 41 | 42 | //This ForEach block calculates the LocalZ values for each column in the trending segment and then finally calcualtes the mean of them 43 | //to account for the LocalZ value for a model 44 | double sumTempLocalZ = 0.0; 45 | foreach (int column in trendingSegmentColumns) 46 | { 47 | sumTempLocalZ += (((model.CountWithPeriods[column] - mean) * 1.0) / standardDeviation); 48 | } 49 | 50 | double LocalZ = sumTempLocalZ / trendingSegmentColumns.Count; 51 | calculatedLocalZItems.Add((model,LocalZ,0)); 52 | //model.LocalZ = LocalZ; 53 | } 54 | return calculatedLocalZItems; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Library/Core/SortingCombiningResults.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using TrendsCalculator.Library.Interfaces; 7 | using TrendsCalculator.Library.Sorter; 8 | 9 | namespace TrendsCalculator.Library.AlgoComponents 10 | { 11 | internal class SortingCombiningResults 12 | { 13 | internal List<(T item, double localZ, double globalZ)> GetSortedCombinedResult(List> categoryTrendingModels) where T : TModel 14 | { 15 | var BothGlobalZLocalZ_Positive_1 = categoryTrendingModels[0]; 16 | var BothGlobalZLocalZ_Alternate_2 = categoryTrendingModels[1]; 17 | var BothGlobalZLocalZ_Negative_3 = categoryTrendingModels[2]; 18 | var trendingModels = new List<(T item, double localZ, double globalZ)>(); 19 | 20 | trendingModels.AddRange(BothGlobalZLocalZ_Positive_1.OrderByDescending(x => x.globalZ)); 21 | trendingModels.AddRange(BothGlobalZLocalZ_Alternate_2.OrderByDescending(x => x.localZ)); 22 | trendingModels.AddRange(BothGlobalZLocalZ_Negative_3.OrderByDescending(x => x.globalZ)); 23 | 24 | return trendingModels; 25 | } 26 | 27 | internal List<(T item, double localZ, double globalZ,double DemandSupplyQuotient)> GetSortedCombinedResultV2(List> categoryDemandSupplyTrendingModels) where T : TDemandSupplyModel 28 | { 29 | var bothGlobalZLocalZ_Positive_1 = categoryDemandSupplyTrendingModels[0]; 30 | var bothGlobalZLocalZ_Alternate_2 = categoryDemandSupplyTrendingModels[1]; 31 | var bothGlobalZLocalZ_Negative_3 = categoryDemandSupplyTrendingModels[2]; 32 | var trendingModels = new List<(T item, double localZ, double globalZ, double DemandSupplyQuotient)>(); 33 | 34 | trendingModels.AddRange(bothGlobalZLocalZ_Positive_1.OrderByDescending(x => x.DemandSupplyQuotient)); 35 | trendingModels.AddRange(bothGlobalZLocalZ_Alternate_2.OrderByDescending(x => x.DemandSupplyQuotient)); 36 | trendingModels.AddRange(bothGlobalZLocalZ_Negative_3.OrderByDescending(x => x.DemandSupplyQuotient)); 37 | 38 | return trendingModels; 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Library/Helper/CalculationHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TrendsCalculator.Library.Helper 6 | { 7 | internal static class CalculationHelper 8 | { 9 | internal static double CalculateStandardDeviation(List values, double mean) 10 | { 11 | double standardDeviation = 0.0; 12 | double summation = 0.0; 13 | foreach (int value in values) 14 | { 15 | summation += Math.Pow((value * 1.0) - mean, 2); 16 | } 17 | summation = summation / values.Count; 18 | standardDeviation = Math.Sqrt(summation); 19 | return standardDeviation; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Library/Helper/Extention.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace TrendsCalculator.Library.Helper 8 | { 9 | public static class ExtensionMethods 10 | { 11 | public static void CopyPropertiesTo(this List source, List dest) 12 | { 13 | var plist = from prop in typeof(T).GetProperties() where prop.CanRead && prop.CanWrite select prop; 14 | 15 | foreach (PropertyInfo prop in plist) 16 | { 17 | prop.SetValue(dest, prop.GetValue(source, null), null); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Library/Interfaces/TModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | 7 | [assembly: InternalsVisibleTo("TrendsCalculator.Test")] 8 | namespace TrendsCalculator.Library.Interfaces 9 | { 10 | public class TModel 11 | { 12 | public List CountWithPeriods { get; set; } 13 | } 14 | 15 | public class TDemandSupplyModel: TModel 16 | { 17 | public int SupplyQuantity { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Library/Sorter/GlobalZSorter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using TrendsCalculator.Library.Interfaces; 5 | 6 | namespace TrendsCalculator.Library.Sorter 7 | { 8 | internal class GlobalZSorter : IComparer<(T item, double localZ, double globalZ)> where T : TModel 9 | { 10 | public int Compare((T item, double localZ, double globalZ) x, (T item, double localZ, double globalZ) y) 11 | { 12 | return (x.globalZ <= y.globalZ) ? 1 : -1; 13 | } 14 | } 15 | 16 | internal class SortingBothPositiveNegative : IComparer<(T item, double localZ, double globalZ)> where T : TModel 17 | { 18 | public int Compare((T item, double localZ, double globalZ) x, (T item, double localZ, double globalZ) y) 19 | { 20 | return (x.globalZ <= y.globalZ) ? 1 : -1; 21 | } 22 | } 23 | internal class SortingAlternate : IComparer<(T item, double localZ, double globalZ)> where T : TModel 24 | { 25 | public int Compare((T item, double localZ, double globalZ) x, (T item, double localZ, double globalZ) y) 26 | { 27 | return (x.localZ <= y.localZ) ? 1 : -1; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Library/TrendCalculatorStrategies/AbstractTrendingCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using TrendsCalculator.Library.AlgoComponents; 5 | using TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias; 6 | using TrendsCalculator.Library.Core; 7 | using TrendsCalculator.Library.Interfaces; 8 | 9 | namespace TrendsCalculator.Library.TrendingCalculatorForModelsStrategy 10 | { 11 | internal abstract class AbstractTrendingCalculator 12 | { 13 | public virtual List> CalculateTrending(int windowPeriod, int numberOfSegmentsOfEachUnit, IEnumerable listOfModels) where T : TModel 14 | { 15 | var trendingModels = new List(); 16 | trendingModels = (List)listOfModels; 17 | int noOfColumns = trendingModels[0].CountWithPeriods.Count; 18 | 19 | // LOGIC FOR COMPONENTIZING THE TRENDING SKILL 20 | //DIVIDE THE WINDOW INTO HISTORY SEGMENT AND TRENDING SEGMENT 21 | 22 | ColumnSegmentation getColumnSegments = new ColumnSegmentation(); 23 | List getHistoricalSegmentColumns = getColumnSegments.GetHistoricalSegmentColumns(windowPeriod, noOfColumns, numberOfSegmentsOfEachUnit); 24 | List getTrendingSegmentColumns = getColumnSegments.GetTrendingSegmentColumns(windowPeriod, noOfColumns, numberOfSegmentsOfEachUnit); 25 | 26 | //Calculating Local Z Value 27 | LocalZValueCalculation localZValueCalculation = new LocalZValueCalculation(); 28 | var calculatedTrendingModels = localZValueCalculation.CalculateLocalZValue(trendingModels, getHistoricalSegmentColumns, getTrendingSegmentColumns); 29 | 30 | //Calculating Global Z Value 31 | var globalZValueCalculator = this.GetAlgoConstruct(); 32 | calculatedTrendingModels = globalZValueCalculator.CalculateGlobalZValue(calculatedTrendingModels, getHistoricalSegmentColumns, getTrendingSegmentColumns); 33 | 34 | //Dividing The Models Into Three Categories 35 | CategoryDivisionOfModels categoryDivisionOfModels = new CategoryDivisionOfModels(); 36 | return categoryDivisionOfModels.GetModelsIntoCategory(calculatedTrendingModels); 37 | } 38 | 39 | public List<(T item, double localZ, double globalZ)> GetSortedCombinedResult(List> categoryTrendingModels) where T : TModel 40 | { 41 | SortingCombiningResults sortingCombiningResults = new SortingCombiningResults(); 42 | return sortingCombiningResults.GetSortedCombinedResult(categoryTrendingModels); 43 | } 44 | 45 | internal abstract IGlobalZCalculationCriteria GetAlgoConstruct(); 46 | 47 | internal abstract List<(T item, double localZ, double globalZ)> PostProcessZScore(List<(T item, double localZ, double globalZ)> trendingModels) where T : TModel; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Library/TrendCalculatorStrategies/CustomTrendingCalculator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias; 6 | 7 | namespace TrendsCalculator.Library.TrendingCalculatorForModelsStrategy 8 | { 9 | internal class CustomTrendingCalculator : AbstractTrendingCalculator 10 | { 11 | internal override IGlobalZCalculationCriteria GetAlgoConstruct() 12 | { 13 | return new GlobalZCalculationCustomCriteria(); 14 | } 15 | 16 | internal override List<(T item, double localZ, double globalZ)> PostProcessZScore(List<(T item, double localZ, double globalZ)> trendingModels) 17 | { 18 | return trendingModels; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Library/TrendCalculatorStrategies/DemandSupplyTrendingCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using TrendsCalculator.Library.AlgoComponents; 6 | using TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias; 7 | using TrendsCalculator.Library.Core; 8 | using TrendsCalculator.Library.Interfaces; 9 | 10 | namespace TrendsCalculator.Library.TrendingCalculatorForModelsStrategy 11 | { 12 | internal class DemandSupplyTrendingCalculator : AbstractTrendingCalculator 13 | { 14 | private int _demandQuotient; 15 | private double _supplyQuantity; 16 | 17 | internal override IGlobalZCalculationCriteria GetAlgoConstruct() 18 | { 19 | return new GlobalZCalculationCustomCriteria(); 20 | } 21 | 22 | public new List<(T item, double localZ, double globalZ, double DemandSupplyQuotient)> GetSortedCombinedResult(List> categoryTrendingModels) where T : TDemandSupplyModel 23 | { 24 | var list = CalculateQuotient(categoryTrendingModels); 25 | var sortingCombiningResults = new SortingCombiningResults(); 26 | return sortingCombiningResults.GetSortedCombinedResultV2(list); 27 | } 28 | private List> CalculateQuotient(List> categoryTrendingModels) where Y: TDemandSupplyModel 29 | { 30 | var categoryDemandSupplyTrendingModels = new List>(); 31 | 32 | for (int i = 0; i < categoryTrendingModels.Count; i++) 33 | { 34 | var transformedModelCategory = new List<(Y item, double localZ, double globalZ, double DemandSupplyQuotient)>(); 35 | for (int j = 0; j < categoryTrendingModels[i].Count; j++) 36 | { 37 | double denominator; 38 | if (categoryTrendingModels[i][j].item.SupplyQuantity == 0) 39 | denominator = 1.0; 40 | else 41 | denominator = (double)(categoryTrendingModels[i][j].item.SupplyQuantity * 1.0); 42 | 43 | double quotient = (double)(categoryTrendingModels[i][j].globalZ / denominator); 44 | transformedModelCategory.Add((categoryTrendingModels[i][j].item, 45 | categoryTrendingModels[i][j].localZ, 46 | categoryTrendingModels[i][j].globalZ, 47 | quotient)); 48 | } 49 | categoryDemandSupplyTrendingModels.Add(transformedModelCategory); 50 | } 51 | 52 | return categoryDemandSupplyTrendingModels; 53 | } 54 | 55 | internal override List<(T item, double localZ, double globalZ)> PostProcessZScore(List<(T item, double localZ, double globalZ)> trendingModels) 56 | { 57 | return trendingModels; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Library/TrendCalculatorStrategies/TrendCalculationStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace TrendsCalculator.Library.Core.Strategy 2 | { 3 | /// 4 | /// This Enum enabled to set the trend calculation strategy 5 | /// 6 | public enum TrendCalculationStrategy 7 | { 8 | /// 9 | /// Z Mean Strategy will consider mean of all the Z values and return trend results according to mean Z threshold 10 | /// 11 | ZMean, 12 | /// 13 | /// Custom strategy will return the Z mean but won't filter the results based on any threshold. 14 | /// 15 | Custom 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Library/TrendCalculatorStrategies/ZMeanTrendingCalculator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using TrendsCalculator.Library.AlgoComponents.GlobalZCalculationCriterias; 6 | using TrendsCalculator.Library.Interfaces; 7 | using TrendsCalculator.Library.Sorter; 8 | 9 | namespace TrendsCalculator.Library.TrendingCalculatorForModelsStrategy 10 | { 11 | internal class ZMeanTrendingCalculator : AbstractTrendingCalculator 12 | { 13 | internal override IGlobalZCalculationCriteria GetAlgoConstruct() 14 | { 15 | return new GlobalZCalculationZMeanCriteria(); 16 | } 17 | 18 | internal override List<(T item, double localZ, double globalZ)> PostProcessZScore(List<(T item, double localZ, double globalZ)> trendingModels) 19 | { 20 | var trendingModelsGlobalZMeanCriteria = new List<(T item, double localZ, double globalZ)>(); 21 | double meanGlobalZ = GlobalZCalculationZMeanCriteria.MeanGlobalZ; 22 | foreach (var model in trendingModels) 23 | { 24 | if (model.globalZ >= meanGlobalZ) 25 | { 26 | trendingModelsGlobalZMeanCriteria.Add(model); 27 | } 28 | } 29 | if (trendingModelsGlobalZMeanCriteria.Count > 1) 30 | { 31 | var sortingGlobalZ = new GlobalZSorter(); 32 | trendingModelsGlobalZMeanCriteria.Sort(0, trendingModelsGlobalZMeanCriteria.Count, sortingGlobalZ); 33 | } 34 | return trendingModelsGlobalZMeanCriteria; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Library/TrendsCalculator.Library.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | true 7 | snupkg 8 | Trending Calculator is a library for calculating the trending models out of the given list of models.The algorithm takes into account primarily two parameters for calculating the trending models out of the given list of data:- 9 | 10 | The spike i.e., the trends indicating the picking up of a particular model. 11 | The quantity in which the model is picked up. 12 | 13 | Three modes for calculation: 14 | a. ZMean criteria. 15 | b. Custom 16 | c. DemandSupply 17 | 18 | Please visit the github documentation on how to consume. 19 | 2.0.0 20 | TrendsCalculator 21 | This release contains below changes: 22 | a. Renaming of the calculation methods. 23 | b. Abstraction of the internal models from the output. The result will be sorted per the algorithm type without revealing internal scores. 24 | Purunjay Bhal, Puneet Walecha 25 | Microsoft 26 | TrendsCalculator 27 | Trending, Trends, Statistics, C#, Z-Score 28 | false 29 | https://github.com/microsoft/TrendCalculator 30 | git 31 | 8.0 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Library/TrendsCalculator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using TrendsCalculator.Library.Core.Strategy; 10 | using TrendsCalculator.Library.Helper; 11 | using TrendsCalculator.Library.Interfaces; 12 | using TrendsCalculator.Library.TrendingCalculatorForModelsStrategy; 13 | 14 | namespace TrendsCalculator.Library 15 | { 16 | /// 17 | /// This class classifies the data qualifying for trending and return the trending data 18 | /// 19 | /// TInterface to adhere to 20 | public class TrendsCalculator 21 | { 22 | private TrendCalculationStrategy _strategy = TrendCalculationStrategy.ZMean; 23 | 24 | /// 25 | /// This method evaluates the trending data based on the strategy selected 26 | /// 27 | /// Window Period to consider for bucketing your input data. E.g. Last 6 months data means window period is 6 28 | /// Number of segments in each window unit. E.g. each window is divided into 2 buckets 29 | /// List of models extending the base class TModel containing the input list of data used for finding trending data 30 | /// Enum of the type TrendCalculationStrategy which returns the trending models as per Custom Criteria or ZMean Criteria 31 | /// 32 | public IEnumerable FindTrendingData(int windowPeriod, int numberOfSegmentsOfEachUnit, List listOfModels, TrendCalculationStrategy strategy) where T: TModel 33 | { 34 | var validationMessage = IsInputDataValid(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels.ConvertAll(x => (TModel)x)); 35 | if (!string.IsNullOrWhiteSpace(validationMessage)) 36 | throw new ArgumentNullException(validationMessage); 37 | 38 | validationMessage = IsCountWithPeriodsDataValid(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels.ConvertAll(x => (TModel)x)); 39 | if (!string.IsNullOrWhiteSpace(validationMessage)) 40 | throw new ArgumentException(validationMessage); 41 | 42 | this._strategy = strategy; 43 | 44 | AbstractTrendingCalculator baseCalculator = null; 45 | switch (_strategy) 46 | { 47 | case TrendCalculationStrategy.ZMean: 48 | baseCalculator = new ZMeanTrendingCalculator(); 49 | break; 50 | case TrendCalculationStrategy.Custom: 51 | baseCalculator = new CustomTrendingCalculator(); 52 | break; 53 | default: 54 | baseCalculator = new CustomTrendingCalculator(); 55 | break; 56 | } 57 | 58 | var trendingModels = baseCalculator.CalculateTrending(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels); 59 | var sortedModel = baseCalculator.GetSortedCombinedResult(trendingModels); 60 | return baseCalculator.PostProcessZScore(sortedModel.ToList()).Select(x => x.item); 61 | 62 | } 63 | 64 | /// 65 | /// This method evalutes trending data based on demand supply variant 66 | /// 67 | /// T represents generic class, user model which extends the TDemandSupplyModel 68 | /// Window Period to consider for bucketing your input data. E.g. Last 6 months data means window period is 6 69 | /// Number of segments in each window unit. E.g. each window is divided into 2 buckets 70 | /// List of TModel containing the input list of data used for finding trending data 71 | /// 72 | public IEnumerable FindTrendingData(int windowPeriod, int numberOfSegmentsOfEachUnit, List listOfModels) where T: TDemandSupplyModel 73 | { 74 | var validationMessage = IsInputDataValid(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels.ConvertAll(x => (TModel)x)); 75 | if (!string.IsNullOrWhiteSpace(validationMessage)) 76 | throw new ArgumentNullException(validationMessage); 77 | 78 | validationMessage = IsCountWithPeriodsDataValid(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels.ConvertAll(x => (TModel)x)); 79 | if (!string.IsNullOrWhiteSpace(validationMessage)) 80 | throw new ArgumentException(validationMessage); 81 | 82 | 83 | AbstractTrendingCalculator baseCalculator = new DemandSupplyTrendingCalculator(); 84 | var trendingModels = baseCalculator.CalculateTrending(windowPeriod, numberOfSegmentsOfEachUnit, listOfModels); 85 | var result = (baseCalculator as DemandSupplyTrendingCalculator).GetSortedCombinedResult(trendingModels); 86 | var processedModel = new List<(T item, double localZ, double globalZ)>(); 87 | foreach(var model in result) 88 | { 89 | processedModel.Add((model.item, model.localZ, model.globalZ)); 90 | } 91 | 92 | return baseCalculator.PostProcessZScore(processedModel).Select(x => x.item); 93 | } 94 | 95 | 96 | private string IsInputDataValid(int windowPeriod, int numberOfSegmentsOfEachUnit, List listOfModels) 97 | { 98 | string validationMessage = string.Empty; 99 | if (windowPeriod == 0) 100 | validationMessage = "windowPeriod cannot be zero"; 101 | else if (numberOfSegmentsOfEachUnit == 0) 102 | validationMessage = "numberOfSegmentsOfEachUnit cannot be zero"; 103 | else if (listOfModels == null || listOfModels.Count() == 0) 104 | validationMessage = "listOfModels can't have zero records"; 105 | 106 | return validationMessage; 107 | } 108 | 109 | private string IsCountWithPeriodsDataValid(int windowPeriod,int numberOfSegmentsOfEachUnit, List listOfModels) 110 | { 111 | string validationMessage = string.Empty; 112 | int colLength = listOfModels[0].CountWithPeriods.Count; 113 | foreach (var model in listOfModels) 114 | { 115 | if (model.CountWithPeriods.Count != colLength) 116 | { 117 | validationMessage = "All models don't have the same data as columns in CountWithPeriods attribute"; 118 | break; 119 | } 120 | } 121 | 122 | if (!validationMessage.Equals(string.Empty)) 123 | return validationMessage; 124 | 125 | if (listOfModels?.First()?.CountWithPeriods.Count < (windowPeriod * numberOfSegmentsOfEachUnit) && 126 | listOfModels?.First()?.CountWithPeriods.Count > ((windowPeriod + 1) * numberOfSegmentsOfEachUnit)) 127 | { 128 | validationMessage = "Insufficient data as columns in the countWithPeriods attribute of the models"; 129 | } 130 | 131 | return validationMessage; 132 | } 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trending Calculator 2 | 3 | ## Overview 4 | Trending Calculator is a library for calculating the trending models out of the given list of models (raw data).The algorithm takes into account two parameters for calculating the trending models out of the given list of data:- 5 | 6 | 1. The spike i.e., the trends indicating the picking up of a particular model. 7 | 1. The quantity/volume in which the model is picked up. 8 | 9 | ## Installation 10 | To install this library, please download the latest version of [NuGet Package](https://www.nuget.org/packages/TrendsCalculator/) from [nuget.org](https://www.nuget.org/) and refer it into your project. 11 | 12 | ## How to use it 13 | The library takes four input parameters:- 14 | 15 | 1. Enum of the type **TrendsCalculationStrategy** which has three predefined enumeration values signifying the type of stratregy required in filtering of results: 16 | 1. **ZMean**:- This Enum evaluates and return trending models based on the Z mean value (threshold value is z mean value). 17 | 2. **Custom**:- This Enum gives the the entire list sorted in order of trends evaluated (sorted by global Z, local Z). 18 | 19 | 2. **windowPeriod**:- This integer input defines the period over which the trending models are to be calculated. The unit of measurement of time period is irrevelant here. Say for example, you have three months of data for usage of your model, then the windowPeriod could be 3 (months). If you have the 25 days of data for usage of your model, then the windowPeriod could be 25 (days). 20 | 21 | 3. **numberOfSegmentsOfEachUnit**:- This integer input defines the value that each unit(days/months/year) whichever is the unit of the *windowPeriod*, is divided into a particular number of segments. For example, let's say, you have collected data for your models for 6 months, divided into 3 segments of 2 months each, then the value of this input parameter would be 3. 22 | 23 | 4. **listOfModels**:- This is the input data provided to the algorithm with the list of models (raw data) over which the trending is to be calculated. 24 | 1. *T* should extend the class *TModel* of the library, in case the user choses *ZMean* or *Custom* as the TrendsCalculationStrategy. which contains the following attribute: *CountWithPeriods*(a list of integer type), or *T* should extend the class *TDemandSupplyModel* of the library, in case the user choses to evalute the trending data via the *Demand-Supply* : *CountWithPeriods*(a list of integer type), *SupplyQuantity*(an integer which signifies the actualy supply or consumption of the skill). 25 | 26 | *Demand-Supply* here means, that the model usage count which the user provides as input via *CountWithPeriods* is the demand of the model, and *SupplyQuantity* will indicate the actual usage or consumption of that model. The trending order of models is then calcualted taking this scenario in consideration. E.g. If a skill of Azure SQL is in demand (i.e. the CountWithPeriods value is quite high), but the supply (SupplyQuantity) is relatively lower, then this is a factor considered to mark this skill as a trending. 27 | 28 | 2. countWithPeriods consists of the number of times a particular model was consumed in the segments of the windowPeriod. Mathematically speaking , CountWithPeriods.Count should be >= *windowPeriod x noOfSegmentsOfEachUnit* and <= *(windowPeriod+1) x noOfSegmentsOfEachUnit*. 29 | 30 | Each of the model type T, should extend from either the TModel or the TDemandSupplyModel of the NuGet package. 31 | 32 | The algorithm takes into account the virtual windowsPeriod, lets say the algorithm is run for the windowPeriod of 6 months in the month of June 2020 and the data for June is not yet complete, so in order to compensate for that , the data can be provided for December 2019 to June 2020.If not using the virtual windowPeriod, the data must be provided for January 2020 to June 2020. 33 | 34 | First, an object of the TrendsCalculator class needs to be instantiated with the constructor which takes Enum as input defining the strategy.The example of the code is :- 35 | ``` 36 | var trendingCalculator = new TrendsCalculator.Library.TrendsCalculator(); 37 | ``` 38 | 39 | The created object then calls the FindTrendingData() which is an overloaded method: 40 | 1. First overloaded implementation of the function takes four arguments as input : windowPeriod(integer type), numberOfSegmentsOfEachUnit(integer type), listOfModels(list of the models, the model should extend the base class TModel) & enum of the type TrendCalculationStrategy. 41 | ``` 42 | trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.Custom); 43 | ``` 44 | or 45 | ``` 46 | trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.ZMean); 47 | ``` 48 | 49 | 2. Second overloaded implementation of the function takes three arguments as input : windowPeriod(integer type), numberOfSegmentsOfEachUnit(integer type), listOfModels(list of the models, the model should extend the base class TDemandSupplyModel) 50 | ``` 51 | trendingCalculator.FindTrendingData(6, 1, inputData); 52 | ``` 53 | 54 | The user can call any of the above implementations of the FindTrendingData() as per use-case. 55 | 56 | The method returns the list of models in the trending order as per the strategy specified in the Enum. 57 | 58 | ##How it works: 59 | The algorithm works on the principle of mathematical stastics conepts of 'Z' value which is calculated from the mean and standard deviation. 60 | 61 | **ZValue = (observation - mean) / standardDeviation** 62 | 63 | ZValue is the statistical quantitative numerical value which accounts for the spikes in the usage of model, and provides us the very basis for comparision. 64 | 65 | The algorithm divides the windowPeriod into two segments the history period and the trending period. 66 | 67 | For each of the model, the mean and standard deviation is calculated taking the history segment values into account.Then for each of the values in the trending segment of each model, the Z values are calculated.The mean of these Z values gives the LocalZ value for each model. 68 | 69 | LocalZ value determines whether the model's demand has spiked in the trending segment as compared to its historical segment. A positive value indicates the model is in demand, a negative value indicates the model's demand has reduced as compared to the historical segment. 70 | 71 | Next we take values of all models from the history segment and calculate mean and standard deviation.This common value of mean and standard deviation is applied to each of the values for a particular model in the trending segment and caluclate the Z values.The mean of these Z values gives out the GlobalZ value for each model. 72 | 73 | GlobalZ value determines whether the model's demand has spiked over the trending segment in comparision to all the input models supplied.A postiive value suggests that the model demand has spiked in comparision to the global average and a negative value suggests, it's demand has reduced as compared to the global average. 74 | 75 | ## Result Calculation:- 76 | 77 | 1. **For the ZMean Strategy** :- 78 | * The mean of the GlobalZ values for each model is calculated.The models having value of GlobalZ greater than the mean GLobalZ value are taken into the trending list.The trending list is then sorted on the GlobalZ values with the top model being the one with highesh GlobalZ value.The list is then returned. 79 | 80 | 2. **For the Custom Strategy**: 81 | * The models are sorted out into three categories: 82 | 1. Models having positive GlobalZ and LocalZ values 83 | 2. Models having alternate signs of GlobalZ and LocalZ values 84 | 3. Models having negative GLobalZ and LocalZ values 85 | 86 | Since the GLobalZ is the relativity parameter which tells whether a particular model was picked up more than the other models in the trending segment as compared to the history segment, and LocalZ is the local parameter for a particular model which tells whether a particular skill was picked up more in the trending segment(positive value) as compared to the history segment, the order of the categories mentioned above is 1>2>3 87 | The models then in the category 1 and category 3 are sorted in descending order of GlobalZ values and models in the category 2 are sorted in the descening order of LocalZ values.The result is combined and then returned. 88 | 89 | 3. **For the DemandSupply Strategy**: 90 | * After calculating the GlobalZ values, we calculate the DemandSupplyQuotient = GlobalZ/SupplyQuantity. This is in accordance with the unitary method of calculating the GlobalZ value of each model per 1 SupplyQuantity.The models are then sorted out into three categories: 91 | 1. Models having positive GlobalZ and LocalZ values 92 | 2. Models having alternate signs of GlobalZ and LocalZ values 93 | 3. Models having negative GLobalZ and LocalZ values 94 | 95 | Since the GlobalZ is the relativity parameter which tells whether a particular model was picked up more than the other models in the trending segment as compared to the history segment, and LocalZ is the local parameter for a particular model which tells whether a particular skill was picked up more in the trending segment(positive value) as compared to the history segment, the order of the categories mentioned above is 1>2>3 96 | The models then are sorted in descending order of DemandSupplyQuotient values in each of the category.The result is combined and then returned. 97 | 98 | An elaborative example of the process :- 99 |
100 | Demo Model 101 |
102 | windowPeriod=2 months;
103 | numberOfSegmentsOfEachUnit=2;
104 | (Unit = 1 month, data recorded is in period of 15 days each, twice in a month, hence numberOfSegmentsOfEachUnit = 2) 105 | 106 | The table with their count usage when plotted looks like :- 107 | (with the count for each segment represented on Y-axis and the 15 days segment represented on X-axis) 108 |
109 | Demo Model 110 |
111 | 112 | The GlobaZ calculation for each of these skills :- 113 |
114 | Demo Model 115 |
116 | 117 | The plot for these GlobalZ values :- 118 |
119 | Demo Model 120 |
121 | 122 | The result/ranking of these skills as per trending order using **Custom Strategy** :- 123 |
124 | Demo Model 125 |
126 | 127 | ## Contributing 128 | 129 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 130 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 131 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 132 | 133 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 134 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 135 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /Test/TestInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TrendsCalculator.Library.Interfaces; 3 | 4 | namespace TrendsCalculator.Test 5 | { 6 | internal class TestInputModel : TModel 7 | { 8 | public string MovieName { get; set; } 9 | } 10 | 11 | internal class TestDemandSupplyModel : TDemandSupplyModel { 12 | public string MovieName { get; set; } 13 | } 14 | 15 | 16 | internal static class DataPreparator 17 | { 18 | internal static List PrepareData_Suite1() 19 | { 20 | return new List() 21 | { 22 | new TestInputModel() 23 | { 24 | CountWithPeriods = new List(){ 23, 34, 56, 67, 78, 89 }, 25 | MovieName = "ABC" 26 | 27 | }, 28 | new TestInputModel() 29 | { 30 | CountWithPeriods = new List(){ 1, 2, 6, 3, 1, 7 }, 31 | MovieName = "XYZ" 32 | }, 33 | new TestInputModel() 34 | { 35 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3, 12 }, 36 | MovieName = "PQR" 37 | } 38 | }; 39 | } 40 | 41 | internal static List PrepareData_Suite2() 42 | { 43 | return new List() 44 | { 45 | new TestInputModel() 46 | { 47 | CountWithPeriods = new List(){ 0, 0, 0, 67, 78, 89, 1 }, 48 | MovieName = "ABC" 49 | 50 | }, 51 | new TestInputModel() 52 | { 53 | CountWithPeriods = new List(){ 0, 0, 0, 0, 0, 0, 0 }, 54 | MovieName = "XYZ" 55 | }, 56 | new TestInputModel() 57 | { 58 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3, 12, 10 }, 59 | MovieName = "PQR" 60 | } 61 | }; 62 | } 63 | 64 | internal static List PrepareData_Suite3() 65 | { 66 | return new List() 67 | { 68 | new TestInputModel() 69 | { 70 | CountWithPeriods = new List(){ 0, 0, 0, 67 }, 71 | MovieName = "ABC" 72 | 73 | }, 74 | new TestInputModel() 75 | { 76 | CountWithPeriods = new List(){ 0, 0, 0, 0, 0, 10 }, 77 | MovieName = "XYZ" 78 | }, 79 | new TestInputModel() 80 | { 81 | CountWithPeriods = new List(){ 11, 25, 6 }, 82 | MovieName = "PQR" 83 | } 84 | }; 85 | } 86 | 87 | internal static List PrepareData_Suite4() 88 | { 89 | return new List() 90 | { 91 | 92 | }; 93 | } 94 | 95 | internal static List PrepareDemandData_Suite1() 96 | { 97 | return new List() 98 | { 99 | new TestDemandSupplyModel() 100 | { 101 | SupplyQuantity=1, 102 | CountWithPeriods = new List(){ 23, 34, 56, 67, 78, 89 }, 103 | MovieName = "ABC" 104 | 105 | }, 106 | new TestDemandSupplyModel() 107 | { 108 | SupplyQuantity=4, 109 | CountWithPeriods = new List(){ 1, 2, 6, 3, 1, 7 }, 110 | MovieName = "XYZ" 111 | }, 112 | new TestDemandSupplyModel() 113 | { 114 | SupplyQuantity=0, 115 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3, 12 }, 116 | MovieName = "PQR" 117 | } 118 | }; 119 | } 120 | 121 | internal static List PrepareDemandData_Suite2() 122 | { 123 | return new List() 124 | { 125 | new TestDemandSupplyModel() 126 | { 127 | SupplyQuantity=50, 128 | CountWithPeriods = new List(){ 0, 0, 0, 0, 0, 0 }, 129 | MovieName = "ABC" 130 | 131 | }, 132 | new TestDemandSupplyModel() 133 | { 134 | SupplyQuantity=21, 135 | CountWithPeriods = new List(){ 1, 2, 6, 3, 1, 7 }, 136 | MovieName = "XYZ" 137 | }, 138 | new TestDemandSupplyModel() 139 | { 140 | SupplyQuantity=13, 141 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3, 12 }, 142 | MovieName = "PQR" 143 | } 144 | }; 145 | } 146 | 147 | internal static List PrepareDemandData_Suite3() 148 | { 149 | return new List() 150 | { 151 | new TestDemandSupplyModel() 152 | { 153 | SupplyQuantity=1, 154 | CountWithPeriods = new List(){ 0, 0, 0 }, 155 | MovieName = "ABC" 156 | 157 | }, 158 | new TestDemandSupplyModel() 159 | { 160 | SupplyQuantity=4, 161 | CountWithPeriods = new List(){ 1, 2, 6, 3 }, 162 | MovieName = "XYZ" 163 | }, 164 | new TestDemandSupplyModel() 165 | { 166 | SupplyQuantity=0, 167 | CountWithPeriods = new List(){ 11, 25, 6, 0, 3 }, 168 | MovieName = "PQR" 169 | } 170 | }; 171 | } 172 | 173 | internal static List PrepareDemandData_Suite4() 174 | { 175 | return new List() 176 | { 177 | 178 | }; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Test/TrendingCalculatorCustomCriteriaTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | using TrendsCalculator.Library.TrendingCalculatorForModelsStrategy; 4 | 5 | namespace TrendsCalculator.Test 6 | { 7 | [TestClass] 8 | public class TrendingCalculatorCustomCriteriaTest 9 | { 10 | [TestMethod] 11 | public void TestTrendingCalculatorCustomCriteria_ShouldRunSuccessfully_Suite1() 12 | { 13 | var trendingStrategy = new CustomTrendingCalculator(); 14 | var inputData = DataPreparator.PrepareData_Suite1(); 15 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 16 | Assert.AreEqual("ABC", output.First().First().item.MovieName); 17 | Assert.AreEqual("PQR", output.Last().Last().item.MovieName); 18 | } 19 | 20 | [TestMethod] 21 | public void TestTrendingCalculatorCustomCriteria_ShouldRunSuccessfully_Suite2() 22 | { 23 | var trendingStrategy = new CustomTrendingCalculator(); 24 | var inputData = DataPreparator.PrepareData_Suite2(); 25 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 26 | Assert.AreEqual("ABC", output.First().First().item.MovieName); 27 | Assert.AreEqual("PQR", output[1][1].item.MovieName); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Test/TrendingCalculatorDemandSupplyCriteriaTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using TrendsCalculator.Library.TrendingCalculatorForModelsStrategy; 7 | 8 | namespace TrendsCalculator.Test 9 | { 10 | [TestClass] 11 | public class TrendingCalculatorDemandSupplyCriteriaTest 12 | { 13 | [TestMethod] 14 | public void TestTrendingCalculatorDemandSupplyCriteria_ShouldRunSuccessfully_Suite1() 15 | { 16 | var trendingStrategy = new DemandSupplyTrendingCalculator(); 17 | var inputData = DataPreparator.PrepareDemandData_Suite1(); 18 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 19 | Assert.AreEqual("ABC", output.First().First().item.MovieName); 20 | Assert.AreEqual("PQR", output.Last().First().item.MovieName); 21 | } 22 | 23 | [TestMethod] 24 | public void TestTrendingCalculatorDemandSupplyCriteria_ShouldRunSuccessfully_Suite2() 25 | { 26 | var trendingStrategy = new DemandSupplyTrendingCalculator(); 27 | var inputData = DataPreparator.PrepareDemandData_Suite2(); 28 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 29 | Assert.AreEqual("ABC", output[1].First().item.MovieName); 30 | Assert.AreEqual("PQR", output.Last().First().item.MovieName); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Test/TrendingCalculatorZMeanCriteriaTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | using TrendsCalculator.Library.TrendingCalculatorForModelsStrategy; 4 | 5 | namespace TrendsCalculator.Test 6 | { 7 | [TestClass] 8 | public class TrendingCalculatorZMeanCriteriaTest 9 | { 10 | [TestMethod] 11 | public void TestTrendingCalculatorZMeanCriteria_ShouldRunSuccessfully_Suite1() 12 | { 13 | var trendingStrategy = new ZMeanTrendingCalculator(); 14 | var inputData = DataPreparator.PrepareData_Suite1(); 15 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 16 | Assert.AreEqual("ABC", output.First().First().item.MovieName); 17 | } 18 | 19 | [TestMethod] 20 | public void TestTrendingCalculatorZMeanCriteria_ShouldRunSuccessfully_Suite2() 21 | { 22 | var trendingStrategy = new ZMeanTrendingCalculator(); 23 | var inputData = DataPreparator.PrepareData_Suite2(); 24 | var output = trendingStrategy.CalculateTrending(6, 1, inputData); 25 | Assert.AreEqual("ABC", output.First().First().item.MovieName); 26 | Assert.AreEqual("PQR", output[1][1].item.MovieName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Test/TrendsCalculator.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Test/TrendsCalculatorTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using TrendsCalculator.Library.Core.Strategy; 7 | 8 | namespace TrendsCalculator.Test 9 | { 10 | [TestClass] 11 | public class TrendsCalculatorTest 12 | { 13 | [TestMethod] 14 | public void TrendsCalculatorTest_CustomCriteria_ShouldRunSuccessfully_Suite1() 15 | { 16 | var trendingCalculator = new Library.TrendsCalculator(); 17 | var inputData = DataPreparator.PrepareData_Suite1(); 18 | var output = trendingCalculator.FindTrendingData(6, 1, inputData,TrendCalculationStrategy.Custom); 19 | 20 | Assert.AreEqual("ABC", output.First().MovieName); 21 | Assert.AreEqual("XYZ", output.ToList()[1].MovieName); 22 | Assert.AreEqual("PQR", output.ToList()[2].MovieName); 23 | } 24 | 25 | [TestMethod] 26 | public void TrendsCalculatorTest_CustomCriteria_ShouldRunSuccessfully_Suite2() 27 | { 28 | var trendingCalculator = new Library.TrendsCalculator(); 29 | var inputData = DataPreparator.PrepareData_Suite2(); 30 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.Custom); 31 | 32 | Assert.AreEqual("ABC", output.First().MovieName); 33 | Assert.AreEqual("XYZ", output.ToList()[1].MovieName); 34 | Assert.AreEqual("PQR", output.ToList()[2].MovieName); 35 | } 36 | 37 | [TestMethod] 38 | [ExpectedException(typeof(ArgumentException))] 39 | public void TrendsCalculatorTest_CustomCriteria_IncorrectData_ShouldGiveException() 40 | { 41 | var trendingCalculator = new Library.TrendsCalculator(); 42 | var inputData = DataPreparator.PrepareData_Suite3(); 43 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.Custom); 44 | } 45 | 46 | [TestMethod] 47 | [ExpectedException(typeof(ArgumentNullException))] 48 | public void TrendsCalculatorTest_BlankRecords_ShouldGiveException() 49 | { 50 | var trendingCalculator = new Library.TrendsCalculator(); 51 | var inputData = DataPreparator.PrepareData_Suite4(); 52 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.Custom); 53 | } 54 | 55 | [TestMethod] 56 | [ExpectedException(typeof(ArgumentNullException))] 57 | public void TrendsCalculatorTest_CustomCriteria_windowPeriodToBeZero_ShouldGiveException() 58 | { 59 | var trendingCalculator = new Library.TrendsCalculator(); 60 | var inputData = DataPreparator.PrepareData_Suite1(); 61 | var output = trendingCalculator.FindTrendingData(0, 1, inputData, TrendCalculationStrategy.Custom); 62 | } 63 | 64 | [TestMethod] 65 | [ExpectedException(typeof(ArgumentNullException))] 66 | public void TrendsCalculatorTest_CustomCriteria_numberOfSegmentsOfEachUnitToBeZero_ShouldGiveException() 67 | { 68 | var trendingCalculator = new Library.TrendsCalculator(); 69 | var inputData = DataPreparator.PrepareData_Suite4(); 70 | var output = trendingCalculator.FindTrendingData(6, 0, inputData, TrendCalculationStrategy.Custom); 71 | } 72 | 73 | [TestMethod] 74 | public void TrendsCalculatorTest_ZMeanCriteria_ShouldRunSuccessfully_Suite1() 75 | { 76 | var trendingCalculator = new Library.TrendsCalculator(); 77 | var inputData = DataPreparator.PrepareData_Suite1(); 78 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.ZMean); 79 | 80 | Assert.AreEqual("ABC", output.First().MovieName); 81 | Assert.ThrowsException(() => output.ToList()[1]); 82 | Assert.ThrowsException(() => output.ToList()[2]); 83 | } 84 | 85 | [TestMethod] 86 | public void TrendsCalculatorTest_ZMeanCriteria_ShouldRunSuccessfully_Suite2() 87 | { 88 | var trendingCalculator = new Library.TrendsCalculator(); 89 | var inputData = DataPreparator.PrepareData_Suite2(); 90 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.ZMean); 91 | 92 | Assert.AreEqual("ABC", output.First().MovieName); 93 | Assert.ThrowsException(() => output.ToList()[1]); 94 | Assert.ThrowsException(() => output.ToList()[2]); 95 | } 96 | 97 | [TestMethod] 98 | [ExpectedException(typeof(ArgumentException))] 99 | public void TrendsCalculatorTest_ZMeanCriteria_IncorrectData_ShouldGiveException() 100 | { 101 | var trendingCalculator = new Library.TrendsCalculator(); 102 | var inputData = DataPreparator.PrepareData_Suite3(); 103 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.ZMean); 104 | } 105 | 106 | [TestMethod] 107 | [ExpectedException(typeof(ArgumentNullException))] 108 | public void TrendsCalculatorTest_ZMeanCriteria_BlankRecords_ShouldGiveException() 109 | { 110 | var trendingCalculator = new Library.TrendsCalculator(); 111 | var inputData = DataPreparator.PrepareData_Suite4(); 112 | var output = trendingCalculator.FindTrendingData(6, 1, inputData, TrendCalculationStrategy.ZMean); 113 | } 114 | 115 | [TestMethod] 116 | [ExpectedException(typeof(ArgumentNullException))] 117 | public void TrendsCalculatorTest_ZMeanCriteria_windowPeriodToBeZero_ShouldGiveException() 118 | { 119 | var trendingCalculator = new Library.TrendsCalculator(); 120 | var inputData = DataPreparator.PrepareData_Suite1(); 121 | var output = trendingCalculator.FindTrendingData(0, 1, inputData, TrendCalculationStrategy.ZMean); 122 | } 123 | 124 | [TestMethod] 125 | [ExpectedException(typeof(ArgumentNullException))] 126 | public void TrendsCalculatorTest_ZMeanCriteria_numberOfSegmentsOfEachUnitToBeZero_ShouldGiveException() 127 | { 128 | var trendingCalculator = new Library.TrendsCalculator(); 129 | var inputData = DataPreparator.PrepareData_Suite1(); 130 | var output = trendingCalculator.FindTrendingData(6, 0, inputData, TrendCalculationStrategy.ZMean); 131 | } 132 | 133 | [TestMethod] 134 | public void TrendsCalculatorTest_DemandSupplyCriteria_ShouldRunSuccessfully_Suite1() 135 | { 136 | var trendingCalculator = new Library.TrendsCalculator(); 137 | var inputData = DataPreparator.PrepareDemandData_Suite1(); 138 | var output = trendingCalculator.FindTrendingData(6, 1, inputData); 139 | 140 | Assert.AreEqual("ABC", output.First().MovieName); 141 | Assert.AreEqual("XYZ", output.ToList()[1].MovieName); 142 | Assert.AreEqual("PQR", output.ToList()[2].MovieName); 143 | } 144 | 145 | [TestMethod] 146 | public void TrendsCalculatorTest_DemandSupplyCriteria_ShouldRunSuccessfully_Suite2() 147 | { 148 | var trendingCalculator = new Library.TrendsCalculator(); 149 | var inputData = DataPreparator.PrepareDemandData_Suite2(); 150 | var output = trendingCalculator.FindTrendingData(6, 1, inputData); 151 | 152 | Assert.AreEqual("XYZ", output.First().MovieName); 153 | Assert.AreEqual("ABC", output.ToList()[1].MovieName); 154 | Assert.AreEqual("PQR", output.ToList()[2].MovieName); 155 | } 156 | 157 | [TestMethod] 158 | [ExpectedException(typeof(ArgumentException))] 159 | public void TrendsCalculatorTest_DemandSupplyCriteria_IncorrectData_ShouldGiveException() 160 | { 161 | var trendingCalculator = new Library.TrendsCalculator(); 162 | var inputData = DataPreparator.PrepareDemandData_Suite3(); 163 | var output = trendingCalculator.FindTrendingData(6, 1, inputData); 164 | } 165 | 166 | [TestMethod] 167 | [ExpectedException(typeof(ArgumentNullException))] 168 | public void TrendsCalculatorTest_DemandSupplyCriteria_BlankRecords_ShouldGiveException() 169 | { 170 | var trendingCalculator = new Library.TrendsCalculator(); 171 | var inputData = DataPreparator.PrepareDemandData_Suite4(); 172 | var output = trendingCalculator.FindTrendingData(6, 1, inputData); 173 | } 174 | 175 | [TestMethod] 176 | [ExpectedException(typeof(ArgumentNullException))] 177 | public void TrendsCalculatorTest_DemandSupplyCriteria_windowPeriodToBeZero_ShouldGiveException() 178 | { 179 | var trendingCalculator = new Library.TrendsCalculator(); 180 | var inputData = DataPreparator.PrepareDemandData_Suite1(); 181 | var output = trendingCalculator.FindTrendingData(0, 1, inputData); 182 | } 183 | 184 | [TestMethod] 185 | [ExpectedException(typeof(ArgumentNullException))] 186 | public void TrendsCalculatorTest_DemandSupplyCriteria_numberOfSegmentsOfEachUnitToBeZero_ShouldGiveException() 187 | { 188 | var trendingCalculator = new Library.TrendsCalculator(); 189 | var inputData = DataPreparator.PrepareDemandData_Suite1(); 190 | var output = trendingCalculator.FindTrendingData(6, 0, inputData); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /TrendsCalculator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31327.30 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E857FD6D-BCEE-49CB-A25C-6E8F09E2BD34}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{061CACEA-E247-4C13-99AC-AC2F519087E7}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{ED625A6C-F37C-4BD3-AFE3-0D427406FE80}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrendsCalculator.Client", "Client\TrendsCalculator.Client.csproj", "{0782FC81-B98E-433D-92E8-0854CB37583D}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrendsCalculator.Library", "Library\TrendsCalculator.Library.csproj", "{79ADE1CF-CAC2-406B-BA43-0939BB38D2E5}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrendsCalculator.Test", "Test\TrendsCalculator.Test.csproj", "{D983BDB0-3107-4E56-A5BD-6E7E1B92E430}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {0782FC81-B98E-433D-92E8-0854CB37583D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {0782FC81-B98E-433D-92E8-0854CB37583D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {0782FC81-B98E-433D-92E8-0854CB37583D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {0782FC81-B98E-433D-92E8-0854CB37583D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {79ADE1CF-CAC2-406B-BA43-0939BB38D2E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {79ADE1CF-CAC2-406B-BA43-0939BB38D2E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {79ADE1CF-CAC2-406B-BA43-0939BB38D2E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {79ADE1CF-CAC2-406B-BA43-0939BB38D2E5}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D983BDB0-3107-4E56-A5BD-6E7E1B92E430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D983BDB0-3107-4E56-A5BD-6E7E1B92E430}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D983BDB0-3107-4E56-A5BD-6E7E1B92E430}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D983BDB0-3107-4E56-A5BD-6E7E1B92E430}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {0782FC81-B98E-433D-92E8-0854CB37583D} = {061CACEA-E247-4C13-99AC-AC2F519087E7} 42 | {79ADE1CF-CAC2-406B-BA43-0939BB38D2E5} = {ED625A6C-F37C-4BD3-AFE3-0D427406FE80} 43 | {D983BDB0-3107-4E56-A5BD-6E7E1B92E430} = {E857FD6D-BCEE-49CB-A25C-6E8F09E2BD34} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {94026CBC-0902-4FC8-9903-C64730E56D00} 47 | EndGlobalSection 48 | EndGlobal 49 | --------------------------------------------------------------------------------