diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NewlyPicturedPerAuthorBonus.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NewlyPicturedPerAuthorBonus.scala new file mode 100644 index 00000000..5e7d6780 --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NewlyPicturedPerAuthorBonus.scala @@ -0,0 +1,52 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.wlx.stat.ContestStat + +case class NewlyPicturedPerAuthorBonus( + stat: ContestStat, + newlyPicturedRate: Double, + newlyPicturedPerAuthorRate: Double + ) extends Rater { + + val oldMonumentIdsByAuthor: Map[String, Set[String]] = oldImages + .groupBy(_.author.getOrElse("")) + .mapValues(_.flatMap(_.monumentId).toSet) + .toMap + + override def rate(monumentId: String, author: String): Double = { + val rate = monumentId match { + case id if disqualify(id, author) => 0 + case id if !oldMonumentIds.contains(id) => + newlyPicturedRate - 1 + case id + if !oldMonumentIdsByAuthor + .getOrElse(author, Set.empty) + .contains(id) => + newlyPicturedPerAuthorRate - 1 + case _ => + 0 + } + rate + } + + override def explain(monumentId: String, author: String): String = { + monumentId match { + case id if disqualify(id, author) => + "Disqualified for reuploading similar images = 0" + case id if !oldMonumentIds.contains(id) => + s"Newly pictured bonus = ${newlyPicturedRate - 1}" + case id + if !oldMonumentIdsByAuthor + .getOrElse(author, Set.empty) + .contains(id) => + s"Newly pictured per author bonus = ${newlyPicturedPerAuthorRate - 1}" + case _ => + s"Not newly pictured = 0" + } + } + + override def disqualify(monumentId: String, author: String): Boolean = { + Set("Петро Халява", "SnizhokAM").contains(author) && + oldMonumentIdsByAuthor.getOrElse(author, Set.empty).contains(monumentId) + } +} \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonus.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonus.scala new file mode 100644 index 00000000..4ecb9e4a --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonus.scala @@ -0,0 +1,66 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.MwBot +import org.scalawiki.wlx.stat.ContestStat +import org.scalawiki.wlx.stat.reports.RateInputDistribution + +case class NumberOfAuthorsBonus(stat: ContestStat, rateRanges: RateRanges) + extends Rater { + val authorsByMonument: Map[String, Set[String]] = oldImages + .groupBy(_.monumentId.getOrElse("")) + .mapValues { images => + images.map(_.author.getOrElse("")).toSet + } + .toMap + + val authorsNumberByMonument: Map[String, Int] = + authorsByMonument.mapValues(_.size).toMap + + val distribution: Map[Int, Int] = + authorsNumberByMonument.values.groupBy(identity).mapValues(_.size).toMap + + if (stat.config.exists(_.rateInputDistribution)) { + new RateInputDistribution( + stat, + distribution, + "Number of authors distribution", + Seq("Number of authors", "Number of monuments") + ).updateWiki(MwBot.fromHost(MwBot.commons)) + } + + override def rate(monumentId: String, author: String): Double = { + if (disqualify(monumentId, author)) 0 + else if ( + rateRanges.sameAuthorZeroBonus && authorsByMonument + .getOrElse(monumentId, Set.empty) + .contains(author) + ) { + 0 + } else { + rateRanges.rate(authorsNumberByMonument.getOrElse(monumentId, 0)) + } + } + + override def explain(monumentId: String, author: String): String = { + val number = rate(monumentId, author) + if (disqualify(monumentId, author)) { + "Disqualified for reuploading similar images = 0" + } else if ( + rateRanges.sameAuthorZeroBonus && authorsByMonument + .getOrElse(monumentId, Set.empty) + .contains(author) + ) { + s"Pictured by same author before = $number" + } else { + val picturedBy = authorsNumberByMonument.getOrElse(monumentId, 0) + val (rate, start, end) = rateRanges.rateWithRange(picturedBy) + s"Pictured before by $picturedBy ($start-${end.getOrElse("")}) authors = $rate" + } + } + + override def disqualify(monumentId: String, author: String): Boolean = { + false + // Set("Петро Халява", "SnizhokAM").contains(author) && + // authorsByMonument.getOrElse(monumentId, Set.empty).contains(author) + } +} \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfImagesInPlaceBonus.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfImagesInPlaceBonus.scala new file mode 100644 index 00000000..6e9fa91e --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfImagesInPlaceBonus.scala @@ -0,0 +1,85 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.MwBot +import org.scalawiki.wlx.ImageDB +import org.scalawiki.wlx.stat.ContestStat +import org.scalawiki.wlx.stat.reports.RateInputDistribution + +import scala.collection.mutable + +case class NumberOfImagesInPlaceBonus( + stat: ContestStat, + rateRanges: RateRanges + ) extends Rater { + + val oldImagesDb = new ImageDB(stat.contest, oldImages, stat.monumentDb) + val perPlaceStat = PerPlaceStat(oldImagesDb) + val unknownPlaceMonumentsByAuthor = mutable.Map[String, Set[String]]() + val authorsByMonument: Map[String, Set[String]] = oldImages + .groupBy(_.monumentId.getOrElse("")) + .mapValues { images => + images.map(_.author.getOrElse("")).toSet + } + .toMap + + val distribution: Map[Int, Int] = perPlaceStat.distribution + + if (stat.config.exists(_.rateInputDistribution)) { + new RateInputDistribution( + stat, + distribution, + "Number of images in place distribution", + Seq("Number of images in place", "Number of monuments") + ).updateWiki(MwBot.fromHost(MwBot.commons)) + } + + override def rate(monumentId: String, author: String): Double = { + if ( + rateRanges.sameAuthorZeroBonus && authorsByMonument + .getOrElse(monumentId, Set.empty) + .contains(author) + ) { + 0.0 + } else { + perPlaceStat.placeByMonument + .get(monumentId) + .map { place => + rateRanges.rate(perPlaceStat.imagesPerPlace.getOrElse(place, 0)) + } + .getOrElse { + val monumentIds = + unknownPlaceMonumentsByAuthor.getOrElse(author, Set.empty[String]) + if (!monumentIds.contains(monumentId)) { + unknownPlaceMonumentsByAuthor(author) = monumentIds + monumentId + } + 0.0 + } + } + } + + override def explain(monumentId: String, author: String): String = { + if ( + rateRanges.sameAuthorZeroBonus && authorsByMonument + .getOrElse(monumentId, Set.empty) + .contains(author) + ) { + s"Pictured by same author before = 0" + } else { + perPlaceStat.placeByMonument + .get(monumentId) + .map { place => + val perPlace = perPlaceStat.imagesPerPlace.getOrElse(place, 0) + val (rate, start, end) = rateRanges.rateWithRange(perPlace) + s"$perPlace ($start-${end.getOrElse("")}) images were of $place, bonus = $rate" + } + .getOrElse { + val monumentIds = + unknownPlaceMonumentsByAuthor.getOrElse(author, Set.empty[String]) + if (!monumentIds.contains(monumentId)) { + unknownPlaceMonumentsByAuthor(author) = monumentIds + monumentId + } + s"unknown place of monument $monumentId, bonus = 0" + } + } + } +} \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfMonuments.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfMonuments.scala new file mode 100644 index 00000000..43729818 --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/NumberOfMonuments.scala @@ -0,0 +1,37 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.wlx.stat.ContestStat + +case class NumberOfMonuments(stat: ContestStat, baseRate: Double) + extends Rater { + val monumentIds = stat.monumentDb.map(_.ids).getOrElse(Set.empty) + + override def rate(monumentId: String, author: String): Double = { + if (monumentIds.contains(monumentId)) baseRate else 0 + } + + override def explain(monumentId: String, author: String): String = { + if (monumentIds.contains(monumentId)) s"Base rate = $baseRate" + else "Not a known monument = 0" + } + + override def withRating: Boolean = false +} + +case class NewlyPicturedBonus(stat: ContestStat, newlyPicturedRate: Double) + extends Rater { + + override def rate(monumentId: String, author: String): Double = { + if (!oldMonumentIds.contains(monumentId)) + newlyPicturedRate - 1 + else + 0 + } + + override def explain(monumentId: String, author: String): String = { + if (!oldMonumentIds.contains(monumentId)) + s"Newly pictured rate bonus = ${newlyPicturedRate - 1}" + else + "Not newly pictured = 0" + } +} \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/PerPlaceStat.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/PerPlaceStat.scala new file mode 100644 index 00000000..28e24b47 --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/PerPlaceStat.scala @@ -0,0 +1,120 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.wlx.ImageDB + +case class PerPlaceStat( + imagesPerPlace: Map[String, Int], + placeByMonument: Map[String, String] + ) { + val distribution = placeByMonument.values + .map(place => imagesPerPlace.getOrElse(place, 0)) + .groupBy(identity) + .mapValues(_.size) + .toMap +} + +object PerPlaceStat { + + val fallbackMapInverse = Map( + "5121085201" -> Set("51-210-0002", "51-210-0011", "51-210-0080"), // Усатове + "7125785201" -> Set("71-257-0008"), // Матусів + "5322610199" -> Set( + "53-226-0068", + "53-226-0069", + "53-226-0070", + "53-226-0071", + "53-226-0072", + "53-226-0073", + "53-226-9001" + ), // урочище Шумейкове + "2611040399" -> Set("26-110-0006"), // Говерла + "0120481999" -> Set( + "01-204-0065" + ), // Голубинська сільська рада (Бахчисарайський район) + "3222099719" -> Set("32-220-0071"), // центр колишнього села Красне + "3222000739" -> Set("32-220-0073"), // колишнє село Купувате + "3222000769" -> Set("32-220-0076"), // колишнє село Ладижичі + "3222000799" -> Set("32-220-0079"), // колишнє село Машеве + "3222000839" -> Set("32-220-0083", "32-220-0084"), // колишнє село Опачичі + "3222000859" -> Set("32-220-0085"), // колишнє село Паришів + "3222000889" -> Set("32-220-0088"), // колишнє село Розсоха + "3222000899" -> Set("32-220-0089"), // колишнє село Роз'їждже + "3222000919" -> Set("32-220-0091"), // колишнє село Старі Шепеличі + "3222000929" -> Set("32-220-0092"), // колишнє село Старосілля + "3222000939" -> Set("32-220-0093"), // колишнє село Стечанка + "3222000949" -> Set("32-220-0094"), // колишнє село Стечанка + "3222000969" -> Set("32-220-0096"), // центр колишнього села Товстий Ліс + "3222000989" -> Set("32-220-0098"), // колишнє село Чапаєвка + "3222001009" -> Set("32-220-0100"), // центр колишнього села Чистогалівка + "0111590019" -> Set( + "01-115-0007", + "01-115-0009", + "01-115-0010", + "01-115-9001" + ), // Армянська міськрада (Перекопський вал), за 5 км від міста + "0122302009" -> Set( + "01-223-0041", + "01-223-0103", + "01-223-0104", + "01-223-0200" + ), // Армянська та Красноперекопська міськради (на межі двох) + "5322690019" -> Set( + "53-226-0068", + "53-226-0069", + "53-226-0070", + "53-226-0071", + "53-226-0072", + "53-226-0073", + "53-226-9001" + ), // урочище Шумейкове (15 км від лохвиці) + "1423055701" -> Set("14-133-0017"), // uk:Торське (Лиманська міська громада) + "1420989202" -> Set( + "14-209-0065" + ), // uk:Григорівка (Сіверська міська громада) + "1422783201" -> Set( + "14-227-0018" + ), // uk:Михайлівка (Покровський район, Михайлівська сільська рада) + "3220490019" -> Set( + "32-204-9001" + ), // між селами Городище-Пустоварівське Володарського і Щербаки Білоцерківського районів + "5322610600" -> Set("53-226-0062"), // uk:Заводське + "5324000829" -> Set("53-240-0082"), // ур. Ступки + "4624801019" -> Set( + "46-248-0101" + ), // Городиловичі (село більше не існує) ) + "5322681102" -> Set("53-226-0041"), // Забодаква — колишнє село + "5322688402" -> Set("53-226-0110"), // + "5322681910" -> Set("53-226-0117"), // Перевалівка + "0111948301" -> Set("01-119-0349"), + "0111949300" -> Set("01-119-0370"), + "0111949702" -> Set("01-119-9002"), + "5121085201" -> Set( + "51-210-0074", + "51-210-0075", + "51-210-0082", + "51-210-0002", + "51-210-0011", + "51-210-0080" + ), + "6524710101" -> Set("65-247-1251"), + "4810800001" -> Set("48-108-0004"), + "3222055103" -> Set("32-220-0060") + ) + val fallbackMap = + for ((koatuu, ids) <- fallbackMapInverse; id <- ids) yield id -> koatuu + + def apply(imageDB: ImageDB): PerPlaceStat = { + val country = imageDB.contest.country + val monumentDb = imageDB.monumentDb.get + + val imagesPerPlace = (for ( + id <- imageDB.ids.toSeq; + place <- monumentDb.placeByMonumentId.get(id) + ) + yield (place -> imageDB.byId(id).size)) + .groupBy(_._1) + .mapValues(_.map(_._2).sum) + + PerPlaceStat(imagesPerPlace.toMap, monumentDb.placeByMonumentId) + } +} \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateConfig.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateConfig.scala new file mode 100644 index 00000000..b33d4bf6 --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateConfig.scala @@ -0,0 +1,24 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.wlx.stat.StatParams + +case class RateConfig( + newObjectRating: Option[Double] = None, + newAuthorObjectRating: Option[Double] = None, + numberOfAuthorsBonus: Boolean = false, + numberOfImagesBonus: Boolean = false, + baseRate: Double = 1 +) + +object RateConfig { + + def apply(conf: StatParams): RateConfig = { + apply( + conf.newObjectRating.toOption, + conf.newAuthorObjectRating.toOption, + conf.numberOfAuthorsBonus.getOrElse(false), + conf.numberOfImagesBonus.getOrElse(false), + conf.baseRate.getOrElse(1) + ) + } +} diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateSum.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateSum.scala new file mode 100644 index 00000000..ac4d45d3 --- /dev/null +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/RateSum.scala @@ -0,0 +1,22 @@ +package org.scalawiki.wlx.stat.rating + +import org.scalawiki.wlx.stat.ContestStat + +case class RateSum(stat: ContestStat, raters: Seq[Rater]) extends Rater { + override def rate(monumentId: String, author: String): Double = { + if (!raters.exists(_.disqualify(monumentId, author))) { + raters.map(_.rate(monumentId, author)).sum + } else 0 + } + + override def explain(monumentId: String, author: String): String = { + val disqualifiedRater = raters.find(_.disqualify(monumentId, author)) + disqualifiedRater.fold( + s"Rating = ${raters.map(_.rate(monumentId, author)).sum}, is a sum of: " + raters + .map(_.explain(monumentId, author)) + .mkString(", ") + ) { rater => + rater.explain(monumentId, author) + } + } +} diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/Rater.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/Rater.scala index c162d22c..19ae51a0 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/Rater.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/rating/Rater.scala @@ -1,34 +1,11 @@ package org.scalawiki.wlx.stat.rating import com.typesafe.config.Config -import org.scalawiki.MwBot import org.scalawiki.wlx.ImageDB -import org.scalawiki.wlx.stat.reports.RateInputDistribution -import org.scalawiki.wlx.stat.{ContestStat, StatParams} +import org.scalawiki.wlx.stat.ContestStat -import scala.collection.mutable import scala.util.Try -case class RateConfig( - newObjectRating: Option[Double] = None, - newAuthorObjectRating: Option[Double] = None, - numberOfAuthorsBonus: Boolean = false, - numberOfImagesBonus: Boolean = false, - baseRate: Double = 1 -) - -object RateConfig { - - def apply(conf: StatParams): RateConfig = { - apply( - conf.newObjectRating.toOption, - conf.newAuthorObjectRating.toOption, - conf.numberOfAuthorsBonus.getOrElse(false), - conf.numberOfImagesBonus.getOrElse(false), - conf.baseRate.getOrElse(1) - ) - } -} trait Rater { @@ -123,361 +100,4 @@ object Rater { } } -} - -case class NumberOfMonuments(stat: ContestStat, baseRate: Double) - extends Rater { - val monumentIds = stat.monumentDb.map(_.ids).getOrElse(Set.empty) - - override def rate(monumentId: String, author: String): Double = { - if (monumentIds.contains(monumentId)) baseRate else 0 - } - - override def explain(monumentId: String, author: String): String = { - if (monumentIds.contains(monumentId)) s"Base rate = $baseRate" - else "Not a known monument = 0" - } - - override def withRating: Boolean = false -} - -case class NewlyPicturedBonus(stat: ContestStat, newlyPicturedRate: Double) - extends Rater { - - override def rate(monumentId: String, author: String): Double = { - if (!oldMonumentIds.contains(monumentId)) - newlyPicturedRate - 1 - else - 0 - } - - override def explain(monumentId: String, author: String): String = { - if (!oldMonumentIds.contains(monumentId)) - s"Newly pictured rate bonus = ${newlyPicturedRate - 1}" - else - "Not newly pictured = 0" - } -} - -case class NewlyPicturedPerAuthorBonus( - stat: ContestStat, - newlyPicturedRate: Double, - newlyPicturedPerAuthorRate: Double -) extends Rater { - - val oldMonumentIdsByAuthor: Map[String, Set[String]] = oldImages - .groupBy(_.author.getOrElse("")) - .mapValues(_.flatMap(_.monumentId).toSet) - .toMap - - override def rate(monumentId: String, author: String): Double = { - val rate = monumentId match { - case id if disqualify(id, author) => 0 - case id if !oldMonumentIds.contains(id) => - newlyPicturedRate - 1 - case id - if !oldMonumentIdsByAuthor - .getOrElse(author, Set.empty) - .contains(id) => - newlyPicturedPerAuthorRate - 1 - case _ => - 0 - } - rate - } - - override def explain(monumentId: String, author: String): String = { - monumentId match { - case id if disqualify(id, author) => - "Disqualified for reuploading similar images = 0" - case id if !oldMonumentIds.contains(id) => - s"Newly pictured bonus = ${newlyPicturedRate - 1}" - case id - if !oldMonumentIdsByAuthor - .getOrElse(author, Set.empty) - .contains(id) => - s"Newly pictured per author bonus = ${newlyPicturedPerAuthorRate - 1}" - case _ => - s"Not newly pictured = 0" - } - } - - override def disqualify(monumentId: String, author: String): Boolean = { - Set("Петро Халява", "SnizhokAM").contains(author) && - oldMonumentIdsByAuthor.getOrElse(author, Set.empty).contains(monumentId) - } -} - -case class NumberOfAuthorsBonus(stat: ContestStat, rateRanges: RateRanges) - extends Rater { - val authorsByMonument: Map[String, Set[String]] = oldImages - .groupBy(_.monumentId.getOrElse("")) - .mapValues { images => - images.map(_.author.getOrElse("")).toSet - } - .toMap - - val authorsNumberByMonument: Map[String, Int] = - authorsByMonument.mapValues(_.size).toMap - - val distribution: Map[Int, Int] = - authorsNumberByMonument.values.groupBy(identity).mapValues(_.size).toMap - - if (stat.config.exists(_.rateInputDistribution)) { - new RateInputDistribution( - stat, - distribution, - "Number of authors distribution", - Seq("Number of authors", "Number of monuments") - ).updateWiki(MwBot.fromHost(MwBot.commons)) - } - - override def rate(monumentId: String, author: String): Double = { - if (disqualify(monumentId, author)) 0 - else if ( - rateRanges.sameAuthorZeroBonus && authorsByMonument - .getOrElse(monumentId, Set.empty) - .contains(author) - ) { - 0 - } else { - rateRanges.rate(authorsNumberByMonument.getOrElse(monumentId, 0)) - } - } - - override def explain(monumentId: String, author: String): String = { - val number = rate(monumentId, author) - if (disqualify(monumentId, author)) { - "Disqualified for reuploading similar images = 0" - } else if ( - rateRanges.sameAuthorZeroBonus && authorsByMonument - .getOrElse(monumentId, Set.empty) - .contains(author) - ) { - s"Pictured by same author before = $number" - } else { - val picturedBy = authorsNumberByMonument.getOrElse(monumentId, 0) - val (rate, start, end) = rateRanges.rateWithRange(picturedBy) - s"Pictured before by $picturedBy ($start-${end.getOrElse("")}) authors = $rate" - } - } - - override def disqualify(monumentId: String, author: String): Boolean = { - false -// Set("Петро Халява", "SnizhokAM").contains(author) && -// authorsByMonument.getOrElse(monumentId, Set.empty).contains(author) - } -} - -case class PerPlaceStat( - imagesPerPlace: Map[String, Int], - placeByMonument: Map[String, String] -) { - val distribution = placeByMonument.values - .map(place => imagesPerPlace.getOrElse(place, 0)) - .groupBy(identity) - .mapValues(_.size) - .toMap -} - -object PerPlaceStat { - - val fallbackMapInverse = Map( - "5121085201" -> Set("51-210-0002", "51-210-0011", "51-210-0080"), // Усатове - "7125785201" -> Set("71-257-0008"), // Матусів - "5322610199" -> Set( - "53-226-0068", - "53-226-0069", - "53-226-0070", - "53-226-0071", - "53-226-0072", - "53-226-0073", - "53-226-9001" - ), // урочище Шумейкове - "2611040399" -> Set("26-110-0006"), // Говерла - "0120481999" -> Set( - "01-204-0065" - ), // Голубинська сільська рада (Бахчисарайський район) - "3222099719" -> Set("32-220-0071"), // центр колишнього села Красне - "3222000739" -> Set("32-220-0073"), // колишнє село Купувате - "3222000769" -> Set("32-220-0076"), // колишнє село Ладижичі - "3222000799" -> Set("32-220-0079"), // колишнє село Машеве - "3222000839" -> Set("32-220-0083", "32-220-0084"), // колишнє село Опачичі - "3222000859" -> Set("32-220-0085"), // колишнє село Паришів - "3222000889" -> Set("32-220-0088"), // колишнє село Розсоха - "3222000899" -> Set("32-220-0089"), // колишнє село Роз'їждже - "3222000919" -> Set("32-220-0091"), // колишнє село Старі Шепеличі - "3222000929" -> Set("32-220-0092"), // колишнє село Старосілля - "3222000939" -> Set("32-220-0093"), // колишнє село Стечанка - "3222000949" -> Set("32-220-0094"), // колишнє село Стечанка - "3222000969" -> Set("32-220-0096"), // центр колишнього села Товстий Ліс - "3222000989" -> Set("32-220-0098"), // колишнє село Чапаєвка - "3222001009" -> Set("32-220-0100"), // центр колишнього села Чистогалівка - "0111590019" -> Set( - "01-115-0007", - "01-115-0009", - "01-115-0010", - "01-115-9001" - ), // Армянська міськрада (Перекопський вал), за 5 км від міста - "0122302009" -> Set( - "01-223-0041", - "01-223-0103", - "01-223-0104", - "01-223-0200" - ), // Армянська та Красноперекопська міськради (на межі двох) - "5322690019" -> Set( - "53-226-0068", - "53-226-0069", - "53-226-0070", - "53-226-0071", - "53-226-0072", - "53-226-0073", - "53-226-9001" - ), // урочище Шумейкове (15 км від лохвиці) - "1423055701" -> Set("14-133-0017"), // uk:Торське (Лиманська міська громада) - "1420989202" -> Set( - "14-209-0065" - ), // uk:Григорівка (Сіверська міська громада) - "1422783201" -> Set( - "14-227-0018" - ), // uk:Михайлівка (Покровський район, Михайлівська сільська рада) - "3220490019" -> Set( - "32-204-9001" - ), // між селами Городище-Пустоварівське Володарського і Щербаки Білоцерківського районів - "5322610600" -> Set("53-226-0062"), // uk:Заводське - "5324000829" -> Set("53-240-0082"), // ур. Ступки - "4624801019" -> Set( - "46-248-0101" - ), // Городиловичі (село більше не існує) ) - "5322681102" -> Set("53-226-0041"), // Забодаква — колишнє село - "5322688402" -> Set("53-226-0110"), // - "5322681910" -> Set("53-226-0117"), // Перевалівка - "0111948301" -> Set("01-119-0349"), - "0111949300" -> Set("01-119-0370"), - "0111949702" -> Set("01-119-9002"), - "5121085201" -> Set( - "51-210-0074", - "51-210-0075", - "51-210-0082", - "51-210-0002", - "51-210-0011", - "51-210-0080" - ), - "6524710101" -> Set("65-247-1251"), - "4810800001" -> Set("48-108-0004"), - "3222055103" -> Set("32-220-0060") - ) - val fallbackMap = - for ((koatuu, ids) <- fallbackMapInverse; id <- ids) yield id -> koatuu - - def apply(imageDB: ImageDB): PerPlaceStat = { - val country = imageDB.contest.country - val monumentDb = imageDB.monumentDb.get - - val imagesPerPlace = (for ( - id <- imageDB.ids.toSeq; - place <- monumentDb.placeByMonumentId.get(id) - ) - yield (place -> imageDB.byId(id).size)) - .groupBy(_._1) - .mapValues(_.map(_._2).sum) - - PerPlaceStat(imagesPerPlace.toMap, monumentDb.placeByMonumentId) - } -} - -case class NumberOfImagesInPlaceBonus( - stat: ContestStat, - rateRanges: RateRanges -) extends Rater { - - val oldImagesDb = new ImageDB(stat.contest, oldImages, stat.monumentDb) - val perPlaceStat = PerPlaceStat(oldImagesDb) - val unknownPlaceMonumentsByAuthor = mutable.Map[String, Set[String]]() - val authorsByMonument: Map[String, Set[String]] = oldImages - .groupBy(_.monumentId.getOrElse("")) - .mapValues { images => - images.map(_.author.getOrElse("")).toSet - } - .toMap - - val distribution: Map[Int, Int] = perPlaceStat.distribution - - if (stat.config.exists(_.rateInputDistribution)) { - new RateInputDistribution( - stat, - distribution, - "Number of images in place distribution", - Seq("Number of images in place", "Number of monuments") - ).updateWiki(MwBot.fromHost(MwBot.commons)) - } - - override def rate(monumentId: String, author: String): Double = { - if ( - rateRanges.sameAuthorZeroBonus && authorsByMonument - .getOrElse(monumentId, Set.empty) - .contains(author) - ) { - 0.0 - } else { - perPlaceStat.placeByMonument - .get(monumentId) - .map { place => - rateRanges.rate(perPlaceStat.imagesPerPlace.getOrElse(place, 0)) - } - .getOrElse { - val monumentIds = - unknownPlaceMonumentsByAuthor.getOrElse(author, Set.empty[String]) - if (!monumentIds.contains(monumentId)) { - unknownPlaceMonumentsByAuthor(author) = monumentIds + monumentId - } - 0.0 - } - } - } - - override def explain(monumentId: String, author: String): String = { - if ( - rateRanges.sameAuthorZeroBonus && authorsByMonument - .getOrElse(monumentId, Set.empty) - .contains(author) - ) { - s"Pictured by same author before = 0" - } else { - perPlaceStat.placeByMonument - .get(monumentId) - .map { place => - val perPlace = perPlaceStat.imagesPerPlace.getOrElse(place, 0) - val (rate, start, end) = rateRanges.rateWithRange(perPlace) - s"$perPlace ($start-${end.getOrElse("")}) images were of $place, bonus = $rate" - } - .getOrElse { - val monumentIds = - unknownPlaceMonumentsByAuthor.getOrElse(author, Set.empty[String]) - if (!monumentIds.contains(monumentId)) { - unknownPlaceMonumentsByAuthor(author) = monumentIds + monumentId - } - s"unknown place of monument $monumentId, bonus = 0" - } - } - } -} - -case class RateSum(stat: ContestStat, raters: Seq[Rater]) extends Rater { - override def rate(monumentId: String, author: String): Double = { - if (!raters.exists(_.disqualify(monumentId, author))) { - raters.map(_.rate(monumentId, author)).sum - } else 0 - } - - override def explain(monumentId: String, author: String): String = { - val disqualifiedRater = raters.find(_.disqualify(monumentId, author)) - disqualifiedRater.fold( - s"Rating = ${raters.map(_.rate(monumentId, author)).sum}, is a sum of: " + raters - .map(_.explain(monumentId, author)) - .mkString(", ") - ) { rater => - rater.explain(monumentId, author) - } - } -} +} \ No newline at end of file diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/NumberOfAuthorsBonusSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonusSpec.scala similarity index 96% rename from scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/NumberOfAuthorsBonusSpec.scala rename to scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonusSpec.scala index 231cb3f2..a4970943 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/NumberOfAuthorsBonusSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/NumberOfAuthorsBonusSpec.scala @@ -1,10 +1,10 @@ -package org.scalawiki.wlx.stat +package org.scalawiki.wlx.stat.rating import com.typesafe.config.ConfigFactory import org.scalawiki.dto.Image -import org.scalawiki.wlx.{ImageDB, MonumentDB} import org.scalawiki.wlx.dto.{Contest, ContestType, Country, Monument} -import org.scalawiki.wlx.stat.rating.NumberOfAuthorsBonus +import org.scalawiki.wlx.stat.ContestStat +import org.scalawiki.wlx.{ImageDB, MonumentDB} import org.specs2.mutable.Specification class NumberOfAuthorsBonusSpec extends Specification { diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/RateRangesSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RateRangesSpec.scala similarity index 89% rename from scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/RateRangesSpec.scala rename to scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RateRangesSpec.scala index dbe4179a..ffc954ce 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/RateRangesSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RateRangesSpec.scala @@ -1,8 +1,6 @@ -package org.scalawiki.wlx.stat +package org.scalawiki.wlx.stat.rating import com.typesafe.config.ConfigFactory -import org.scalawiki.wlx.dto.{Contest, ContestType, Country} -import org.scalawiki.wlx.{ImageDB, MonumentDB} import org.specs2.mutable.Specification class RateRangesSpec extends Specification { diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RaterSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RaterSpec.scala index f67edb66..a2413497 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RaterSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/rating/RaterSpec.scala @@ -4,6 +4,7 @@ import com.typesafe.config.ConfigFactory import org.scalawiki.wlx.dto.ContestType.{WLE, WLM} import org.scalawiki.wlx.dto.{Contest, ContestType, Country} import org.scalawiki.wlx.stat.ContestStat +import org.scalawiki.wlx.stat.rating.RaterSpec.loadRater import org.scalawiki.wlx.{ImageDB, MonumentDB} import org.specs2.mutable.Specification