mod leaderboard history
parent
97cdb41e27
commit
bb19f26f14
|
@ -131,9 +131,10 @@ object Mod extends LilaController {
|
|||
|
||||
def gamify = Secure(_.SeeReport) { implicit ctx =>
|
||||
me =>
|
||||
Env.mod.gamify.leaderboards map { leaderboards =>
|
||||
Ok(html.mod.gamify.index(leaderboards))
|
||||
}
|
||||
Env.mod.gamify.leaderboards zip
|
||||
Env.mod.gamify.history(orCompute = true) map {
|
||||
case (leaderboards, history) => Ok(html.mod.gamify.index(leaderboards, history))
|
||||
}
|
||||
}
|
||||
def gamifyPeriod(periodStr: String) = Secure(_.SeeReport) { implicit ctx =>
|
||||
me =>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
@(leaderboards: lila.mod.Gamify.Leaderboards)(implicit ctx: Context)
|
||||
@(leaderboards: lila.mod.Gamify.Leaderboards, history: List[lila.mod.Gamify.HistoryMonth])(implicit ctx: Context)
|
||||
|
||||
@import lila.mod.Gamify.Period
|
||||
|
||||
@title = @{ "Moderator hall of fame" }
|
||||
|
||||
@yearHeader(year: Int) = @{
|
||||
Html(s"""<tr class="year">
|
||||
<th>$year</th>
|
||||
<th>Champions of the past</th>
|
||||
<th>Score</th>
|
||||
<th>Actions taken</th>
|
||||
<th>Reports closed</th>
|
||||
</tr>""")
|
||||
}
|
||||
|
||||
@mod.layout(
|
||||
title = title,
|
||||
active = "gamify",
|
||||
|
@ -23,7 +33,25 @@ moreCss = cssTag("mod-gamify.css")) {
|
|||
</div>
|
||||
</div>
|
||||
<div class="history">
|
||||
TODO: History of former champions for past months.
|
||||
<table class="slist">
|
||||
<tbody>
|
||||
@history.headOption.filterNot(_.date.getMonthOfYear == 12).map { h =>
|
||||
@yearHeader(h.date.getYear)
|
||||
}
|
||||
@history.map { h =>
|
||||
@if(h.date.getMonthOfYear == 12) {
|
||||
@yearHeader(h.date.getYear)
|
||||
}
|
||||
<tr>
|
||||
<th>@h.date.monthOfYear.getAsText</th>
|
||||
<th>@userIdLink(h.champion.modId.some, withOnline = false)</th>
|
||||
<td class="score">@h.champion.score.localize</td>
|
||||
<td>@h.champion.action.localize</td>
|
||||
<td>@h.champion.report.localize</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@(perfType: lila.rating.PerfType, users: List[lila.user.User.LightPerf])(implicit ctx: Context)
|
||||
|
||||
@user.layout(trans.onlinePlayers.str()) {
|
||||
@title = @{ "s{perfType.name} top 200" }
|
||||
e
|
||||
@user.layout(title) {
|
||||
<div class="content_box small_box">
|
||||
<h1>
|
||||
<a href="@routes.User.list" data-icon="I" class="text">
|
||||
@perfType.name top 200
|
||||
</a>
|
||||
<a href="@routes.User.list" data-icon="I" class="text">@title</a>
|
||||
</h1>
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -287,6 +287,7 @@ mod {
|
|||
modlog = modlog
|
||||
player_assessment = player_assessment
|
||||
boosting = boosting
|
||||
gaming_history = mod_gaming_history
|
||||
}
|
||||
boosting.nb_games_to_mark = 5
|
||||
boosting.ratio_games_to_mark = 0.01
|
||||
|
|
|
@ -21,6 +21,7 @@ final class Env(
|
|||
val CollectionPlayerAssessment = config getString "collection.player_assessment"
|
||||
val CollectionBoosting = config getString "collection.boosting"
|
||||
val CollectionModlog = config getString "collection.modlog"
|
||||
val CollectionGamingHistory = config getString "collection.gaming_history"
|
||||
val ActorName = config getString "actor.name"
|
||||
val NbGamesToMark = config getInt "boosting.nb_games_to_mark"
|
||||
val RatioGamesToMark = config getDouble "boosting.ratio_games_to_mark"
|
||||
|
@ -56,7 +57,8 @@ final class Env(
|
|||
|
||||
lazy val gamify = new Gamify(
|
||||
logColl = logColl,
|
||||
reportColl = reportColl)
|
||||
reportColl = reportColl,
|
||||
historyColl = db(CollectionGamingHistory))
|
||||
|
||||
private val neuralApi = new NeuralApi(
|
||||
endpoint = NeuralApiEndpoint,
|
||||
|
|
|
@ -11,22 +11,60 @@ import lila.memo.AsyncCache
|
|||
|
||||
final class Gamify(
|
||||
logColl: Coll,
|
||||
reportColl: Coll) {
|
||||
reportColl: Coll,
|
||||
historyColl: Coll) {
|
||||
|
||||
import Gamify._
|
||||
|
||||
def history(orCompute: Boolean = true): Fu[List[HistoryMonth]] = {
|
||||
val until = DateTime.now minusMonths 1 withDayOfMonth 1
|
||||
val lastId = HistoryMonth.makeId(until.getYear, until.getMonthOfYear)
|
||||
historyColl.find(BSONDocument()).sort(BSONDocument(
|
||||
"year" -> -1,
|
||||
"month" -> -1
|
||||
)).cursor[HistoryMonth]().collect[List]().flatMap { months =>
|
||||
months.headOption match {
|
||||
case Some(m) if m._id == lastId || !orCompute => fuccess(months)
|
||||
case Some(m) => buildHistoryAfter(m.year, m.month, until) >> history(false)
|
||||
case _ => buildHistoryAfter(2012, 6, until) >> history(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private implicit val modMixedBSONHandler = Macros.handler[ModMixed]
|
||||
private implicit val historyMonthBSONHandler = Macros.handler[HistoryMonth]
|
||||
|
||||
private def buildHistoryAfter(afterYear: Int, afterMonth: Int, until: DateTime): Funit =
|
||||
(afterYear to until.getYear).flatMap { year =>
|
||||
((year == afterYear).fold(afterMonth + 1, 1) to
|
||||
(year == until.getYear).fold(until.getMonthOfYear, 12)).map { month =>
|
||||
mixedLeaderboard(
|
||||
after = new DateTime(year, month, 1, 0, 0).pp("compute mod history"),
|
||||
before = new DateTime(year, month, 1, 0, 0).plusMonths(1).some
|
||||
).map {
|
||||
_.headOption.map { champ =>
|
||||
HistoryMonth(HistoryMonth.makeId(year, month), year, month, champ)
|
||||
}
|
||||
}
|
||||
}.toList
|
||||
}.toList.sequenceFu.map(_.flatten).flatMap {
|
||||
_.map { month =>
|
||||
historyColl.update(BSONDocument("_id" -> month._id), month, upsert = true)
|
||||
}.sequenceFu
|
||||
}.void
|
||||
|
||||
def leaderboards = leaderboardsCache(true)
|
||||
|
||||
private val leaderboardsCache = AsyncCache.single[Leaderboards](
|
||||
f = mixedLeaderboard(_ minusDays 1) zip
|
||||
mixedLeaderboard(_ minusWeeks 1) zip
|
||||
mixedLeaderboard(_ minusMonths 1) map {
|
||||
f = mixedLeaderboard(DateTime.now minusDays 1, none) zip
|
||||
mixedLeaderboard(DateTime.now minusWeeks 1, none) zip
|
||||
mixedLeaderboard(DateTime.now minusMonths 1, none) map {
|
||||
case ((daily, weekly), monthly) => Leaderboards(daily, weekly, monthly)
|
||||
},
|
||||
timeToLive = 10 seconds)
|
||||
|
||||
private def mixedLeaderboard(since: DateTime => DateTime): Fu[List[ModMixed]] =
|
||||
actionLeaderboard(since) zip reportLeaderboard(since) map {
|
||||
private def mixedLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModMixed]] =
|
||||
actionLeaderboard(after, before) zip reportLeaderboard(after, before) map {
|
||||
case (actions, reports) => actions.map(_.modId) intersect reports.map(_.modId) map { modId =>
|
||||
ModMixed(modId,
|
||||
action = actions.find(_.modId == modId) ?? (_.count),
|
||||
|
@ -34,31 +72,31 @@ final class Gamify(
|
|||
} sortBy (-_.score)
|
||||
}
|
||||
|
||||
private def after(date: DateTime) = BSONDocument("$gte" -> date)
|
||||
private def dateRange(from: DateTime, toOption: Option[DateTime]) =
|
||||
BSONDocument("$gte" -> from) ++ toOption.?? { to => BSONDocument("$lt" -> to) }
|
||||
|
||||
private val notLichess = BSONDocument("$ne" -> "lichess")
|
||||
|
||||
private def actionLeaderboard(since: DateTime => DateTime): Fu[List[ModCount]] =
|
||||
private def actionLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] =
|
||||
logColl.aggregate(Match(BSONDocument(
|
||||
"date" -> after(since(DateTime.now)),
|
||||
"date" -> dateRange(after, before),
|
||||
"mod" -> notLichess
|
||||
)), List(
|
||||
GroupField("mod")("nb" -> SumValue(1)),
|
||||
Sort(Descending("nb"))
|
||||
)).map {
|
||||
Sort(Descending("nb")))).map {
|
||||
_.documents.flatMap { obj =>
|
||||
obj.getAs[String]("_id") |@| obj.getAs[Int]("nb") apply ModCount.apply
|
||||
}
|
||||
}
|
||||
|
||||
private def reportLeaderboard(since: DateTime => DateTime): Fu[List[ModCount]] =
|
||||
private def reportLeaderboard(after: DateTime, before: Option[DateTime]): Fu[List[ModCount]] =
|
||||
reportColl.aggregate(
|
||||
Match(BSONDocument(
|
||||
"createdAt" -> after(since(DateTime.now)),
|
||||
"createdAt" -> dateRange(after, before),
|
||||
"processedBy" -> notLichess
|
||||
)), List(
|
||||
GroupField("processedBy")("nb" -> SumValue(1)),
|
||||
Sort(Descending("nb"))
|
||||
)).map {
|
||||
Sort(Descending("nb")))).map {
|
||||
_.documents.flatMap { obj =>
|
||||
obj.getAs[String]("_id") |@| obj.getAs[Int]("nb") apply ModCount.apply
|
||||
}
|
||||
|
@ -67,6 +105,13 @@ final class Gamify(
|
|||
|
||||
object Gamify {
|
||||
|
||||
case class HistoryMonth(_id: String, year: Int, month: Int, champion: ModMixed) {
|
||||
def date = new DateTime(year, month, 1, 0, 0)
|
||||
}
|
||||
object HistoryMonth {
|
||||
def makeId(year: Int, month: Int) = s"$year/$month"
|
||||
}
|
||||
|
||||
sealed trait Period {
|
||||
def name = toString.toLowerCase
|
||||
}
|
||||
|
|
|
@ -79,6 +79,24 @@
|
|||
font-size: 1.5em;
|
||||
}
|
||||
#mod-gamify .history {
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#mod-gamify .history table tr.year th {
|
||||
font-family: 'PT Serif';
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
#mod-gamify .history table tr.year th:first-child {
|
||||
padding-left: 25px;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
#mod-gamify .history table tr.year th:nth-child(n+3),
|
||||
#mod-gamify .history table tr td {
|
||||
text-align: right;
|
||||
padding-right: 25px;
|
||||
}
|
||||
#mod-gamify .history table a,
|
||||
#mod-gamify .history table .score {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue