more work on weighted reports
parent
e901327f97
commit
42aebed3a1
|
@ -3,8 +3,8 @@ package templating
|
|||
|
||||
import play.twirl.api.Html
|
||||
|
||||
import lila.user.{ User, UserContext }
|
||||
import lila.security.{ Permission, Granter }
|
||||
import lila.user.{ User, UserContext }
|
||||
|
||||
trait SecurityHelper {
|
||||
|
||||
|
@ -21,6 +21,9 @@ trait SecurityHelper {
|
|||
Granter(permission)(user)
|
||||
|
||||
def reportScore(score: lila.report.Report.Score) = Html {
|
||||
s"Score: <strong>${score.value.toInt}</strong>"
|
||||
s"""<div class="score ${score.color}" title="Report score">${score.value.toInt}</div>"""
|
||||
}
|
||||
// def reportScore(score: lila.report.Report.Score) = Html {
|
||||
// s"""<div class="score"><i>Score</i><strong>${score.value.toInt}</strong></div>"""
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ moreCss = cssTag("report.css")) {
|
|||
<table class="slist see">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Score</th>
|
||||
<th>Author</th>
|
||||
<th>Reported</th>
|
||||
<th>For</th>
|
||||
|
|
|
@ -38,7 +38,8 @@ db.report.aggregate(
|
|||
text: r.text,
|
||||
score: 30
|
||||
})),
|
||||
score: 30
|
||||
score: 30,
|
||||
open: true
|
||||
};
|
||||
|
||||
db.report2.insert(report);
|
||||
|
|
|
@ -367,9 +367,7 @@ lazy val bookmark = module("bookmark", Seq(common, memo, db, hub, user, game)).s
|
|||
)
|
||||
|
||||
lazy val report = module("report", Seq(common, db, user, game, security)).settings(
|
||||
libraryDependencies ++= provided(
|
||||
play.api, reactivemongo.driver
|
||||
)
|
||||
libraryDependencies ++= provided(play.api, reactivemongo.driver, reactivemongo.iteratees)
|
||||
)
|
||||
|
||||
lazy val explorer = module("explorer", Seq(common, db, game, importer)).settings(
|
||||
|
|
|
@ -44,5 +44,6 @@ private[api] final class Cli(bus: lila.common.Bus) extends lila.common.Cli {
|
|||
lila.studySearch.Env.current.cli.process orElse
|
||||
lila.coach.Env.current.cli.process orElse
|
||||
lila.evalCache.Env.current.cli.process orElse
|
||||
lila.report.Env.current.cli.process orElse
|
||||
process
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@ final class Env(
|
|||
|
||||
lazy val modFilters = new ModReportFilter
|
||||
|
||||
def cli = new lila.common.Cli {
|
||||
def process = {
|
||||
case "report" :: "score" :: "reset" :: Nil => api.resetScores inject "done"
|
||||
}
|
||||
}
|
||||
|
||||
// api actor
|
||||
system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
|
|
|
@ -25,6 +25,7 @@ case class Report(
|
|||
def slug = _id
|
||||
|
||||
def closed = !open
|
||||
def suspect = SuspectId(user)
|
||||
|
||||
def add(atom: Atom) = atomBy(atom.by).fold(copy(atoms = atom <:: atoms)) { existing =>
|
||||
val newAtom = existing.copy(
|
||||
|
@ -73,6 +74,11 @@ object Report {
|
|||
|
||||
case class Score(value: Double) extends AnyVal {
|
||||
def +(s: Score) = Score(s.value + value)
|
||||
def color =
|
||||
if (value >= 150) "red"
|
||||
else if (value >= 100) "orange"
|
||||
else if (value >= 50) "yellow"
|
||||
else "green"
|
||||
}
|
||||
implicit val scoreIso = lila.common.Iso.double[Score](Score.apply, _.value)
|
||||
|
||||
|
@ -90,8 +96,8 @@ object Report {
|
|||
case class WithSuspect(report: Report, suspect: Suspect, isOnline: Boolean) {
|
||||
|
||||
def urgency: Int =
|
||||
(nowSeconds - report.recentAtom.at.getSeconds).toInt +
|
||||
(isOnline ?? (86400 * 5)) +
|
||||
report.score.value.toInt +
|
||||
(isOnline ?? 1000) +
|
||||
(report.closed ?? Int.MinValue)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ final class ReportApi(
|
|||
private implicit val ReporterIdBSONHandler = stringIsoHandler[ReporterId](ReporterId.reporterIdIso)
|
||||
private implicit val ScoreIdBSONHandler = doubleIsoHandler[Score](Report.scoreIso)
|
||||
private implicit val AtomBSONHandler = Macros.handler[Atom]
|
||||
private implicit val ReportBSONHandler = lila.db.BSON.LoggingHandler(logger)(Macros.handler[Report])
|
||||
private implicit val ReportBSONHandler = Macros.handler[Report]
|
||||
|
||||
private lazy val scorer = new ReportScore(getAccuracy = accuracy.of)
|
||||
|
||||
|
@ -236,6 +236,8 @@ final class ReportApi(
|
|||
}
|
||||
} yield withNotes
|
||||
|
||||
private[report] def resetScores: Funit = scorer reset coll void
|
||||
|
||||
object accuracy {
|
||||
|
||||
private val cache = asyncCache.clearable[User.ID, Option[Int]](
|
||||
|
@ -296,11 +298,13 @@ final class ReportApi(
|
|||
ReadPreference.secondaryPreferred
|
||||
)
|
||||
|
||||
private def findRecent(nb: Int, selector: Bdoc) =
|
||||
private def findRecent(nb: Int, selector: Bdoc): Fu[List[Report]] = (nb > 0) ?? {
|
||||
coll.find(selector).sort($sort.createdDesc).list[Report](nb)
|
||||
}
|
||||
|
||||
private def findBest(nb: Int, selector: Bdoc) =
|
||||
private def findBest(nb: Int, selector: Bdoc): Fu[List[Report]] = (nb > 0) ?? {
|
||||
coll.find(selector).sort($sort desc "score").list[Report](nb)
|
||||
}
|
||||
|
||||
private def selectRecent(suspect: Suspect, reason: Reason): Bdoc = $doc(
|
||||
"atoms.0.at" $gt DateTime.now.minusDays(7),
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package lila.report
|
||||
|
||||
import lila.user.User
|
||||
import reactivemongo.bson._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private final class ReportScore(
|
||||
getAccuracy: ReporterId => Fu[Option[Accuracy]]
|
||||
|
@ -8,11 +11,48 @@ private final class ReportScore(
|
|||
|
||||
def apply(candidate: Report.Candidate): Fu[Report.Candidate.Scored] =
|
||||
getAccuracy(candidate.reporter.id) map { accuracy =>
|
||||
impl.accuracyScore(accuracy) + impl.reporterScore(candidate.reporter)
|
||||
impl.accuracyScore(accuracy) +
|
||||
impl.reporterScore(candidate.reporter) +
|
||||
impl.textScore(candidate.reason, candidate.text)
|
||||
} map { score =>
|
||||
candidate scored Report.Score(score atLeast 0 atMost 100)
|
||||
}
|
||||
|
||||
private[report] def reset(coll: Coll)(implicit handler: BSONDocumentHandler[Report]): Fu[Int] = {
|
||||
import play.api.libs.iteratee._
|
||||
import reactivemongo.play.iteratees.cursorProducer
|
||||
coll.find($doc("open" -> true)).cursor[Report]().enumerator() |>>>
|
||||
Iteratee.foldM[Report, Int](0) {
|
||||
case (nb, report) => for {
|
||||
newAtoms <- report.atoms.map { atom =>
|
||||
candidateOf(report, atom) map {
|
||||
_.fold(atom) { scored =>
|
||||
atom.copy(score = scored.score)
|
||||
}
|
||||
}
|
||||
}.toList.sequenceFu.map(_.toNel | report.atoms)
|
||||
newReport = report.copy(atoms = newAtoms).recomputeScore
|
||||
_ <- coll.update($id(report.id), newReport)
|
||||
} yield {
|
||||
if (nb % 100 == 0) logger.info(s"Score reset $nb")
|
||||
nb + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def candidateOf(report: Report, atom: Report.Atom): Fu[Option[Report.Candidate.Scored]] = for {
|
||||
reporter <- UserRepo byId atom.by.value map2 Reporter.apply
|
||||
suspect <- UserRepo named report.suspect.value map2 Suspect.apply
|
||||
score <- (reporter |@| suspect).tupled ?? {
|
||||
case (r, s) => apply(Report.Candidate(
|
||||
reporter = r,
|
||||
suspect = s,
|
||||
reason = report.reason,
|
||||
text = atom.text
|
||||
)) map some
|
||||
}
|
||||
} yield score
|
||||
|
||||
private object impl {
|
||||
|
||||
def accuracyScore(a: Option[Accuracy]): Double = a ?? { accuracy =>
|
||||
|
@ -27,5 +67,12 @@ private final class ReportScore(
|
|||
|
||||
def flagScore(user: User) =
|
||||
(user.lameOrTroll) ?? -30d
|
||||
|
||||
private val gamePattern = """lichess.org/(\w{8,12})\b""".r.pattern
|
||||
|
||||
def textScore(reason: Reason, text: String) = {
|
||||
(reason == Reason.Cheat || reason == Reason.Boost) &&
|
||||
gamePattern.matcher(text).find
|
||||
} ?? 20
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import lila.user.User
|
|||
case class Mod(user: User) extends AnyVal
|
||||
|
||||
case class Suspect(user: User) extends AnyVal {
|
||||
|
||||
def set(f: User => User) = copy(user = f(user))
|
||||
}
|
||||
case class SuspectId(value: User.ID) extends AnyVal
|
||||
|
||||
case class Victim(user: User) extends AnyVal
|
||||
|
||||
|
|
|
@ -117,3 +117,25 @@
|
|||
#report_list tr.new td:first-child {
|
||||
border-left: 3px solid #3893E8;
|
||||
}
|
||||
.score {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.score {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
padding: 0.3em 0.5em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
.score.green {
|
||||
/* actually blue */
|
||||
background-color: rgba(32, 119, 192, 0.4);
|
||||
}
|
||||
.score.yellow {
|
||||
background-color: rgba(221, 207, 63, 0.4);
|
||||
}
|
||||
.score.orange {
|
||||
background-color: rgba(231, 155, 100, 0.4);
|
||||
}
|
||||
.score.red {
|
||||
background-color: rgba(231, 59, 56, 0.4);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue