import Fluent import Vapor struct CODDate { let month:Int let year:Int let day: Int let hour:Int let minute:Int } struct StatsController: RouteCollection { func boot(routes: RoutesBuilder) throws { let statsRoute = routes.grouped("cod-tracker","api", "stats") statsRoute.get("allMatches", use: index) statsRoute.get("totalWins", use: totalWins) statsRoute.get("totalLosses", use: totalLosses) statsRoute.get("overall", use: overall) statsRoute.get("all", use: all) statsRoute.get("allDaily", use: allDaily) } func index(req: Request) throws -> EventLoopFuture<[Match]> { return Match.query(on: req.db).sort(\.$date).all() } func totalWins(req: Request) throws -> EventLoopFuture { return Match.query(on: req.db) .filter(\.$win == true) .count() } func totalLosses(req: Request) throws -> EventLoopFuture { return Match.query(on: req.db) .filter(\.$win == false) .count() } func getMarchStats(req:Request) throws -> EventLoopFuture { return getstatsForMonth(year: 2020, month: 03, req: req) } func getstatsForMonth(year:Int, month:Int, req: Request) -> EventLoopFuture{ let winCount = Match.query(on: req.db) .filter(\.$date >= getStartOfMonth(month: month, year: year)) .filter(\.$date <= getEndOfMonth(month: month, year: year)) .filter(\.$win == true ) .count() let lossCount = Match.query(on: req.db) .filter(\.$date >= getStartOfMonth(month: month, year: year)) .filter(\.$date <= getEndOfMonth(month: month, year: year)) .filter(\.$win == false ) .count() let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in let ratio:Double = (Double(winCount) / Double(lossCount)).truncate(places: 2) return Stats.init(winLoss: String(ratio), totalWins: winCount, totalLosses: lossCount) } } func getStatsForDay(year:Int, month:Int, day:Int, req: Request) -> EventLoopFuture{ let winCount = Match.query(on: req.db) .filter(\.$date >= getStartOfDay(day:day, month: month, year: year)) .filter(\.$date <= getEndOfDay(day: day, month: month, year: year)) .filter(\.$win == true ) .count() let lossCount = Match.query(on: req.db) .filter(\.$date >= getStartOfDay(day:day, month: month, year: year)) .filter(\.$date <= getEndOfDay(day: day, month: month, year: year)) .filter(\.$win == false ) .count() let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in return Stats.init(winLoss: self.getRatio(num: Double(winCount), den: Double(lossCount)), totalWins: winCount, totalLosses: lossCount) } } func statsForRecent(numberGames:Int, req:Request) -> EventLoopFuture { let winCount = Match.query(on: req.db) .sort(\.$date) .range(lower: 0, upper: numberGames) .filter(\.$win == true ) .count() let lossCount = Match.query(on: req.db) .sort(\.$date) .range(lower: 0, upper: numberGames) .filter(\.$win == false ) .count() let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in let ratio:Double = (Double(winCount) / Double(lossCount)).truncate(places: 2) return Stats.init(winLoss: String(ratio), totalWins: winCount, totalLosses: lossCount) } } private func getStartOfMonth(month:Int, year:Int) -> Date { let calendar = Calendar.current var components = DateComponents() components.timeZone = TimeZone(identifier: "GMT") components.day = 1 components.month = month components.year = year components.hour = 0 components.minute = 0 return calendar.date(from: components)! } private func getEndOfMonth(month:Int, year:Int) -> Date { let calendar = Calendar.current var components = DateComponents() components.day = -0 components.timeZone = TimeZone(identifier: "GMT") components.month = month + 1 components.year = year components.hour = 23 components.minute = 59 return calendar.date(from: components)! } private func getStartOfDay(day:Int, month:Int, year:Int) -> Date { let calendar = Calendar.current var components = DateComponents() components.timeZone = TimeZone(identifier: "GMT") components.day = day components.month = month components.year = year components.hour = 0 components.minute = 0 return calendar.date(from: components)! } private func getEndOfDay(day:Int, month:Int, year:Int) -> Date { let calendar = Calendar.current var components = DateComponents() components.timeZone = TimeZone(identifier: "GMT") components.day = day components.month = month components.year = year components.hour = 23 components.minute = 59 return calendar.date(from: components)! } private func getStartDate() -> Date { let calendar = Calendar.current var components = DateComponents() components.timeZone = TimeZone(identifier: "GMT") components.day = 10 components.month = 03 components.year = 2020 components.hour = 4 components.minute = 0 return calendar.date(from: components)! } private func createDate(day:Int, month:Int, year:Int, hour:Int, minute:Int) -> Date { let calendar = Calendar.current var components = DateComponents() components.timeZone = TimeZone(identifier: "GMT") components.day = day components.month = month components.year = year components.hour = hour components.minute = minute return calendar.date(from: components)! } func mostRecentDailyStats (req:Request) -> EventLoopFuture{ return getDaysPlayed(req: req).flatMap { (days) -> (EventLoopFuture) in return self.getStatsForDay(year: days.first?.year ?? 0, month: days.first?.month ?? 0, day: days.first?.day ?? 0, req: req) } } func all(req: Request) throws -> EventLoopFuture { var date = getStartDate() var previousMonths:[CODDate] = [] repeat { //let stats = getStatsByMonth(year: date.year, month: date.month, req: req) //returns eventloopfuture previousMonths.append(CODDate(month: date.month, year: date.year, day: 15, hour:6, minute: 0)) date = Calendar.current.date(byAdding: .month, value: 1, to: date)! } while (date.month != (Date().month + 1) || date.year != Date().year) func getMonthStats (_ remaining: ArraySlice, allMonthlyStats: inout [MonthStats], eventLoop: EventLoop) -> EventLoopFuture<[MonthStats]> { var remaining = remaining if let first = remaining.popLast() { return getstatsForMonth(year: first.year, month: first.month, req: req).flatMap { [remaining, allMonthlyStats] (stats) -> EventLoopFuture<[MonthStats]> in var allMonthlyStats = allMonthlyStats allMonthlyStats.append(MonthStats(month: first.month, year: first.year, stats:stats )) return getMonthStats(remaining, allMonthlyStats:&allMonthlyStats, eventLoop: eventLoop) } } else { return req.eventLoop.makeSucceededFuture(allMonthlyStats) } } var stats:[MonthStats] = [] let monthstats = getMonthStats(previousMonths[0.. AllStats in let (((overall, monthlyStats), cumulativeRatios), mostRecentDayStats) = arg let highestWinLossRatio = cumulativeRatios.reduce("0") { (highestRatio, dataPoint) -> String in if dataPoint.y > Double(highestRatio)!{ return String(dataPoint.y) } return highestRatio } return AllStats.init(overall: overall, byMonth: monthlyStats, highestWinLossRatio: highestWinLossRatio, dataPoints: cumulativeRatios, mostRecentRecord: "\(mostRecentDayStats.totalWins) - \(mostRecentDayStats.totalLosses)") } } func overall(req: Request) throws -> EventLoopFuture { let lossCount = Match.query(on: req.db) .filter(\.$win == false) .count() let winCount = Match.query(on: req.db) .filter(\.$win == true ) .count() let combined = winCount.and(lossCount) return combined.map { (winCount, lossCount) -> (Stats) in return Stats.init(winLoss: self.getRatio(num: Double(winCount), den: Double(lossCount)), totalWins: winCount, totalLosses: lossCount) } } private func getRatio( num:Double, den:Double) -> String { var returnString = "" let deno = (den != 0) ? den : 1 returnString = String((Double(num) / Double(deno)).truncate(places: 2)) if den == 0 { returnString = returnString + "+" } return returnString } private func getDaysPlayed(req:Request) -> EventLoopFuture<[CODDate]> { return Match.query(on: req.db).sort(\.$date, .descending).all().map { (matches) -> ([CODDate]) in return matches.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) }.reduce([CODDate]()) { (datesPlayed, codDate) -> [CODDate] in if datesPlayed.contains(where: { (existingDate) -> Bool in if codDate.month == existingDate.month && codDate.year == existingDate.year && existingDate.day == codDate.day{ return true } return false }){ return datesPlayed }else { return datesPlayed + [codDate] } } } // repeat { // previousDays.append(CODDate(month: date.month, year: date.year, day: date.day, hour: date.hour, minute: date.minute)) // date = Calendar.current.date(byAdding: .day, value: 1, to: date)! // } while (date < Date()) // // return previousDays } func allDaily(req:Request) -> EventLoopFuture{ return getDaysPlayed(req: req).flatMap { (previousDays) -> (EventLoopFuture) in func getDailyStats (_ remaining: ArraySlice, allDailyStats: inout [DailyStats], eventLoop: EventLoop) -> EventLoopFuture<[DailyStats]> { var remaining = remaining if let first = remaining.popLast() { return self.getStatsForDay(year: first.year, month: first.month, day:first.day, req: req).flatMap { [remaining, allDailyStats] (stats) -> EventLoopFuture<[DailyStats]> in var allDailyStats = allDailyStats let totalWins = allDailyStats.reduce(Double(stats.totalWins)) { (total, dailyStats) -> Double in return total + Double(dailyStats.stats.totalWins) } let totalLosses = allDailyStats.reduce(Double(stats.totalLosses)) { (total, dailyStats) -> Double in return total + Double(dailyStats.stats.totalLosses) } allDailyStats.append(DailyStats(day: first.day, month: first.month, year: first.year, stats: stats, cumulativeRatio: self.getRatio(num: totalWins, den: totalLosses))) return getDailyStats(remaining, allDailyStats:&allDailyStats, eventLoop: eventLoop) } } else { return req.eventLoop.makeSucceededFuture(allDailyStats) } } var stats:[DailyStats] = [] let dailyStats = getDailyStats(Array(previousDays)[0.. AllDailyStats in return AllDailyStats(dailyStats: dailyStats.filter({ (dailyStats) -> Bool in if dailyStats.stats.totalWins == 0 && dailyStats.stats.totalLosses == 0 { return false } return true }).reversed() ) } } } func getCumulativeWinLossRatios(req:Request) -> EventLoopFuture<[DataPoint]> { // let previousDays = getDaysPlayed().reversed() return getDaysPlayed(req: req).flatMap { (previousDays) -> (EventLoopFuture<[DataPoint]>) in func getRatios (_ remaining: ArraySlice, allDailyStats: inout [DailyStats], cumulativeWinLossRatios: inout [DataPoint], eventLoop: EventLoop) -> EventLoopFuture<[DataPoint]> { var remaining = remaining if let first = remaining.popLast() { return self.getStatsForDay(year: first.year, month: first.month, day:first.day, req: req).flatMap { [remaining, allDailyStats, cumulativeWinLossRatios] (stats) -> EventLoopFuture<[DataPoint]> in var allDailyStats = allDailyStats let totalWins = allDailyStats.reduce(Double(stats.totalWins)) { (total, dailyStats) -> Double in return total + Double(dailyStats.stats.totalWins) } let totalLosses = allDailyStats.reduce(Double(stats.totalLosses)) { (total, dailyStats) -> Double in return total + Double(dailyStats.stats.totalLosses) } var cumulativeWinLossRatios = cumulativeWinLossRatios if !(stats.totalWins == 0 && stats.totalLosses == 0) { let date = self.createDate(day: first.day, month: first.month, year: first.year, hour: first.hour + 6, minute:first.minute) // 6 hours to make sure we pick a time that isnt on borders of us time zones print ("p \(date.timeIntervalSince1970)") let x = Double(cumulativeWinLossRatios.count) let d = Date(timeIntervalSince1970: date.timeIntervalSince1970) cumulativeWinLossRatios.append(DataPoint(x:x , y: (totalWins/totalLosses).truncate(places: 2), label:("\(Utilities.monthToString(month: d.month)) \(d.day)"))) } allDailyStats.append(DailyStats(day: first.day, month: first.month, year: first.year, stats: stats, cumulativeRatio: self.getRatio(num: totalWins, den: totalLosses))) return getRatios(remaining, allDailyStats:&allDailyStats, cumulativeWinLossRatios:&cumulativeWinLossRatios, eventLoop: eventLoop) } } else { return req.eventLoop.makeSucceededFuture(cumulativeWinLossRatios) } } var stats:[DailyStats] = [] var cumulativeWinLossRatios:[DataPoint] = [DataPoint]() return getRatios(Array(previousDays)[0.. Double { return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places))) } } extension Date { var month:Int { var calendar = Calendar.current calendar.timeZone = TimeZone(identifier: "GMT")! let calanderDate = calendar.dateComponents([.minute, .month, .year, .hour, .day], from: self) return calanderDate.month ?? 1 } var year:Int { var calendar = Calendar.current calendar.timeZone = TimeZone(identifier: "GMT")! let calanderDate = calendar.dateComponents([.minute, .month, .year, .hour, .day], from: self) return calanderDate.year ?? 1 } var day:Int { var calendar = Calendar.current calendar.timeZone = TimeZone(identifier: "GMT")! let calanderDate = calendar.dateComponents([.minute, .month, .year, .hour, .day], from: self) return calanderDate.day ?? 1 } var hour:Int { var calendar = Calendar.current calendar.timeZone = TimeZone(identifier: "GMT")! let calanderDate = calendar.dateComponents([.minute, .month, .year, .hour, .day], from: self) return calanderDate.hour ?? 1 } var minute:Int { var calendar = Calendar.current calendar.timeZone = TimeZone(identifier: "GMT")! let calanderDate = calendar.dateComponents([.minute, .month, .year, .hour, .day], from: self) return calanderDate.minute ?? 1 } }