private eventResult GenerateEvent(int queueId, List <string> versions, string title, string filename) { Console.WriteLine($"Generating stats at {DateTime.Now}..."); var result = new eventResult(); result.Versions = versions; var matches = DataStore.ReadMatchesByRegVerQue(f => f.queueId == queueId && versions.Contains(f.version)) .Select(m => matchFromJson(m.json, m.region)) .Where(m => m != null); int matchCount = 0; var champWins = new AutoDictionary <int, int>(); var champSeens = new AutoDictionary <int, int>(); var bans = new AutoDictionary <int, winrateStat>(ch => new winrateStat()); foreach (var match in matches) { matchCount++; // Champ winrates foreach (var champ in match.WinChamps) { if (!match.LoseChamps.Contains(champ)) { champSeens.IncSafe(champ); champWins.IncSafe(champ); } } foreach (var champ in match.LoseChamps) { if (!match.WinChamps.Contains(champ)) { champSeens.IncSafe(champ); } } // Bans: winrate for games where champ wasn't banned, and our team didn't pick it foreach (var champ in LeagueStaticData.Champions.Keys) { if (!match.BannedChamps.Contains(champ)) { if (!match.WinChamps.Contains(champ)) { bans[champ].Total++; bans[champ].Wins++; } if (!match.LoseChamps.Contains(champ)) { bans[champ].Total++; } } } } var html = new List <object>(); html.Add(new H1(title)); html.Add(new H2("Champion Winrates")); html.Add(new P($"Last updated: {DateTime.UtcNow:d' 'MMM' 'yyyy' at 'HH:mm' UTC'}")); html.Add(new P($"Based on {matchCount:#,0} total matches. Game version(s) {versions.JoinString(", ")} in queue {queueId}.")); html.Add(new P(new RawTag(" "))); html.Add(new P("Note: win rate is an unreliable measure when the number of matches is low. To correctly account for this, sort by the p95 columns.")); html.Add(new P("One column is optimal for finding the best champions, the other is optimal for finding the worst ones.")); html.Add(new P(new RawTag(" "))); var bestworst = new List <(int champ, double p95best, double p95worst, double winrate)>(); html.Add(makeSortableTable( new TR(colAsc("Champion"), colDesc("Matches"), colDesc("Winrate"), colDesc("Best (p95 lower)", true), colAsc("Worst (p95 upper)")), LeagueStaticData.Champions.Values.Where(c => champSeens[c.Id] > 0).Select(c => { var winrate = champWins[c.Id] / (double)champSeens[c.Id]; var p95 = Utils.WilsonConfidenceInterval(winrate, champSeens[c.Id], 1.96); bestworst.Add((c.Id, p95.lower, p95.upper, winrate)); return(new TR(cellStr(c.Name), cellInt(champSeens[c.Id]), cellPrc(winrate, 1), cellPrc(p95.lower, 1), cellPrc(p95.upper, 1))); }))); result.LinkWinrates = $"{filename}-winrates.html"; GenerateHtmlToFile(Path.Combine(_settings.OutputPath, result.LinkWinrates), html, true); if (bestworst.Count > 0) { var bestchamp = bestworst.MaxElement(x => x.p95best); var worstchamp = bestworst.MinElement(x => x.p95worst); result.BestChamp = bestchamp.champ; result.BestChampWinrate = bestchamp.winrate; result.WorstChamp = worstchamp.champ; result.WorstChampWinrate = worstchamp.winrate; } html = new List <object>(); html.Add(new H1(title)); html.Add(new H2("Best Bans")); html.Add(new P($"Last updated: {DateTime.UtcNow:d' 'MMM' 'yyyy' at 'HH:mm' UTC'}")); html.Add(new P($"Based on {matchCount:#,0} total matches. Game version(s) {versions.JoinString(", ")} in queue {queueId}.")); html.Add(new P(new RawTag(" "))); html.Add(makeSortableTable( new TR(colAsc("Champion"), colDesc("Matchups evaluated"), colDesc("Δwinrate if banned"), colDesc("Best bans (p95 Δwr lower)", true), colAsc("Worst bans (p95 Δwr upper)")), LeagueStaticData.Champions.Values.Select(c => { var p95 = Utils.WilsonConfidenceInterval(bans[c.Id].Winrate, bans[c.Id].Total, 1.96); return(new TR(cellStr(c.Name), cellInt(bans[c.Id].Total), cellPrcDelta(0.5 - bans[c.Id].Winrate, 2), cellPrcDelta(0.5 - p95.upper, 2), cellPrcDelta(0.5 - p95.lower, 2))); }))); result.LinkBans = $"{filename}-bans.html"; GenerateHtmlToFile(Path.Combine(_settings.OutputPath, result.LinkBans), html, true); result.MatchCount = matchCount; return(result); }