lila/modules/clas/src/main/ClasProgress.scala

171 lines
4.5 KiB
Scala

package lila.clas
import org.joda.time.{ DateTime, Period }
import reactivemongo.api._
import reactivemongo.api.bson._
import lila.db.dsl._
import lila.game.{ Game, GameRepo }
import lila.puzzle.PuzzleRound
import lila.rating.PerfType
import lila.user.User
case class ClasProgress(
perfType: PerfType,
days: Int,
students: Map[User.ID, StudentProgress]
) {
def apply(user: User) =
students.getOrElse(
user.id,
StudentProgress(
nb = 0,
rating = (user.perfs(perfType).intRating, user.perfs(perfType).intRating),
wins = 0,
millis = 0
)
)
def isPuzzle = perfType == PerfType.Puzzle
}
case class StudentProgress(
nb: Int,
wins: Int,
millis: Long,
rating: (Int, Int)
) {
def ratingProgress = rating._2 - rating._1
def winRate = if (nb > 0) wins * 100 / nb else 0
def period = new Period(millis)
}
final class ClasProgressApi(
gameRepo: GameRepo,
historyApi: lila.history.HistoryApi,
puzzleColls: lila.puzzle.PuzzleColls,
studentCache: ClasStudentCache
)(implicit ec: scala.concurrent.ExecutionContext) {
case class PlayStats(nb: Int, wins: Int, millis: Long)
def apply(perfType: PerfType, days: Int, students: List[Student.WithUser]): Fu[ClasProgress] = {
val users = students.map(_.user)
val userIds = users.map(_.id)
val playStatsFu =
if (perfType == PerfType.Puzzle) getPuzzleStats(userIds, days)
else getGameStats(perfType, userIds, days)
val progressesFu = historyApi.progresses(users, perfType, days)
playStatsFu zip progressesFu map { case (playStats, progresses) =>
ClasProgress(
perfType,
days,
users zip progresses map { case (u, rating) =>
val playStat = playStats get u.id
u.id -> StudentProgress(
nb = playStat.??(_.nb),
rating = rating,
wins = playStat.??(_.wins),
millis = playStat.??(_.millis)
)
} toMap
)
}
}
private def getPuzzleStats(userIds: List[User.ID], days: Int): Fu[Map[User.ID, PlayStats]] =
puzzleColls.round {
_.aggregateList(
maxDocs = Int.MaxValue,
ReadPreference.secondaryPreferred
) { framework =>
import framework._
Match(
$doc(
PuzzleRound.BSONFields.user $in userIds,
PuzzleRound.BSONFields.date $gt DateTime.now.minusDays(days)
)
) -> List(
GroupField("u")(
"nb" -> SumAll,
"win" -> Sum(
$doc(
"$cond" -> $arr("$w", 1, 0)
)
)
)
)
}.map {
_.flatMap { obj =>
obj.string("_id") map { id =>
id -> PlayStats(
nb = ~obj.int("nb"),
wins = ~obj.int("win"),
millis = 0
)
}
}.toMap
}
}
private def getGameStats(
perfType: PerfType,
userIds: List[User.ID],
days: Int
): Fu[Map[User.ID, PlayStats]] = {
import Game.{ BSONFields => F }
import lila.game.Query
gameRepo.coll
.aggregateList(
maxDocs = Int.MaxValue,
ReadPreference.secondaryPreferred
) { framework =>
import framework._
Match(
$doc(
F.playerUids $in userIds,
Query.createdSince(DateTime.now minusDays days),
F.perfType -> perfType.id
)
) -> List(
Project(
$doc(
F.playerUids -> true,
F.winnerId -> true,
"ms" -> $doc("$subtract" -> $arr(s"$$${F.movedAt}", s"$$${F.createdAt}")),
F.id -> false
)
),
UnwindField(F.playerUids),
Match($doc(F.playerUids $in userIds)),
GroupField(F.playerUids)(
"nb" -> SumAll,
"win" -> Sum(
$doc(
"$cond" -> $arr($doc("$eq" -> $arr("$us", "$wid")), 1, 0)
)
),
"ms" -> SumField("ms")
)
)
}
.map {
_.flatMap { obj =>
obj.string(F.id) map { id =>
id -> PlayStats(
nb = ~obj.int("nb"),
wins = ~obj.int("win"),
millis = ~obj.long("ms")
)
}
}.toMap
}
}
private[clas] def onFinishGame(game: lila.game.Game): Unit =
if (game.userIds.exists(studentCache.isStudent)) gameRepo.denormalizePerfType(game)
}