AccountInfo history statistics query

This commit is contained in:
Mikhail Shabanov committed 2020-12-23 16:34:12 +01:00
1 parent ec8f434f42
commit 758aa10e55
18 files changed
+521 -27

No files matched your search

@@ -0,0 +1,119 @@
namespace WotBlitzStatisticsPro.Common.Model
{
/// <summary>
/// Represents tank or account statistics
/// </summary>
public interface IStatistics
{
/// <summary>
/// LastBattle
/// </summary>
int LastBattleTime { get; }
///<summary>
/// Battles count
///</summary>
long? Battles { get; set; }
///<summary>
/// Capture points
///</summary>
long? CapturePoints { get; set; }
///<summary>
/// Total damage amount
///</summary>
long? DamageDealt { get; set; }
///<summary>
/// Total amount of received damage
///</summary>
long? DamageReceived { get; set; }
///<summary>
/// Dropped capture points
///</summary>
long? DroppedCapturePoints { get; set; }
///<summary>
/// Total amount of frags
///</summary>
long? Frags { get; set; }
///<summary>
/// Total amount of fras grater ten 8 lvl
///</summary>
long? Frags8P { get; set; }
///<summary>
/// Total amount of hits
///</summary>
long? Hits { get; set; }
///<summary>
/// Total amount of losses
///</summary>
long? Losses { get; set; }
///<summary>
/// Max frags per battle
///</summary>
long? MaxFrags { get; set; }
///<summary>
/// Tank id, which kills max frags per battle
///</summary>
long? MaxFragsTankId { get; set; }
///<summary>
/// Max experience per battle
///</summary>
long? MaxXp { get; set; }
///<summary>
/// Tank Id which created max experience per battle
///</summary>
long? MaxXpTankId { get; set; }
///<summary>
/// Total shots count
///</summary>
long? Shots { get; set; }
///<summary>
/// Total count of spotted vehicles
///</summary>
long? Spotted { get; set; }
///<summary>
/// Total count of survived battles
///</summary>
long? SurvivedBattles { get; set; }
///<summary>
/// Total count of survived and winned battles
///</summary>
long? WinAndSurvived { get; set; }
///<summary>
/// Total wins count
///</summary>
long? Wins { get; set; }
///<summary>
/// Total amount of experience
///</summary>
long? Xp { get; set; }
/// <summary>
/// Average tier
/// </summary>
double AvgTier { get; set; }
/// <summary>
/// Wn7 coefficient
/// </summary>
double Wn7 { get; set; }
}
}
@@ -1,8 +1,9 @@
using MongoDB.Bson.Serialization.Attributes;
using WotBlitzStatisticsPro.Common.Model;
namespace WotBlitzStatisticsPro.DataAccess.Model.Accounts
{
public class AccountInfoHistory
public class AccountInfoHistory: IStatistics
{
public AccountInfoHistory(long accountId, int lastBattleTime)
{
@@ -111,14 +111,24 @@ namespace WotBlitzStatisticsPro.DataAccess
.InsertOneAsync(tankInfoHistory);
}
public Task<IEnumerable<AccountInfoHistory>> GetAccountHistory(long accountId, int lastBattleSince)
public async Task<IEnumerable<AccountInfoHistory>> GetAccountHistory(long accountId, int lastBattleSince)
{
throw new System.NotImplementedException();
var history = await _database.GetCollection<AccountInfoHistory>(AccountInfoHistoryCollectionName)
.FindAsync(Builders<AccountInfoHistory>.Filter.Where(a =>
a.AccountInfoHistoryId.AccountId == accountId && a.AccountInfoHistoryId.LastBattleTime >= lastBattleSince),
new FindOptions<AccountInfoHistory> { Sort = Builders<AccountInfoHistory>.Sort.Descending(h => h.AccountInfoHistoryId.LastBattleTime)});
return history.ToList();
}
public Task<IEnumerable<TankInfoHistory>> GetTankHistory(long accountId, long tankId, int lastBattleSince)
public async Task<IEnumerable<TankInfoHistory>> GetTankHistory(long accountId, long tankId, int lastBattleSince)
{
throw new System.NotImplementedException();
var history = await _database.GetCollection<TankInfoHistory>(TankInfoHistoryCollectionName)
.FindAsync(
Builders<TankInfoHistory>.Filter.Where(t =>
t.TankInfoHistoryId.AccountId == accountId && t.TankId == tankId && t.TankInfoHistoryId.LastBattleTime >= lastBattleSince),
new FindOptions<TankInfoHistory>
{Sort = Builders<TankInfoHistory>.Sort.Descending(h => h.TankInfoHistoryId.LastBattleTime)});
return history.ToList();
}
}
}
@@ -0,0 +1,26 @@
using System;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.DataAccess.Model.Accounts;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline
{
public class AccountHistoryInformationPipelineContextData: IDatabasePipelineContextData, IStatisticsPipelineData
{
public AccountHistoryInformationPipelineContextData(DateTime lastBattleSince)
{
LastBattleSince = lastBattleSince;
}
public DateTime LastBattleSince { get; }
public AccountInfo DbAccountInfo { get; set; }
public IStatistics[] History { get; set; }
public ShortStatistics OverallStatistics { get; set; }
public ShortStatistics PeriodAccountStatistics { get; set; }
public StatisticsDifference PeriodDifference { get; set; }
public StatisticsDifference[] StatisticsHistory { get; set; }
public AccountInfoHistoryResponse Response { get; set; }
}
}
@@ -1,13 +0,0 @@
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.DataAccess.Model.Accounts;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline
{
public class HistoryInformationPipelineContextData: IDatabasePipelineContextData
{
public AccountInfo DbAccountInfo { get; set; }
public AccountInfoHistoryResponse Response { get; set; }
}
}
@@ -0,0 +1,18 @@
using WotBlitzStatisticsPro.Common.Model;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline
{
public interface IStatisticsPipelineData
{
IStatistics[] History { get; set; }
ShortStatistics OverallStatistics { get; set; }
public ShortStatistics PeriodAccountStatistics { get; set; }
public StatisticsDifference PeriodDifference { get; set; }
public StatisticsDifference[] StatisticsHistory { get; set; }
}
}
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class CheckIfHistoryIsEmptyOperation : IOperation<IOperationContext>
{
private readonly ILogger<CheckIfHistoryIsEmptyOperation> _logger;
public CheckIfHistoryIsEmptyOperation(ILogger<CheckIfHistoryIsEmptyOperation> logger)
{
_logger = logger;
}
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<IStatisticsPipelineData>();
if (contextData.History == null ||
contextData.History.Length == 0)
{
_logger.LogInformation(
$"History request doesn't return any data");
return Task.CompletedTask;
}
return next.Invoke(context);
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Calculations;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class FillAccountInfoHistoryResponse : IOperation<IOperationContext>
{
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<AccountHistoryInformationPipelineContextData>();
contextData.Response = new AccountInfoHistoryResponse
{
AccountId = context.Request.AccountId,
CreatedAt = contextData.DbAccountInfo.CreatedAt.ToDateTime(),
Nickname = contextData.DbAccountInfo.Nickname,
PeriodDifference = contextData.PeriodDifference,
StatisticsHistory = contextData.StatisticsHistory,
PeriodAccountStatistics = contextData.PeriodAccountStatistics,
OverallAccountStatistics = contextData.OverallStatistics
};
return next.Invoke(context);
}
}
}
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using AutoMapper;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class FillOverallStatisticsOperation : IOperation<IOperationContext>
{
private readonly IMapper _mapper;
public FillOverallStatisticsOperation(IMapper mapper)
{
_mapper = mapper;
}
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<IStatisticsPipelineData>();
contextData.OverallStatistics = _mapper.Map<IStatistics, ShortStatistics>(contextData.History[0]);
return next.Invoke(context);
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Threading.Tasks;
using AutoMapper;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Calculations;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class FillPeriodDifferenceOperation : IOperation<IOperationContext>
{
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<IStatisticsPipelineData>();
if (contextData.History.Length > 1)
{
contextData.PeriodDifference = new StatisticsDifference();
var historyLastIndex = contextData.History.Length - 1;
contextData.PeriodDifference.FillDifference(contextData.History[0], contextData.History[historyLastIndex]);
}
return next.Invoke(context);
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using AutoMapper;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.DataAccess.Model.Accounts;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class FillPeriodStatisticsOperation : IOperation<IOperationContext>
{
private readonly IMapper _mapper;
public FillPeriodStatisticsOperation(IMapper mapper)
{
_mapper = mapper;
}
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<IStatisticsPipelineData>();
if (contextData.History.Length > 1)
{
var diff = new AccountInfoHistory(context.Request.AccountId, contextData.History[0].LastBattleTime);
var historyLastIndex = contextData.History.Length - 1;
diff.Battles = contextData.History[0].Battles - contextData.History[historyLastIndex].Battles;
diff.Wins = contextData.History[0].Wins - contextData.History[historyLastIndex].Wins;
diff.DamageDealt = contextData.History[0].DamageDealt - contextData.History[historyLastIndex].DamageDealt;
diff.Xp = contextData.History[0].Xp - contextData.History[historyLastIndex].Xp;
contextData.PeriodAccountStatistics = _mapper.Map<IStatistics, ShortStatistics>(diff);
}
return next.Invoke(context);
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Calculations;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class FillStatisticsDifferenceOperation : IOperation<IOperationContext>
{
public Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<IStatisticsPipelineData>();
if (contextData.History.Length > 1)
{
contextData.StatisticsHistory = new StatisticsDifference[contextData.History.Length - 1];
for (int i = 1; i < contextData.History.Length; i++)
{
contextData.StatisticsHistory[i - 1] = new StatisticsDifference();
contextData.StatisticsHistory[i - 1].FillDifference(contextData.History[i - 1], contextData.History[i]);
}
}
return next.Invoke(context);
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using WotBlitzStatisticsPro.DataAccess;
using WotBlitzStatisticsPro.Logic.AccountInformationPipeline.OperationContext;
using WotBlitzStatisticsPro.Logic.Calculations;
using WotBlitzStatisticsPro.Logic.Pipeline;
namespace WotBlitzStatisticsPro.Logic.AccountInformationPipeline.Operations
{
public class ReadAccountInfoHistoryFromDbOperation : IOperation<IOperationContext>
{
private readonly IWargamingAccountDataAccessor _accountDataAccessor;
public ReadAccountInfoHistoryFromDbOperation(IWargamingAccountDataAccessor accountDataAccessor)
{
_accountDataAccessor = accountDataAccessor;
}
public async Task Invoke(IOperationContext context, Func<IOperationContext, Task> next)
{
var contextData = context.Get<AccountHistoryInformationPipelineContextData>();
contextData.History = (await _accountDataAccessor.GetAccountHistory(context.Request.AccountId, contextData.LastBattleSince.ToUnixTimestamp())).ToArray();
await next.Invoke(context);
}
}
}
@@ -12,6 +12,12 @@ namespace WotBlitzStatisticsPro.Logic.Calculations
return dtDateTime;
}
public static int ToUnixTimestamp(this DateTime date)
{
var diff = date - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt32(diff.TotalSeconds);
}
}
}
@@ -0,0 +1,77 @@
using WotBlitzStatisticsPro.Common.Model;
namespace WotBlitzStatisticsPro.Logic.Calculations
{
public static class StatisticsHelper
{
public static void FillDifference(
this StatisticsDifference difference,
IStatistics firstItem,
IStatistics lastItem)
{
difference.LastBattleTime = firstItem.LastBattleTime.ToDateTime();
difference.Battles = new StatisticsDifferenceItem<long>
{
CurrentValue = firstItem.Battles ?? 0,
Difference = firstItem.Battles - lastItem.Battles
};
difference.AvgTier = new StatisticsDifferenceItem<double>
{
CurrentValue = firstItem.AvgTier,
Difference = firstItem.AvgTier - lastItem.AvgTier
};
difference.Wn7 = new StatisticsDifferenceItem<double>
{
CurrentValue = firstItem.Wn7,
Difference = firstItem.Wn7 - lastItem.Wn7
};
decimal firstWinRate = lastItem.Battles.HasValue
? 100 * (decimal)(lastItem.Wins ?? 0) /
lastItem.Battles.Value
: 0m;
decimal lastWinRate = firstItem.Battles.HasValue
? 100 * (decimal)(firstItem.Wins ?? 0) /
firstItem.Battles.Value
: 0m;
difference.WinRate = new StatisticsDifferenceItem<decimal>
{
CurrentValue = lastWinRate,
Difference = lastWinRate - firstWinRate
};
decimal firsAvgDmg = lastItem.Battles.HasValue
? (decimal)(lastItem.DamageDealt ?? 0) /
lastItem.Battles.Value
: 0m;
decimal lastAvgDmg = firstItem.Battles.HasValue
? (decimal)(firstItem.DamageDealt ?? 0) /
firstItem.Battles.Value
: 0m;
difference.AvgDamage = new StatisticsDifferenceItem<decimal>
{
CurrentValue = lastAvgDmg,
Difference = lastAvgDmg - firsAvgDmg
};
decimal firsAvgXp = lastItem.Battles.HasValue
? (decimal)(lastItem.Xp ?? 0) /
lastItem.Battles.Value
: 0m;
decimal lastAvgXp = firstItem.Battles.HasValue
? (decimal)(firstItem.Xp ?? 0) /
firstItem.Battles.Value
: 0m;
difference.AvgXp = new StatisticsDifferenceItem<decimal>
{
CurrentValue = lastAvgXp,
Difference = lastAvgXp - firsAvgXp
};
}
}
}
@@ -0,0 +1,23 @@
using AutoMapper;
using WotBlitzStatisticsPro.Common.Model;
using WotBlitzStatisticsPro.Logic.Calculations;
namespace WotBlitzStatisticsPro.Logic.Mappers
{
public class ShortStatisticsProfile: Profile
{
public ShortStatisticsProfile()
{
CreateMap<IStatistics, ShortStatistics>()
.ForMember(d => d.LastBattleTime,
o => o.MapFrom(s => s.LastBattleTime.ToDateTime()))
.ForMember(d => d.AvgDamage,
o => o.MapFrom(s => s.Battles.HasValue && s.Battles > 0 ? ((decimal)s.DamageDealt / s.Battles) : 0m))
.ForMember(d => d.AvgXp,
o => o.MapFrom(s => s.Battles.HasValue && s.Battles > 0 ? ((decimal)s.Xp / s.Battles) : 0m))
.ForMember(d => d.WinRate,
o => o.MapFrom(s => s.Battles.HasValue && s.Battles > 0 ? (100 * (decimal)s.Wins / s.Battles) : 0m))
;
}
}
}
@@ -62,20 +62,19 @@ namespace WotBlitzStatisticsPro.Logic
public async Task<AccountInfoHistoryResponse> GetAccountInfoHistory(RealmType realm, long accountId, DateTime startDate, RequestLanguage requestLanguage)
{
var contextData = new HistoryInformationPipelineContextData();
var contextData = new AccountHistoryInformationPipelineContextData(startDate);
var context = new OperationContext(new AccountRequest(accountId, realm, requestLanguage));
context.AddOrReplace(contextData);
var pipeline = new Pipeline<IOperationContext>(_operationFactory);
// ToDo:
// 1. First read account info from DB operation
pipeline.AddOperation<ReadAccountInfoFromDbOperation>()
// 2. Read account history from DB at the startDate !Array SortedByDate! operation
// 3. Fill the OverallAccountStatistics
// PeriodAccountStatistics
// PeriodDifference
// StatisticsHistory
.AddOperation<ReadAccountInfoHistoryFromDbOperation>()
.AddOperation<CheckIfHistoryIsEmptyOperation>()
.AddOperation<FillOverallStatisticsOperation>()
.AddOperation<FillPeriodStatisticsOperation>()
.AddOperation<FillPeriodDifferenceOperation>()
.AddOperation<FillStatisticsDifferenceOperation>()
.AddOperation<FillAccountInfoHistoryResponse>()
;
await pipeline.Build()
@@ -66,6 +66,13 @@ namespace WotBlitzStatisticsPro.Logic
services.AddTransient<CalculateStatisticsOperation>();
services.AddTransient<BuildAccountInfoResponseOperation>();
services.AddTransient<SaveAccountAndTanksOperation>();
services.AddTransient<ReadAccountInfoHistoryFromDbOperation>();
services.AddTransient<CheckIfHistoryIsEmptyOperation>();
services.AddTransient<FillOverallStatisticsOperation>();
services.AddTransient<FillPeriodStatisticsOperation>();
services.AddTransient<FillPeriodDifferenceOperation>();
services.AddTransient<FillStatisticsDifferenceOperation>();
services.AddTransient<FillAccountInfoHistoryResponse>();
services.AddTransient<IOperationFactory>(serviceProvider =>
new ServiceProviderOperationFactory(serviceProvider));