speed update, new table for cached stats
This commit is contained in:
25
Sources/App/Commands/RecalulateRecords.swift
Normal file
25
Sources/App/Commands/RecalulateRecords.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// RecalculateStatistics.swift
|
||||
// RecalculateStatistics
|
||||
//
|
||||
// Created by Michael Simard on 8/3/21.
|
||||
//
|
||||
|
||||
import Vapor
|
||||
import Fluent
|
||||
|
||||
struct RecalulateRecords: Command {
|
||||
struct Signature: CommandSignature {
|
||||
}
|
||||
|
||||
var help: String {
|
||||
"Recaluclates all stats with a w/l ratio"
|
||||
}
|
||||
|
||||
func run(using context: CommandContext, signature: Signature) throws {
|
||||
|
||||
DBHelpers.relcalulateRecords(db: context.application.db) { message in
|
||||
context.console.print(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,26 +41,35 @@ final class AllStats: Content {
|
||||
}
|
||||
}
|
||||
|
||||
final class OverallStats: Content {
|
||||
var overall:StatsWithMostRecentDailyRecord
|
||||
var mwStats:StatsWithMostRecentDailyRecord
|
||||
var bocwStats:StatsWithMostRecentDailyRecord
|
||||
var mostRecentRecord:String
|
||||
final class DashboardStats: Content {
|
||||
var dashboardItems:[DashboardItem] = []
|
||||
|
||||
var statsWithHyder:Stats
|
||||
var statsWithoutHyder:Stats
|
||||
|
||||
init(dashboardItems:[DashboardItem] ){
|
||||
self.dashboardItems = dashboardItems
|
||||
}
|
||||
}
|
||||
|
||||
final class OverallStats: Content {
|
||||
// var overall:StatsWithMostRecentDailyRecord
|
||||
// var mwStats:StatsWithMostRecentDailyRecord
|
||||
// var bocwStats:StatsWithMostRecentDailyRecord
|
||||
// var mostRecentRecord:String
|
||||
|
||||
// var statsWithHyder:Stats
|
||||
// var statsWithoutHyder:Stats
|
||||
|
||||
var dashboardItems:[DashboardItem] = []
|
||||
|
||||
init( overall:StatsWithMostRecentDailyRecord, mwStats:StatsWithMostRecentDailyRecord, bocwStats:StatsWithMostRecentDailyRecord, mostRecentRecord:String, statsWithHyder:Stats, statsWithoutHyder:Stats, dashboardItems:[DashboardItem]){
|
||||
init(dashboardItems:[DashboardItem]){
|
||||
|
||||
self.overall = overall
|
||||
self.mwStats = mwStats;
|
||||
self.bocwStats = bocwStats;
|
||||
self.mostRecentRecord = mostRecentRecord
|
||||
// self.overall = overall
|
||||
// self.mwStats = mwStats;
|
||||
// self.bocwStats = bocwStats;
|
||||
// self.mostRecentRecord = mostRecentRecord
|
||||
|
||||
self.statsWithHyder = statsWithHyder
|
||||
self.statsWithoutHyder = statsWithoutHyder
|
||||
// self.statsWithHyder = statsWithHyder
|
||||
// self.statsWithoutHyder = statsWithoutHyder
|
||||
|
||||
self.dashboardItems = dashboardItems
|
||||
}
|
||||
|
||||
@@ -10,10 +10,13 @@ import Fluent
|
||||
import Vapor
|
||||
|
||||
struct DashboardItem: Content {
|
||||
|
||||
var codTrackerId: String
|
||||
var title:String
|
||||
var content:String
|
||||
var title2:String? = nil
|
||||
var content2:String? = nil
|
||||
var sortOrder: Int
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ struct MatchController: RouteCollection {
|
||||
matchRoute.delete("delete", "id",":id", use: deleteMatch)
|
||||
matchRoute.post("update", "id",":id", use: updateMatch)
|
||||
matchRoute.post("add", use: logMatch)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -28,20 +27,21 @@ struct MatchController: RouteCollection {
|
||||
|
||||
let newMatch = try req.content.decode(Match.self)
|
||||
|
||||
|
||||
return Match.find(id, on: req.db)
|
||||
.unwrap(or: Abort(.notFound))
|
||||
.flatMap {
|
||||
|
||||
|
||||
$0.update(newMatch: newMatch)
|
||||
|
||||
return $0.save(on: req.db)
|
||||
}
|
||||
.map {
|
||||
|
||||
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||
}
|
||||
return .ok
|
||||
|
||||
}
|
||||
.map { .ok }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
func deleteMatch(req: Request) throws -> EventLoopFuture<HTTPStatus> {
|
||||
@@ -51,14 +51,36 @@ struct MatchController: RouteCollection {
|
||||
return Match.find(id, on: req.db)
|
||||
.unwrap(or: Abort(.notFound))
|
||||
.flatMap { $0.delete(on: req.db) }
|
||||
.map { .ok }
|
||||
.map {
|
||||
|
||||
|
||||
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||
}
|
||||
|
||||
return .ok
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func logMatch(req: Request) throws -> EventLoopFuture<Match> {
|
||||
|
||||
let newMatch = try req.content.decode(Match.self)
|
||||
return newMatch.save(on: req.db).map { newMatch}
|
||||
return newMatch.save(on: req.db).map {
|
||||
|
||||
|
||||
|
||||
|
||||
req.application.threadPool.runIfActive(eventLoop: req.eventLoop.next()) {
|
||||
DBHelpers.relcalulateRecords(db: req.db) { MessagePort in}
|
||||
}
|
||||
|
||||
|
||||
return newMatch
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,9 +24,140 @@ struct StatsController: RouteCollection {
|
||||
statsRoute.get("history","page",":page", use: history)
|
||||
statsRoute.get("history", use: history)
|
||||
statsRoute.get("maps", use: mapRecords)
|
||||
statsRoute.get("dashboard", use: dashboard)
|
||||
|
||||
}
|
||||
|
||||
func dashboard(db: Database) throws -> EventLoopFuture<DashboardStats> {
|
||||
|
||||
let statistics = WinLossRecords.query(on: db).all()
|
||||
let adamAffectedMatches = Match.query(on: db).filter(\.$finalKillRuinedPlayerId == 6).count()
|
||||
let totalMWGames = Match.query(on: db).filter(\.$codGame == "mw").count()
|
||||
|
||||
return (statistics.and(adamAffectedMatches).and(totalMWGames)).map { arg -> (DashboardStats) in
|
||||
|
||||
let (((statistics, adamAffectedMatches), totalMWGames)) = arg
|
||||
|
||||
|
||||
// return statistics.map { statistics in
|
||||
return DashboardStats(dashboardItems: statistics.map({ statisticItem in
|
||||
|
||||
|
||||
let title = statisticItem.title
|
||||
var title2:String? = ""
|
||||
var content1 = ""
|
||||
var content2 = ""
|
||||
var sortOrder = 0
|
||||
|
||||
if statisticItem.codTrackerId == "no_hyder_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 10
|
||||
}
|
||||
|
||||
else if statisticItem.codTrackerId == "mw_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 2
|
||||
|
||||
}
|
||||
else if statisticItem.codTrackerId == "mw_six_players"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 6
|
||||
|
||||
|
||||
}
|
||||
else if statisticItem.codTrackerId == "2021_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 5
|
||||
|
||||
}
|
||||
// else if statisticItem.codTrackerId == "best_map_overall"{
|
||||
// title2 = "Ratio"
|
||||
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
//
|
||||
//
|
||||
// }
|
||||
else if statisticItem.codTrackerId == "bocw_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 3
|
||||
|
||||
}
|
||||
// else if statisticItem.codTrackerId == "worst_map_overall"{
|
||||
// title2 = "Ratio"
|
||||
// content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
// content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
//
|
||||
//
|
||||
// }
|
||||
else if statisticItem.codTrackerId == "with_hyder_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 11
|
||||
}
|
||||
else if statisticItem.codTrackerId == "mw_five_players"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 7
|
||||
|
||||
}
|
||||
else if statisticItem.codTrackerId == "casual_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 4
|
||||
|
||||
|
||||
}
|
||||
else if statisticItem.codTrackerId == "overall_four_players"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 8
|
||||
}
|
||||
else if statisticItem.codTrackerId == "overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 1
|
||||
}
|
||||
else if statisticItem.codTrackerId == "2020_overall"{
|
||||
title2 = "Ratio"
|
||||
content1 = "\(statisticItem.wins)-\(statisticItem.losses)"
|
||||
content2 = Utils.getRatio(wins: statisticItem.wins, losses: statisticItem.losses)
|
||||
sortOrder = 9
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
}
|
||||
|
||||
return DashboardItem(codTrackerId:statisticItem.codTrackerId, title: title, content: content1, title2: title2, content2: content2, sortOrder: sortOrder)
|
||||
} ) +
|
||||
[
|
||||
DashboardItem(codTrackerId: "adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(adamAffectedMatches + 7)", title2: "", content2: "",sortOrder: 100),
|
||||
DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(totalMWGames)", title2: "", content2: "", sortOrder: 0)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func dashboard(req: Request) throws -> EventLoopFuture<DashboardStats> {
|
||||
|
||||
return try dashboard(db: req.db)
|
||||
}
|
||||
|
||||
func history(req: Request) throws -> EventLoopFuture<MatchHistory> {
|
||||
|
||||
@@ -86,8 +217,6 @@ struct StatsController: RouteCollection {
|
||||
|
||||
func getAllMatches(matches:[Match]) -> Stats{
|
||||
|
||||
|
||||
|
||||
let totals = matches.reduce([0,0]) { (totals, match) -> [Int] in
|
||||
if match.win == true {
|
||||
return [totals[0] + 1, totals[1]]
|
||||
@@ -184,12 +313,10 @@ struct StatsController: RouteCollection {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func getStatsForYear(year:Int, req: Request) -> EventLoopFuture<Stats>{
|
||||
func getStatsForYear(year:Int, db: Database) -> EventLoopFuture<Stats>{
|
||||
|
||||
// Specify date components
|
||||
var dateComponents = DateComponents()
|
||||
@@ -218,7 +345,7 @@ struct StatsController: RouteCollection {
|
||||
let endDate = userCalendar.date(from: endDateComponenents)
|
||||
|
||||
|
||||
return Match.query(on: req.db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
|
||||
return Match.query(on: db).filter(\.$date > startDate!).filter(\.$date < endDate!).all().map { (matches) -> (Stats) in
|
||||
return self.getCountedMatches(matches: matches)
|
||||
}
|
||||
}
|
||||
@@ -237,10 +364,6 @@ struct StatsController: RouteCollection {
|
||||
let dates = sortedMatches.suffix(30).map { (match) -> CODDate in
|
||||
return CODDate(month: match.date.month, year: match.date.year, day: match.date.day, hour: match.date.hour, minute: match.date.minute)
|
||||
}
|
||||
|
||||
//print ("MDP to dates \(Date().timeIntervalSince(startTime))")
|
||||
|
||||
|
||||
return dates.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in
|
||||
|
||||
if datesPlayed.contains(where: { (existingDate) -> Bool in
|
||||
@@ -257,8 +380,6 @@ struct StatsController: RouteCollection {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func getCumulativeWinLossRatios(matches:[Match]) -> [DataPoint] {
|
||||
|
||||
let daysPlayed = getDaysPlayed(sortedMatches: matches)
|
||||
@@ -483,34 +604,29 @@ struct StatsController: RouteCollection {
|
||||
}
|
||||
}
|
||||
|
||||
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
|
||||
// func cachedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
func forceCalculatedStats(db: Database) -> EventLoopFuture<[String:Stats]> {
|
||||
|
||||
let startTime = Date()
|
||||
|
||||
let statsWithHyder = statsWithPlayer(req: req, playerId: 5)
|
||||
|
||||
let statsWithoutHyder = statsWithoutPlayer(req: req, playerId: 5)
|
||||
|
||||
let statsFor2020 = getStatsForYear(year: 2020, req: req)
|
||||
let statsFor2021 = getStatsForYear(year: 2021, req: req)
|
||||
|
||||
|
||||
let statsWithHyder = statsWithPlayer(db: db, playerId: 5)
|
||||
let statsWithoutHyder = statsWithoutPlayer(db: db, playerId: 5)
|
||||
let statsFor2020 = getStatsForYear(year: 2020, db: db)
|
||||
let statsFor2021 = getStatsForYear(year: 2021, db: db)
|
||||
|
||||
|
||||
let hyderFuture = statsWithHyder.and(statsWithoutHyder)
|
||||
|
||||
let hyderStats = hyderFuture.map { (withHyder, withoutHyder) -> [Stats] in
|
||||
return [withHyder, withoutHyder]
|
||||
|
||||
//print ("Hyder done \(Date().timeIntervalSince(startTime))")
|
||||
}
|
||||
|
||||
|
||||
let matches = Match.query(on: req.db).sort( \.$date).all()
|
||||
let matches = Match.query(on: db).sort( \.$date).all()
|
||||
|
||||
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> (OverallStats) in
|
||||
return matches.and(hyderStats).and(statsFor2020).and(statsFor2021).map { arg -> ([String:Stats]) in
|
||||
|
||||
let (((matches, hyderStats), statsFor2020), statsFor2021) = arg
|
||||
|
||||
@@ -527,11 +643,11 @@ struct StatsController: RouteCollection {
|
||||
var mwSixPlayers:Stats?
|
||||
var mwFivePlayers:Stats?
|
||||
var overallFourPlayers:Stats?
|
||||
|
||||
var mapStats:[Int:Stats]?
|
||||
var worstMap:Int?
|
||||
var bestMap:Int?
|
||||
|
||||
//
|
||||
// var mapStats:[Int:Stats]?
|
||||
// var worstMap:Int?
|
||||
// var bestMap:Int?
|
||||
//
|
||||
group.enter()
|
||||
queue.async {
|
||||
overallStats = self.getStatsWithMostRecentDailyRecord(sortedMatches: matches)
|
||||
@@ -559,42 +675,13 @@ struct StatsController: RouteCollection {
|
||||
return match.codGame == "bocw" && self.shouldCountMatch(match: match )
|
||||
}))
|
||||
|
||||
//print ("cw done \(Date().timeIntervalSince(startTime))")
|
||||
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.enter()
|
||||
queue.async {
|
||||
mapStats = self.getMapStats(matches: matches)
|
||||
//print ("maps done \(Date().timeIntervalSince(startTime))")
|
||||
group.leave()
|
||||
|
||||
}
|
||||
//
|
||||
group.enter()
|
||||
queue.async {
|
||||
let mapStats = self.getMapStats(matches: matches)
|
||||
|
||||
bestMap = self.getBestMap(records: mapStats)
|
||||
//print ("best done \(Date().timeIntervalSince(startTime))")
|
||||
group.leave()
|
||||
|
||||
}
|
||||
|
||||
group.enter()
|
||||
queue.async {
|
||||
let mapStats = self.getMapStats(matches: matches)
|
||||
|
||||
worstMap = self.getWorstMap(records: mapStats)
|
||||
//print ("worst done \(Date().timeIntervalSince(startTime))")
|
||||
group.leave()
|
||||
|
||||
}
|
||||
|
||||
group.enter()
|
||||
queue.async {
|
||||
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb 1 21
|
||||
overallFourPlayers = self.getAllMatchesByPlayerCount(matches: matches, playerCount: 4) //Feb
|
||||
group.leave()
|
||||
}
|
||||
|
||||
@@ -615,27 +702,111 @@ struct StatsController: RouteCollection {
|
||||
|
||||
group.wait()
|
||||
|
||||
let dashboardItems = [
|
||||
|
||||
DashboardItem(title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
|
||||
DashboardItem(title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
|
||||
DashboardItem(title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
|
||||
DashboardItem(title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
|
||||
DashboardItem(title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
|
||||
DashboardItem(title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
|
||||
DashboardItem(title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
|
||||
DashboardItem(title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
|
||||
DashboardItem(title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
|
||||
DashboardItem(title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
|
||||
DashboardItem(title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
|
||||
DashboardItem(title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
|
||||
DashboardItem(title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
|
||||
DashboardItem(title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
|
||||
DashboardItem(title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
|
||||
// let dashboardItems:[DashboardItem] = [
|
||||
//
|
||||
// // DashboardItem(codTrackerId:"total_mw_games", title: "Total MW Games", content: "\(mwStats!.totalWins + mwStats!.totalLosses)" , title2:"", content2:""),
|
||||
// DashboardItem(codTrackerId:"mw_overall",title: "MW Overall", content: mwStats!.record, title2: "Ratio", content2: mwStats!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"mw_six_players", title: "MW 6 Players ", content: mwSixPlayers!.record, title2: "Ratio", content2: mwSixPlayers!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"mw_five_players", title: "MW 5 Players ", content: mwFivePlayers!.record, title2: "Ratio", content2: mwFivePlayers!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"overall_four_players", title: "4 Player Crew", content: overallFourPlayers!.record, title2: "Ratio", content2: overallFourPlayers!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"overall", title: "Overall", content: overallStats!.record, title2: "Ratio", content2: overallStats!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"2021_overall", title: "2021 Overall", content: statsFor2021.record, title2: "Ratio", content2: statsFor2021.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"2020_overall", title: "2020 Overall", content: statsFor2020.record, title2: "Ratio", content2: statsFor2020.winLossRatio),
|
||||
//// DashboardItem(codTrackerId:"win_rate_overall", title: "Win Rate", content: "\(overallStats!.winRate)", title2: "", content2: ""),
|
||||
// DashboardItem(codTrackerId:"bocw_overall", title: "Cold War Overall", content: bocwStats!.record, title2: "Ratio", content2: bocwStats!.winLossRatio),
|
||||
// DashboardItem(codTrackerId:"with_hyder_overall", title: "With Hyder", content: hyderStats[0].record, title2: "Ratio", content2: hyderStats[0].winLossRatio),
|
||||
// DashboardItem(codTrackerId:"no_hyder_overall", title: "No Hyder", content: hyderStats[1].record, title2: "Ratio", content2: hyderStats[1].winLossRatio),
|
||||
//// DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)"),
|
||||
//// DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)"),
|
||||
//// DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: ""),
|
||||
////
|
||||
// ]
|
||||
//
|
||||
return [
|
||||
"mw_overall":Stats(totalWins: mwStats!.totalWins, totalLosses: mwStats!.totalLosses),
|
||||
"no_hyder_overall":Stats(totalWins: hyderStats[1].totalWins, totalLosses: hyderStats[1].totalLosses),
|
||||
"with_hyder_overall":Stats(totalWins:hyderStats[0].totalWins, totalLosses: hyderStats[0].totalWins),
|
||||
"bocw_overall":Stats(totalWins: bocwStats!.totalWins, totalLosses: bocwStats!.totalLosses),
|
||||
"2020_overall":Stats(totalWins: statsFor2020.totalWins, totalLosses: statsFor2020.totalLosses),
|
||||
"2021_overall":Stats(totalWins: statsFor2021.totalWins, totalLosses: statsFor2021.totalLosses),
|
||||
"mw_six_players":Stats(totalWins: mwSixPlayers!.totalWins, totalLosses: mwSixPlayers!.totalLosses),
|
||||
"overall_four_players":Stats(totalWins: overallFourPlayers!.totalWins, totalLosses: overallFourPlayers!.totalLosses),
|
||||
"mw_five_players":Stats(totalWins: mwFivePlayers!.totalWins, totalLosses: mwFivePlayers!.totalLosses),
|
||||
"overall":Stats(totalWins: overallStats!.totalWins, totalLosses: overallStats!.totalLosses),
|
||||
|
||||
]
|
||||
return OverallStats(overall: overallStats!, mwStats: mwStats!, bocwStats: bocwStats!, mostRecentRecord: "Temporarily Unavailable", statsWithHyder:hyderStats[0], statsWithoutHyder: hyderStats[1], dashboardItems:dashboardItems)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func overall(db: Database) throws -> EventLoopFuture<OverallStats> {
|
||||
|
||||
|
||||
let dashboardStats = try dashboard(db:db)
|
||||
|
||||
let matches = Match.query(on: db).sort( \.$date).all()
|
||||
|
||||
return matches.and(dashboardStats).map { arg -> (OverallStats) in
|
||||
|
||||
let (matches, dashboardStats) = arg
|
||||
|
||||
|
||||
let queue = DispatchQueue(label: "com.sledsoft.cod-tracker.queue", attributes: .concurrent)
|
||||
let group = DispatchGroup()
|
||||
|
||||
var mapStats:[Int:Stats]?
|
||||
var worstMap:Int?
|
||||
var bestMap:Int?
|
||||
|
||||
//
|
||||
group.enter()
|
||||
queue.async {
|
||||
mapStats = self.getMapStats(matches: matches)
|
||||
//print ("maps done \(Date().timeIntervalSince(startTime))")
|
||||
group.leave()
|
||||
|
||||
}
|
||||
//
|
||||
group.enter()
|
||||
queue.async {
|
||||
let mapStats = self.getMapStats(matches: matches)
|
||||
|
||||
bestMap = self.getBestMap(records: mapStats)
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.enter()
|
||||
queue.async {
|
||||
let mapStats = self.getMapStats(matches: matches)
|
||||
|
||||
worstMap = self.getWorstMap(records: mapStats)
|
||||
group.leave()
|
||||
}
|
||||
|
||||
group.wait()
|
||||
|
||||
let dashboardItems:[DashboardItem] =
|
||||
|
||||
(dashboardStats.dashboardItems +
|
||||
[
|
||||
DashboardItem(codTrackerId:"best_map_overall", title: "Best Map", content: MapData.allMaps[bestMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![bestMap!]!.winLossRatio) \(mapStats![bestMap!]!.record)", sortOrder: 12),
|
||||
DashboardItem(codTrackerId:"worst_map_overall", title: "Worst Map", content: MapData.allMaps[worstMap!]?.name ?? "error", title2: "Ratio", content2: "\(mapStats![worstMap!]!.winLossRatio) \(mapStats![worstMap!]!.record)",sortOrder: 13),
|
||||
DashboardItem(codTrackerId:"adam_ruined_final_kills", title: "Final Kills Ruined by Adam", content: "\(matches.filter{$0.finalKillRuinedPlayerId == 6}.count + 7)", title2: "", content2: "",sortOrder: 14),
|
||||
|
||||
]).sorted{
|
||||
$0.sortOrder < $1.sortOrder
|
||||
}
|
||||
return OverallStats(dashboardItems: dashboardItems)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func overall(req: Request) throws -> EventLoopFuture<OverallStats> {
|
||||
|
||||
return try overall(db: req.db)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -722,8 +893,8 @@ struct StatsController: RouteCollection {
|
||||
|
||||
}
|
||||
|
||||
func statsWithPlayer(req: Request, playerId:Int) -> EventLoopFuture<Stats> {
|
||||
return Match.query(on: req.db)
|
||||
func statsWithPlayer(db: Database, playerId:Int) -> EventLoopFuture<Stats> {
|
||||
return Match.query(on: db)
|
||||
.filter(\.$players ~~ "\(playerId)")
|
||||
.all().map { (matches) -> (Stats) in
|
||||
return self.getCountedMatches(matches: matches)
|
||||
@@ -731,8 +902,8 @@ struct StatsController: RouteCollection {
|
||||
|
||||
}
|
||||
|
||||
func statsWithoutPlayer (req: Request, playerId:Int) -> EventLoopFuture<Stats> {
|
||||
return Match.query(on: req.db)
|
||||
func statsWithoutPlayer (db: Database, playerId:Int) -> EventLoopFuture<Stats> {
|
||||
return Match.query(on: db)
|
||||
.filter(\.$players !~ "\(playerId)")
|
||||
.all().map { (matches) -> (Stats) in
|
||||
return self.getCountedMatches(matches: matches)
|
||||
|
||||
@@ -9,6 +9,7 @@ struct CreateStatistics: Migration {
|
||||
.field("title", .string)
|
||||
.field("wins", .int)
|
||||
.field("losses", .int)
|
||||
|
||||
.create()
|
||||
}
|
||||
|
||||
@@ -19,6 +20,22 @@ struct CreateStatistics: Migration {
|
||||
}
|
||||
|
||||
|
||||
struct AddCodTrackerId: Migration {
|
||||
// Prepares the database for storing Galaxy models.
|
||||
func prepare(on database: Database) -> EventLoopFuture<Void> {
|
||||
database.schema("statistics")
|
||||
|
||||
.field("cod_tracker_id", .string)
|
||||
.update()
|
||||
}
|
||||
|
||||
// Optionally reverts the changes made in the prepare method.
|
||||
func revert(on database: Database) -> EventLoopFuture<Void> {
|
||||
database.schema("statistics").delete()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//struct CreateMatch: Migration {
|
||||
// func prepare(on database: Database) -> EventLoopFuture<Void> {
|
||||
|
||||
@@ -100,8 +100,8 @@ final class Match: Model, Content {
|
||||
|
||||
}
|
||||
|
||||
extension Match {
|
||||
struct Create: Content {
|
||||
}
|
||||
}
|
||||
//extension Match {
|
||||
// struct Create: Content {
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Statistics.swift
|
||||
// WinLossRecords.swift
|
||||
// Statistics
|
||||
//
|
||||
// Created by Michael Simard on 8/2/21.
|
||||
@@ -8,9 +8,9 @@
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
final class Statistics: Model {
|
||||
final class WinLossRecords: Model, Content {
|
||||
// Name of the table or collection.
|
||||
static let schema = "statistics"
|
||||
static let schema = "win_loss_records"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
@@ -18,22 +18,45 @@ final class Statistics: Model {
|
||||
@Field(key: "title")
|
||||
var title: String
|
||||
|
||||
@Field(key: "Wins")
|
||||
@Field(key: "wins")
|
||||
var wins: Int
|
||||
|
||||
@Field(key: "losses")
|
||||
var losses: Int
|
||||
|
||||
@Field(key: "cod_tracker_id")
|
||||
var codTrackerId: String
|
||||
|
||||
|
||||
// Creates a new, empty .
|
||||
init() { }
|
||||
|
||||
// Creates a new with all properties set.
|
||||
init(id: UUID? = nil, title: String, wins: Int, losses: Int) {
|
||||
init(id: UUID? = nil, title: String, wins: Int, losses: Int, codTrackerId:String) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.wins = wins
|
||||
self.losses = losses
|
||||
self.codTrackerId = codTrackerId
|
||||
|
||||
}
|
||||
|
||||
func update( statistics:WinLossRecords) {
|
||||
|
||||
print ("saving: \(statistics.codTrackerId)")
|
||||
guard let _ = statistics.id else {
|
||||
return
|
||||
}
|
||||
print ("saving: \(statistics)")
|
||||
|
||||
|
||||
self.id = statistics.id
|
||||
self.title = statistics.title
|
||||
self.wins = statistics.wins
|
||||
self.losses = statistics.losses
|
||||
self.codTrackerId = statistics.codTrackerId
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
29
Sources/App/Utils.swift
Normal file
29
Sources/App/Utils.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Utils.swift
|
||||
// Utils
|
||||
//
|
||||
// Created by Michael Simard on 8/2/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Utils {
|
||||
|
||||
|
||||
static func getRatio(wins:Int, losses:Int) -> String {
|
||||
var returnString = ""
|
||||
let deno = (losses != 0) ? losses : 1
|
||||
returnString = String(Utils.getRatioDouble(wins: wins, losses: losses))
|
||||
if deno == 0 {
|
||||
returnString = returnString + "+"
|
||||
}
|
||||
return returnString
|
||||
}
|
||||
|
||||
static func getRatioDouble(wins:Int, losses:Int) -> Double {
|
||||
let deno = (losses != 0) ? losses : 1
|
||||
|
||||
return (Double(wins) / Double(deno)).truncate(places: 3)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@ public func configure(_ app: Application) throws {
|
||||
app.http.server.configuration.hostname = "0.0.0.0"
|
||||
app.http.server.configuration.port = 8080
|
||||
|
||||
app.commands.use(RecalulateRecords(), as: "recalc")
|
||||
|
||||
|
||||
app.databases.use(.postgres(
|
||||
hostname: Environment.get("DATABASE_HOST") ?? "localhost",
|
||||
@@ -26,6 +28,7 @@ public func configure(_ app: Application) throws {
|
||||
|
||||
//app.migrations.add(CreateMatch())
|
||||
app.migrations.add(CreateStatistics())
|
||||
app.migrations.add(AddCodTrackerId())
|
||||
|
||||
|
||||
|
||||
@@ -45,23 +48,11 @@ formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
encodeFormatter.calendar = Calendar(identifier: .iso8601)
|
||||
encodeFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
||||
let date = Date()
|
||||
let tz = TimeZone.current
|
||||
// if tz.isDaylightSavingTime(for: date) {
|
||||
// encodeFormatter.timeZone = TimeZone(abbreviation: "EST")
|
||||
// }
|
||||
// else{
|
||||
// encodeFormatter.timeZone = TimeZone(abbreviation: "EDT")
|
||||
//
|
||||
// }
|
||||
|
||||
encodeFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
encoder.dateEncodingStrategy = .formatted(encodeFormatter)
|
||||
|
||||
|
||||
// override the global encoder used for the `.json` media type
|
||||
ContentConfiguration.global.use(decoder: decoder, for: .json)
|
||||
// ContentConfiguration.global.use(encoder: encoder, for: .json)
|
||||
|
||||
// register routes
|
||||
try routes(app)
|
||||
|
||||
56
Sources/App/db/DBHelpers.swift
Normal file
56
Sources/App/db/DBHelpers.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// DBHelpers.swift
|
||||
// App
|
||||
//
|
||||
// Created by Michael Simard on 8/4/21.
|
||||
//
|
||||
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
class DBHelpers {
|
||||
|
||||
static func relcalulateRecords(db:Database, completion:(_ message:String)->()){
|
||||
|
||||
let group = DispatchGroup()
|
||||
_ = try? StatsController().forceCalculatedStats(db: db).map({ statsDict in
|
||||
|
||||
|
||||
|
||||
for key in statsDict.keys {
|
||||
print (key)
|
||||
group.enter()
|
||||
}
|
||||
|
||||
for key in statsDict.keys {
|
||||
|
||||
let s:EventLoopFuture<Void> = WinLossRecords.query(on: db).filter(\.$codTrackerId == key).first()
|
||||
.unwrap(or: Abort(.notFound))
|
||||
.flatMap {
|
||||
|
||||
$0.update(statistics: WinLossRecords(id: $0.id, title: $0.title, wins: statsDict[key]?.totalWins ?? 0, losses: statsDict[key]?.totalLosses ?? 0, codTrackerId: $0.codTrackerId))
|
||||
|
||||
return $0.save(on: db)
|
||||
}
|
||||
|
||||
|
||||
s.whenComplete { result in
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
.wait()
|
||||
|
||||
|
||||
group.wait()
|
||||
|
||||
completion("complete")
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user