From 56080fc8a6eeebc2795ca0ea22df4af490001802 Mon Sep 17 00:00:00 2001 From: Ilya Date: Sun, 17 Dec 2023 12:07:48 +0200 Subject: [PATCH] some updates (#468) * some formatting * 2023 wle conf * authorsContributedPerRegion * update PoiXwpfV * ttn reader * more warnings * lastMonth stat * lastMonth stat * typo * evict cache on parse exception * 2023 rating * update scalafmt * java-version: 11 for PR * java-version: 11 for appveyor * drop scala 2.12 * remove number * fix tests --- .github/workflows/ci.yml | 2 +- .scalafmt.conf | 2 + appveyor.yml | 1 + build.sbt | 5 +- project/Dependencies.scala | 24 ++- .../scala/org/scalawiki/bots/MessageBot.scala | 2 +- .../bots/museum/ImageListParser.scala | 6 +- .../scala/org/scalawiki/bots/np/TTN.scala | 43 +++++ .../org/scalawiki/bots/np/TTNReader.scala | 123 +++++++++++++ .../org/scalawiki/bots/np/WlmContacts.scala | 41 +++++ .../scalawiki/bots/np/WlmContactsSpec.scala | 27 +++ .../scala/org/scalawiki/query/DslQuery.scala | 20 ++- .../scala/org/scalawiki/query/PageQuery.scala | 17 +- .../scalawiki/query/PageQueryImplDsl.scala | 167 +++++++++++------- .../org/scalawiki/query/SinglePageQuery.scala | 39 ++-- scalawiki-wlx/src/main/resources/wle_ua.conf | 10 +- scalawiki-wlx/src/main/resources/wlm_ua.conf | 94 +++++++--- .../org/scalawiki/wlx/stat/StatParams.scala | 139 ++++++++++----- .../org/scalawiki/wlx/stat/Statistics.scala | 10 +- .../wlx/stat/reports/AuthorsStat.scala | 145 +++++++++++---- .../wlx/stat/reports/MonumentDbStat.scala | 17 +- .../scalawiki/wlx/stat/reports/Output.scala | 1 + .../wlx/stat/reports/ReporterRegistry.scala | 4 +- .../wlx/stat/AuthorsContributedSpec.scala | 144 ++++++++++++--- .../wlx/stat/SpecialNominationsSpec.scala | 133 ++++++++------ .../scalawiki/wlx/stat/StatParamsSpec.scala | 2 +- 26 files changed, 917 insertions(+), 301 deletions(-) create mode 100644 scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTN.scala create mode 100644 scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTNReader.scala create mode 100644 scalawiki-bots/src/main/scala/org/scalawiki/bots/np/WlmContacts.scala create mode 100644 scalawiki-bots/src/test/scala/org/scalawiki/bots/np/WlmContactsSpec.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba0dc139..ceca0272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: temurin - java-version: 8 + java-version: 11 cache: sbt - name: Build and Test run: sbt -v +test \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index e69de29b..259f078c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +version = 3.7.17 +runner.dialect = scala213 \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 032be530..f0acba61 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ install: ) [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt") } + - cmd: SET JAVA_HOME=C:\Program Files\Java\jdk11 - cmd: SET PATH=C:\sbt\sbt\bin;%JAVA_HOME%\bin;%PATH% - cmd: SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g build_script: diff --git a/build.sbt b/build.sbt index ce36f1ff..1f21ee55 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ lazy val isScala213 = settingKey[Boolean]("Is the scala version 2.13.") lazy val commonSettings = Seq( organization := "org.scalawiki", version := "0.7.0-SNAPSHOT", - crossScalaVersions := Seq(Scala212V, Scala213V), + crossScalaVersions := Seq(Scala213V), scalaVersion := crossScalaVersions.value.last, isScala213 := scalaVersion.value.startsWith("2.13."), Global / excludeLintKeys += isScala213, @@ -83,8 +83,11 @@ lazy val bots = Project("scalawiki-bots", file("scalawiki-bots")) "com.github.pathikrit" %% "better-files" % BetterFilesV, "org.rogach" %% "scallop" % ScallopV, "org.xwiki.commons" % "xwiki-commons-blame-api" % BlameApiV, + Library.Commons.io, Library.Poi.scratchpad, Library.Poi.ooxml, + Library.Poi.ooxmlFull, + Library.Poi.poi, Library.Poi.converter, Library.Play.twirlApi(isScala213.value), "com.github.tototoshi" %% "scala-csv" % ScalaCsvV diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1ae8c46e..da8c5f6c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -18,7 +18,6 @@ object Dependencies { val ReactiveStreamsV = "1.0.4" val RetryV = "0.3.6" val Scala213V = "2.13.12" - val Scala212V = "2.12.18" val ScalaChartV = "0.8.0" val ScalaCheckV = "1.17.0" val ScalaCsvV = "1.3.10" @@ -49,13 +48,17 @@ object Dependencies { Library.Commons.lang, "com.typesafe" % "config" % TypesafeConfigV, Library.Poi.ooxml, + Library.Poi.poi, + Library.Poi.ooxmlFull, Library.Jackson.core, Library.Jackson.annotations, Library.Jackson.databind, "joda-time" % "joda-time" % JodaTimeV, "org.slf4j" % "slf4j-api" % Slf4jV, "ch.qos.logback" % "logback-classic" % LogbackClassicV, - "javax.xml.bind" % "jaxb-api" % "2.3.1" + "javax.xml.bind" % "jaxb-api" % "2.3.1", + "org.apache.logging" % "log4j:log4j-core" % "2.18.1", + "org.apache.logging" % "log4j:log4j-api" % "2.18.1" ) object Library { @@ -81,25 +84,30 @@ object Dependencies { def TwirlV(isScala213: Boolean) = "1.5.2" - def json(isScala213: Boolean) = "com.typesafe.play" %% "play-json" % PlayJsonV(isScala213) + def json(isScala213: Boolean) = + "com.typesafe.play" %% "play-json" % PlayJsonV(isScala213) - def twirlApi(isScala213: Boolean) = "com.typesafe.play" %% "twirl-api" % TwirlV(isScala213) + def twirlApi(isScala213: Boolean) = + "com.typesafe.play" %% "twirl-api" % TwirlV(isScala213) } object Poi { val PoiV = "5.2.5" - val PoiXwpfV = "1.0.6" + val PoiXwpfV = "2.0.4" val scratchpad = "org.apache.poi" % "poi-scratchpad" % PoiV + val poi = "org.apache.poi" % "poi" % PoiV val ooxml = "org.apache.poi" % "poi-ooxml" % PoiV - val converter = "fr.opensagres.xdocreport" % "org.apache.poi.xwpf.converter.xhtml" % PoiXwpfV + val ooxmlFull = "org.apache.poi" % "poi-ooxml-full" % PoiV + + val converter = "fr.opensagres.xdocreport" % "fr.opensagres.xdocreport.converter.docx.xwpf" % PoiXwpfV } object Commons { val CommonsCodecV = "1.16.0" val CommonsCompressV = "1.25.0" val CommonsLang3V = "3.7" - val CommonsIoV = "2.6" + val CommonsIoV = "2.7" val codec = "commons-codec" % "commons-codec" % CommonsCodecV val io = "commons-io" % "commons-io" % CommonsIoV @@ -132,4 +140,4 @@ object Dependencies { } -} \ No newline at end of file +} diff --git a/scalawiki-bots/src/main/scala/org/scalawiki/bots/MessageBot.scala b/scalawiki-bots/src/main/scala/org/scalawiki/bots/MessageBot.scala index c5f3b58c..ab10c309 100644 --- a/scalawiki-bots/src/main/scala/org/scalawiki/bots/MessageBot.scala +++ b/scalawiki-bots/src/main/scala/org/scalawiki/bots/MessageBot.scala @@ -51,7 +51,7 @@ class MessageBot(val conf: Config) extends ActionLibrary with QueryLibrary { */ val talkPageMessage = conf.as[Message]("talk-page") - implicit lazy val bot = MwBot.fromHost(host) + implicit lazy val bot: MwBot = MwBot.fromHost(host) def run() = { for (users <- fetchUsers(userListPage)) diff --git a/scalawiki-bots/src/main/scala/org/scalawiki/bots/museum/ImageListParser.scala b/scalawiki-bots/src/main/scala/org/scalawiki/bots/museum/ImageListParser.scala index db4bec7b..bca15626 100644 --- a/scalawiki-bots/src/main/scala/org/scalawiki/bots/museum/ImageListParser.scala +++ b/scalawiki-bots/src/main/scala/org/scalawiki/bots/museum/ImageListParser.scala @@ -1,12 +1,12 @@ package org.scalawiki.bots.museum -import java.io.{ByteArrayOutputStream, File, FileInputStream} +import fr.opensagres.poi.xwpf.converter.core.FileURIResolver +import fr.opensagres.poi.xwpf.converter.xhtml.{XHTMLConverter, XHTMLOptions} +import java.io.{ByteArrayOutputStream, File, FileInputStream} import org.apache.poi.ooxml.POIXMLDocument import org.apache.poi.hwpf.converter.WordToHtmlConverter import org.apache.poi.hwpf.extractor.WordExtractor -import org.apache.poi.xwpf.converter.core.FileURIResolver -import org.apache.poi.xwpf.converter.xhtml.{XHTMLConverter, XHTMLOptions} import org.apache.poi.xwpf.extractor.XWPFWordExtractor import org.apache.poi.xwpf.usermodel.XWPFDocument import better.files.{File => SFile} diff --git a/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTN.scala b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTN.scala new file mode 100644 index 00000000..a1c60555 --- /dev/null +++ b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTN.scala @@ -0,0 +1,43 @@ +package org.scalawiki.bots.np + +import org.apache.poi.ss.usermodel.{Cell, CellType} + +case class TTN(ttn: String, + date: String, + route: String, + sender: String, + senderContact: String, + receiver: String, + receiverContact: String, + description: String, + mass: Double, + places: Int, + value: Double, + cost: Double) { + def month: String = date.split("\\.").tail.reverse.mkString(".") + def year: String = date.split("\\.").last + def yyMmDd: String = date.split("\\.").reverse.mkString(".") +} + +object TTN { + def apply(cells: Seq[Cell]): Option[TTN] = { + if (cells.headOption.exists(_.getCellType == CellType.NUMERIC)) { + Some( + TTN( + ttn = cells(1).getStringCellValue, + date = cells(2).getStringCellValue, + route = cells(3).getStringCellValue, + sender = cells(4).getStringCellValue, + senderContact = cells(5).getStringCellValue, + receiver = cells(6).getStringCellValue, + receiverContact = cells(7).getStringCellValue, + description = cells(8).getStringCellValue, + mass = cells(9).getNumericCellValue, + places = cells(10).getNumericCellValue.toInt, + value = cells(11).getNumericCellValue, + cost = cells(12).getNumericCellValue + ) + ) + } else None + } +} diff --git a/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTNReader.scala b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTNReader.scala new file mode 100644 index 00000000..cc004a04 --- /dev/null +++ b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/TTNReader.scala @@ -0,0 +1,123 @@ +package org.scalawiki.bots.np + +import org.apache.poi.xssf.usermodel.XSSFWorkbook + +import java.io.{File, FileInputStream} +import scala.jdk.CollectionConverters._ + +case class Person(contact: String, ttns: Seq[TTN]) + +object Person { + def receivers(ttns: Seq[TTN]) = { + ttns.groupBy(_.receiverContact).map { + case (contact, personReceived) => Person(contact, personReceived) + } + + } +} + +case class TTNData(ttns: Seq[TTN]) { + + val byMonth = + ttns.groupBy(_.month).view.mapValues(_.map(_.cost).sum).toSeq.sortBy(_._1) + val byYear = byMonth.groupMap { + case (month, ttns) => month.split("\\.").head + } { case (month, ttns) => ttns } + + def variousStat(): Unit = { + val byYearAvg = + byYear.view.mapValues(ttns => ttns.sum / ttns.size).toSeq.sortBy(_._1) + + val year2023 = ttns.filter(_.year == "2022") + val lastYear: Seq[(Int, Seq[String])] = year2023 + .groupBy(_.receiverContact) + .view + .mapValues(x => x.size) + .map { + case (contact, count) => (count, contact) + } + .groupBy(_._1) + .view + .mapValues { x => + x.toSeq.map(_._2).distinct.sorted + } + .toSeq + .sortBy(_._1) + + println(byMonth) + println(byYearAvg) + + lastYear.filter(_._1 >= 5) foreach { + case (count, people) => println(s"$count: $people") + } + + // ttns.filter(x => x.receiverContact.contains("Мамон") && x.year == "2023").sortBy(_.yyMmDd).foreach { t => + // println(s"${t.date}, ${t.description}, ${t.mass}, ${t.cost}") + // } + + val distinct = year2023.map(_.receiverContact).distinct.size + val all = year2023.size + + println(s"All: $all, distinct: $distinct") + } + +} + +object TTNReader { + def main(args: Array[String]): Unit = { + val wlmNumbers = WlmContacts.getNumbers + + val dir = new File("c:\\wmua\\np") + val ttns2023 = TTNData(readDir(dir)).ttns.filter(_.year == "2023") + val ttnsLastMonth = ttns2023.filter { ttn => + !ttn.receiverContact.contains("Корбут") && + ttn.month == "2023.08" // || ttn.month == "2023.09" + } + println("Ttns: " + ttnsLastMonth.size) + + val wlmTtns = ttnsLastMonth.filter { ttn => + wlmNumbers.exists(n => ttn.receiverContact.contains(n)) + } + val wlmTtnsWithIndex = wlmTtns.sortBy(_.yyMmDd).zipWithIndex.map(_.swap) + println("wlm ttns: " + wlmTtnsWithIndex.size) +// wlmTtnsWithIndex.foreach(println) + val wlmTtnsNumbers = wlmTtns.map(_.ttn).toSet + + val nonWlmTtns = ttnsLastMonth + .filterNot(ttn => wlmTtnsNumbers.contains(ttn.ttn)) + .sortBy(_.yyMmDd) + .zipWithIndex + .map(_.swap) + + println("not wlm ttns: " + nonWlmTtns.size) + // nonWlmTtns.foreach(println) +// val receivers = ttnsLastMonth.map(_.receiverContact).distinct.sorted +// println("receivers: " + receivers.size) +// receivers.foreach(println) + +// ttns2023 +// .filter(x => x.receiverContact.contains("380681234567") && x.year == "2023") +// .sortBy(_.yyMmDd).zipWithIndex +// .foreach { case (t, i) => +// println(s"${i+1}. ${t.date}, ${t.description}, ${t.mass}, ${t.cost}, ${t.receiverContact}") +// } + + } + + private def readDir(dir: File): Seq[TTN] = { + val files = dir.listFiles().filter(_.getName.endsWith(".xlsx")) + files.flatMap(readFile) + } + + private def readFile(file: File): Seq[TTN] = { + val fis = new FileInputStream(file) + val workbook = new XSSFWorkbook(fis) + val sheet = workbook.getSheetAt(0) + val rowIterator = sheet.iterator.asScala + rowIterator.toSeq.flatMap { row => + TTN.apply(row.cellIterator.asScala.toSeq) + } + } + + def readWlmPhoneNumbers() = {} +} diff --git a/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/WlmContacts.scala b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/WlmContacts.scala new file mode 100644 index 00000000..1393fd9c --- /dev/null +++ b/scalawiki-bots/src/main/scala/org/scalawiki/bots/np/WlmContacts.scala @@ -0,0 +1,41 @@ +package org.scalawiki.bots.np + +object WlmContacts { + + private val Number = """[ +][\d\-()\s]{10,}""".r + + def main(args: Array[String]): Unit = { + println(getNumbers.size) + getNumbers.foreach(println) + } + + def getLines: Seq[String] = { + Seq( + " 0931234567", + " 380671234567", + " 063-123-45-67", + " 097 123-45-67", + " 095 123 45 67", + " 095-1234567", + " (097)1234567", + " 380 (63) 123 45 67", + " 38 050 123 45 67" + ) + } + + def getNumbers: Seq[String] = { + getLines.flatMap(getNumber) + } + + def getNumber(line: String): Seq[String] = { + Number + .findAllIn(line) + .toSeq + .map(_.filter(_.isDigit)) + .collect { + case n if n.length == 10 => "38" + n + case n if n.length == 12 => n + } + } + +} diff --git a/scalawiki-bots/src/test/scala/org/scalawiki/bots/np/WlmContactsSpec.scala b/scalawiki-bots/src/test/scala/org/scalawiki/bots/np/WlmContactsSpec.scala new file mode 100644 index 00000000..b61fdfb2 --- /dev/null +++ b/scalawiki-bots/src/test/scala/org/scalawiki/bots/np/WlmContactsSpec.scala @@ -0,0 +1,27 @@ +package org.scalawiki.bots.np + +import org.specs2.mutable.Specification + +class WlmContactsSpec extends Specification{ + + "WlmContacts" should { + val numbers = WlmContacts.getNumbers + "pick 10 digits" in { + numbers.contains("380931234567") === true + } + + "pick 12 digits" in { + numbers.contains("380671234567") === true + } + + "pick dashed/spaced/braced digits" in { + numbers.contains("380631234567") === true + numbers.contains("380971234567") === true + numbers.contains("380951234567") === true + numbers.contains("380951234567") === true + numbers.contains("380971234567") === true + numbers.contains("380631234567") === true + numbers.contains("380501234567") === true + } + } +} diff --git a/scalawiki-core/src/main/scala/org/scalawiki/query/DslQuery.scala b/scalawiki-core/src/main/scala/org/scalawiki/query/DslQuery.scala index 76d47f54..a76f4429 100644 --- a/scalawiki-core/src/main/scala/org/scalawiki/query/DslQuery.scala +++ b/scalawiki-core/src/main/scala/org/scalawiki/query/DslQuery.scala @@ -8,9 +8,15 @@ import org.scalawiki.json.Parser import scala.concurrent.Future import scala.util.{Failure, Success} -case class QueryProgress(pages: Long, done: Boolean, action: Action, bot: MwBot, context: Map[String, String] = Map.empty) +case class QueryProgress(pages: Long, + done: Boolean, + action: Action, + bot: MwBot, + context: Map[String, String] = Map.empty) -class DslQuery(val action: Action, val bot: MwBot, context: Map[String, String] = Map.empty) { +class DslQuery(val action: Action, + val bot: MwBot, + context: Map[String, String] = Map.empty) { import scala.concurrent.ExecutionContext.Implicits.global @@ -27,9 +33,9 @@ class DslQuery(val action: Action, val bot: MwBot, context: Map[String, String] onProgress(pages.size) - implicit val success: retry.Success[String] = retry.Success[String](_ => true) + implicit val success: retry.Success[String] = + retry.Success[String](_ => true) - // TODO fix memory leak retry.Backoff()(odelay.Timer.default)(() => bot.post(params.toMap)) flatMap { body => val parser = new Parser(action) @@ -53,17 +59,17 @@ class DslQuery(val action: Action, val bot: MwBot, context: Map[String, String] Future.failed(withParams) case Failure(ex) => bot.log.error(s"${bot.host} exception $ex, body: $body") - Future.failed(ex) + Future.failed(MwException(ex.getMessage, ex.getMessage, params.toMap)) } } } - def onProgress(pages: Long, done: Boolean = false): Unit = { if (done) { val estimatedTime = (System.nanoTime() - startTime) / Math.pow(10, 9) - bot.log.info(s"${bot.host} Action completed with $pages pages in $estimatedTime seconds, $action.pairs") + bot.log.info( + s"${bot.host} Action completed with $pages pages in $estimatedTime seconds, $action.pairs") } else { bot.log.info(s"${bot.host} pages: $pages action: $action.pairs") } diff --git a/scalawiki-core/src/main/scala/org/scalawiki/query/PageQuery.scala b/scalawiki-core/src/main/scala/org/scalawiki/query/PageQuery.scala index 4075899b..b6c31a03 100644 --- a/scalawiki-core/src/main/scala/org/scalawiki/query/PageQuery.scala +++ b/scalawiki-core/src/main/scala/org/scalawiki/query/PageQuery.scala @@ -7,17 +7,24 @@ import scala.concurrent.Future trait PageQuery { - def revisions(namespaces: Set[Int] = Set.empty, props: Set[String] = Set.empty, continueParam: Option[(String, String)] = None): Future[Iterable[Page]] + def revisions( + namespaces: Set[Int] = Set.empty, + props: Set[String] = Set.empty, + continueParam: Option[(String, String)] = None): Future[Iterable[Page]] } object PageQuery { - def byTitles(titles: Set[String], bot: MwBot): PageQuery = new PageQueryImplDsl(Right(titles), bot) + def byTitles(titles: Set[String], bot: MwBot): PageQuery = + new PageQueryImplDsl(Right(titles), bot) - def byTitle(title: String, bot: MwBot): SinglePageQuery = new PageQueryImplDsl(Right(Set(title)), bot) + def byTitle(title: String, bot: MwBot): SinglePageQuery = + new PageQueryImplDsl(Right(Set(title)), bot) - def byIds(ids: Set[Long], bot: MwBot):PageQuery = new PageQueryImplDsl(Left(ids), bot) + def byIds(ids: Set[Long], bot: MwBot): PageQuery = + new PageQueryImplDsl(Left(ids), bot) - def byId(id: Long, bot: MwBot): SinglePageQuery = new PageQueryImplDsl(Left(Set(id)), bot) + def byId(id: Long, bot: MwBot): SinglePageQuery = + new PageQueryImplDsl(Left(Set(id)), bot) } diff --git a/scalawiki-core/src/main/scala/org/scalawiki/query/PageQueryImplDsl.scala b/scalawiki-core/src/main/scala/org/scalawiki/query/PageQueryImplDsl.scala index e628ca92..f7b4bf12 100644 --- a/scalawiki-core/src/main/scala/org/scalawiki/query/PageQueryImplDsl.scala +++ b/scalawiki-core/src/main/scala/org/scalawiki/query/PageQueryImplDsl.scala @@ -17,12 +17,17 @@ import scala.concurrent.Future class PageQueryImplDsl(query: Either[Set[Long], Set[String]], bot: MwBot, - context: Map[String, String] = Map.empty) extends PageQuery with SinglePageQuery { + context: Map[String, String] = Map.empty) + extends PageQuery + with SinglePageQuery { override def withContext(context: Map[String, String]) = new PageQueryImplDsl(query, bot, context) - override def revisions(namespaces: Set[Int], props: Set[String], continueParam: Option[(String, String)]): Future[Iterable[Page]] = { + override def revisions( + namespaces: Set[Int], + props: Set[String], + continueParam: Option[(String, String)]): Future[Iterable[Page]] = { import org.scalawiki.dto.cmd.query.prop.rvprop._ @@ -31,59 +36,63 @@ class PageQueryImplDsl(query: Either[Set[Long], Set[String]], titles => TitlesParam(titles.toSeq) ) - val action = Action(Query( - pages, - Prop( - Info(), - Revisions( - RvProp(RvPropArgs.byNames(props.toSeq): _*), - RvLimit("max") + val action = Action( + Query( + pages, + Prop( + Info(), + Revisions( + RvProp(RvPropArgs.byNames(props.toSeq): _*), + RvLimit("max") + ) ) - ) - )) + )) bot.run(action, context) } override def revisionsByGenerator( - generator: String, - generatorPrefix: String, - namespaces: Set[Int], - props: Set[String], - continueParam: Option[(String, String)], - limit: String, - titlePrefix: Option[String]): Future[Iterable[Page]] = { + generator: String, + generatorPrefix: String, + namespaces: Set[Int], + props: Set[String], + continueParam: Option[(String, String)], + limit: String, + titlePrefix: Option[String]): Future[Iterable[Page]] = { val pageId: Option[Long] = query.left.toOption.map(_.head) val title: Option[String] = query.right.toOption.map(_.head) - val generatorArg = ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)) + val generatorArg = + ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)) val queryParams = pagesParam(pageId, title, generatorArg) ++ Seq( Prop( Info(), Revisions(RvProp(RvPropArgs.byNames(props.toSeq): _*)) ), - Generator(ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)).get) + Generator( + ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)).get) ) - val action = Action(Query(queryParams:_*)) + val action = Action(Query(queryParams: _*)) bot.run(action, context) } override def imageInfoByGenerator( - generator: String, - generatorPrefix: String, - namespaces: Set[Int], - props: Set[String], - continueParam: Option[(String, String)], - limit: String, - titlePrefix: Option[String]): Future[Iterable[Page]] = { + generator: String, + generatorPrefix: String, + namespaces: Set[Int], + props: Set[String], + continueParam: Option[(String, String)], + limit: String, + titlePrefix: Option[String]): Future[Iterable[Page]] = { import org.scalawiki.dto.cmd.query.prop.iiprop._ val pageId: Option[Long] = query.left.toOption.map(_.head) val title: Option[String] = query.right.toOption.map(_.head) - val generatorArg = ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)) + val generatorArg = + ListArgs.toDsl(generator, title, pageId, namespaces, Some(limit)) val queryParams = pagesParam(pageId, title, generatorArg) ++ Seq( Prop( ImageInfo( @@ -93,38 +102,52 @@ class PageQueryImplDsl(query: Either[Set[Long], Set[String]], Generator(generatorArg.get) ) - val action = Action(Query(queryParams:_*)) + val action = Action(Query(queryParams: _*)) bot.run(action, context) } - private def pagesParam(pageId: Option[Long], title: Option[String], generatorArg: Option[GeneratorArg]) = { - val pagesInGenerator = generatorArg.exists(_.pairs.map(_._1).exists(p => p.endsWith("title") || p.endsWith("pageid"))) - if (pagesInGenerator) Seq.empty[QueryParam[String]] else { - title.map(t => TitlesParam(Seq(t))).toSeq ++ pageId.map(id => PageIdsParam(Seq(id))).toSeq + private def pagesParam(pageId: Option[Long], + title: Option[String], + generatorArg: Option[GeneratorArg]) = { + val pagesInGenerator = generatorArg.exists( + _.pairs + .map(_._1) + .exists(p => p.endsWith("title") || p.endsWith("pageid"))) + if (pagesInGenerator) Seq.empty[QueryParam[String]] + else { + title.map(t => TitlesParam(Seq(t))).toSeq ++ pageId + .map(id => PageIdsParam(Seq(id))) + .toSeq } } - override def edit(text: String, summary: Option[String] = None, section: Option[String] = None, token: Option[String] = None, multi: Boolean = false) = { + override def edit(text: String, + summary: Option[String] = None, + section: Option[String] = None, + token: Option[String] = None, + multi: Boolean = false) = { val page = query.fold( ids => PageId(ids.head), titles => Title(titles.head) ) - val action = Action(Edit( - page, - Text(text), - Token(token.fold(bot.token)(identity)) - )) + val action = Action( + Edit( + page, + Text(text), + Token(token.fold(bot.token)(identity)) + )) val params = action.pairs.toMap ++ Map("action" -> "edit", - "format" -> "json", - "utf8" -> "", - "bot" -> "x", - "assert" -> "user", - "assert" -> "bot") ++ section.map(s => "section" -> s).toSeq ++ summary.map(s => "summary" -> s).toSeq - + "format" -> "json", + "utf8" -> "", + "bot" -> "x", + "assert" -> "user", + "assert" -> "bot") ++ section + .map(s => "section" -> s) + .toSeq ++ summary.map(s => "summary" -> s).toSeq import scala.concurrent.ExecutionContext.Implicits.global def performEdit(): Future[String] = { @@ -157,7 +180,8 @@ class PageQueryImplDsl(query: Either[Set[Long], Set[String]], "comment" -> "update", "filesize" -> fileContents.size.toString, "assert" -> "user", - "assert" -> "bot") ++ + "assert" -> "bot" + ) ++ text.map("text" -> _) ++ comment.map("comment" -> _) ++ (if (ignoreWarnings) Seq("ignorewarnings" -> "true") else Seq.empty) @@ -165,44 +189,53 @@ class PageQueryImplDsl(query: Either[Set[Long], Set[String]], bot.postFile(uploadResponseReads, params, "file", filename) } - override def whatTranscludesHere(namespaces: Set[Int], continueParam: Option[(String, String)]): Future[Iterable[Page]] = { + override def whatTranscludesHere( + namespaces: Set[Int], + continueParam: Option[(String, String)]): Future[Iterable[Page]] = { val pages = query.fold( ids => EiPageId(ids.head), titles => EiTitle(titles.head) ) - val action = Action(Query( - ListParam( - EmbeddedIn( - pages, - EiLimit("max"), - EiNamespace(namespaces.toSeq) + val action = Action( + Query( + ListParam( + EmbeddedIn( + pages, + EiLimit("max"), + EiNamespace(namespaces.toSeq) + ) ) - ) - )) + )) bot.run(action, context) } - override def categoryMembers(namespaces: Set[Int], continueParam: Option[(String, String)]): Future[Iterable[Page]] = { + override def categoryMembers( + namespaces: Set[Int], + continueParam: Option[(String, String)]): Future[Iterable[Page]] = { val pages = query.fold( ids => CmPageId(ids.head), titles => CmTitle(titles.head) ) - val cmTypes = namespaces.filter(_ == Namespace.CATEGORY).map(_ => CmTypeSubCat) ++ + val cmTypes = namespaces + .filter(_ == Namespace.CATEGORY) + .map(_ => CmTypeSubCat) ++ namespaces.filter(_ == Namespace.FILE).map(_ => CmTypeFile) - val cmParams = Seq(pages, - CmLimit("max"), - CmNamespace(namespaces.toSeq) - ) ++ (if (cmTypes.nonEmpty) Seq(CmType(cmTypes.toSeq: _*)) else Seq.empty) + val cmParams = Seq(pages, CmLimit("max"), CmNamespace(namespaces.toSeq)) ++ (if (cmTypes.nonEmpty) + Seq(CmType( + cmTypes.toSeq: _*)) + else + Seq.empty) - val action = Action(Query( - ListParam( - CategoryMembers(cmParams: _*) - ) - )) + val action = Action( + Query( + ListParam( + CategoryMembers(cmParams: _*) + ) + )) bot.run(action, context) } diff --git a/scalawiki-core/src/main/scala/org/scalawiki/query/SinglePageQuery.scala b/scalawiki-core/src/main/scala/org/scalawiki/query/SinglePageQuery.scala index a09462a8..201f3c0e 100644 --- a/scalawiki-core/src/main/scala/org/scalawiki/query/SinglePageQuery.scala +++ b/scalawiki-core/src/main/scala/org/scalawiki/query/SinglePageQuery.scala @@ -6,26 +6,37 @@ import scala.concurrent.Future trait SinglePageQuery { - def whatTranscludesHere(namespaces: Set[Int] = Set.empty, continueParam: Option[(String, String)] = None): Future[Iterable[Page]] + def whatTranscludesHere( + namespaces: Set[Int] = Set.empty, + continueParam: Option[(String, String)] = None): Future[Iterable[Page]] - def categoryMembers(namespaces: Set[Int] = Set.empty, continueParam: Option[(String, String)] = None): Future[Iterable[Page]] + def categoryMembers( + namespaces: Set[Int] = Set.empty, + continueParam: Option[(String, String)] = None): Future[Iterable[Page]] - def revisions(namespaces: Set[Int] = Set.empty, props: Set[String] = Set.empty, continueParam: Option[(String, String)] = None): Future[Iterable[Page]] + def revisions( + namespaces: Set[Int] = Set.empty, + props: Set[String] = Set.empty, + continueParam: Option[(String, String)] = None): Future[Iterable[Page]] def revisionsByGenerator( - generator: String, generatorPrefix: String, - namespaces: Set[Int] = Set.empty, props: Set[String] = Set.empty, - continueParam: Option[(String, String)] = None, - limit: String = "max", - titlePrefix: Option[String] = None): Future[Iterable[Page]] + generator: String, + generatorPrefix: String, + namespaces: Set[Int] = Set.empty, + props: Set[String] = Set.empty, + continueParam: Option[(String, String)] = None, + limit: String = "max", + titlePrefix: Option[String] = None): Future[Iterable[Page]] def imageInfoByGenerator( - generator: String, generatorPrefix: String, - namespaces: Set[Int] = Set(), - props: Set[String] = Set("timestamp", "user", "size", "url" /*, "extmetadata"*/), - continueParam: Option[(String, String)] = None, - limit: String = "max", - titlePrefix: Option[String] = None): Future[Iterable[Page]] + generator: String, + generatorPrefix: String, + namespaces: Set[Int] = Set(), + props: Set[String] = + Set("timestamp", "user", "size", "url" /*, "extmetadata"*/ ), + continueParam: Option[(String, String)] = None, + limit: String = "max", + titlePrefix: Option[String] = None): Future[Iterable[Page]] def edit(text: String, summary: Option[String] = None, diff --git a/scalawiki-wlx/src/main/resources/wle_ua.conf b/scalawiki-wlx/src/main/resources/wle_ua.conf index 9f2667d4..292f7126 100644 --- a/scalawiki-wlx/src/main/resources/wle_ua.conf +++ b/scalawiki-wlx/src/main/resources/wle_ua.conf @@ -44,7 +44,15 @@ "1-3": 3.5, "4-9": 1.5 } + }, + "2023" : { + "base-rate" : 0.5 + "number-of-authors-bonus": { + same-author-zero-bonus: true, + "0-0": 9.5, + "1-3": 3.5, + "4-9": 1.5 + } } - } } \ No newline at end of file diff --git a/scalawiki-wlx/src/main/resources/wlm_ua.conf b/scalawiki-wlx/src/main/resources/wlm_ua.conf index 35625eba..b1654bb6 100644 --- a/scalawiki-wlx/src/main/resources/wlm_ua.conf +++ b/scalawiki-wlx/src/main/resources/wlm_ua.conf @@ -27,7 +27,7 @@ "fileTemplate": "Monument Ukraine" rates: { - "2020" : { + "2020": { "number-of-authors-bonus": { same-author-zero-bonus: false, "0-0": 10, @@ -43,7 +43,7 @@ "10-49": 1 } }, - "2021" : { + "2021": { "number-of-authors-bonus": { same-author-zero-bonus: false, "0-0": 10, @@ -59,22 +59,38 @@ "10-49": 1 } }, - "2022" : { - "number-of-authors-bonus": { - same-author-zero-bonus: false, - "0-0": 10, - "1-3": 6, - "4-6": 3, - "7-9": 1 - }, - "number-of-images-bonus": { - same-author-zero-bonus: false, - "0-0": 10, - "1-3": 6, - "4-9": 3, - "10-49": 1 - } - } + "2022": { + "number-of-authors-bonus": { + same-author-zero-bonus: false, + "0-0": 10, + "1-3": 6, + "4-6": 3, + "7-9": 1 + }, + "number-of-images-bonus": { + same-author-zero-bonus: false, + "0-0": 10, + "1-3": 6, + "4-9": 3, + "10-49": 1 + } + }, + "2023": { + "number-of-authors-bonus": { + same-author-zero-bonus: false, + "0-0": 10, + "1-3": 6, + "4-6": 3, + "7-9": 1 + }, + "number-of-images-bonus": { + same-author-zero-bonus: false, + "0-0": 10, + "1-3": 6, + "4-9": 3, + "10-49": 1 + } + } } "nominations": [ @@ -138,14 +154,38 @@ "name": "Єврейська спадщина", "listTemplate": "WLM-рядок", "pages": ["Template:WLM єврейська спадщина"], - "years": [2019, 2020, 2021, 2022] + "years": [2019, 2020, 2021, 2022, 2023] }, { - "name": "Плівка", + "name": "Німецька спадщина", + "listTemplate": "WLM-рядок", + "pages": ["Template:WLM Німецька спадщина"], + "years": [2023] + }, + { + "name": "Плівка 2022", "listTemplate": "WLM-рядок", "fileTemplate": "WLM2022-UA-film", "years": [2022] }, + { + "name": "Плівка 2023", + "listTemplate": "WLM-рядок", + "fileTemplate": "WLM2023-UA-film", + "years": [2023] + }, + { + "name": "Екстер'єри 2023", + "listTemplate": "WLM-рядок", + "fileTemplate": "WLM2023-UA-exterior", + "years": [2023] + }, + { + "name": "Інтер'єри 2023", + "listTemplate": "WLM-рядок", + "fileTemplate": "WLM2023-UA-interior", + "years": [2023] + }, { "name": "Віа Регіа", "listTemplate": "ВЛП-рядок", @@ -190,6 +230,20 @@ "name": "Аерофото", "fileTemplate": "WLM2021-UA-Aero" "years": [2021] + }, + { + "name": "Харків", + "listTemplate": "ВЛП-рядок", + "cities": [ + {"name": "Харків", "code": "63101"} + ], + "years": [2023] + }, + { + "name": "Війна руйнує пам'ятки", + "listTemplate": "WLM-рядок", + "pages": ["Template:WLM destroyed"], + "years": [2023] } ] } \ No newline at end of file diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/StatParams.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/StatParams.scala index 0c849f73..9b13e166 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/StatParams.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/StatParams.scala @@ -2,66 +2,107 @@ package org.scalawiki.wlx.stat import java.time.ZonedDateTime -case class StatConfig(campaign: String, - years: Seq[Int] = Nil, - regions: Seq[String] = Nil, - exceptRegions: Seq[String] = Nil, - cities: Seq[String] = Nil, - exceptCities: Seq[String] = Nil, - rateConfig: RateConfig = RateConfig(), - gallery: Boolean = false, - fillLists: Boolean = false, - wrongIds: Boolean = false, - missingIds: Boolean = false, - multipleIds: Boolean = false, - lowRes: Boolean = false, - specialNominations: Boolean = false, - regionalStat: Boolean = false, - regionalDetails: Boolean = false, - authorsStat: Boolean = false, - regionalGallery: Boolean = false, - missingGallery: Boolean = false, - placeDetection: Boolean = false, - newMonuments: Boolean = false, - rateInputDistribution: Boolean = false, - mostPopularMonuments: Boolean = false, - minMpx: Option[Float] = None, - previousYearsGallery: Boolean = false, - numberOfMonumentsByNumberOfPictures: Boolean = false) +case class StatConfig( + campaign: String, + years: Seq[Int] = Nil, + regions: Seq[String] = Nil, + exceptRegions: Seq[String] = Nil, + cities: Seq[String] = Nil, + exceptCities: Seq[String] = Nil, + rateConfig: RateConfig = RateConfig(), + gallery: Boolean = false, + fillLists: Boolean = false, + wrongIds: Boolean = false, + missingIds: Boolean = false, + multipleIds: Boolean = false, + lowRes: Boolean = false, + specialNominations: Boolean = false, + regionalStat: Boolean = false, + regionalDetails: Boolean = false, + authorsStat: Boolean = false, + regionalGallery: Boolean = false, + missingGallery: Boolean = false, + placeDetection: Boolean = false, + newMonuments: Boolean = false, + rateInputDistribution: Boolean = false, + mostPopularMonuments: Boolean = false, + minMpx: Option[Float] = None, + previousYearsGallery: Boolean = false, + numberOfMonumentsByNumberOfPictures: Boolean = false +) import org.rogach.scallop._ class StatParams(arguments: Seq[String]) extends ScallopConf(arguments) { val years = opt[List[Int]]("year", 'y', "contest year.") - val startYear = opt[Int]("startyear", 's', "contest year.") - val campaign = opt[String]("campaign", 'c', "upload campaign, like wlm-ua.", required = true) + val startYear = opt[Int]("start-year", 's', "contest year.") + val campaign = opt[String]( + "campaign", + 'c', + "upload campaign, like wlm-ua.", + required = true + ) val regions = opt[List[String]]("region", 'r', "region code") - val exceptRegions = opt[List[String]](name = "exceptregions", descr = "except region codes") + val exceptRegions = + opt[List[String]](name = "except-regions", descr = "except region codes") val cities = opt[List[String]](name = "cities", descr = "cities") - val exceptCities = opt[List[String]](name = "exceptcities", descr = "except cities") - val newObjectRating = opt[Double](name = "new-object-rating", descr = "new object rating") - val newAuthorObjectRating = opt[Double](name = "new-author-object-rating", descr = "new author object rating") - val numberOfAuthorsBonus = opt[Boolean](name = "number-of-authors-bonus", descr = "number of authors bonus") - val numberOfImagesBonus = opt[Boolean](name = "number-of-images-bonus", descr = "number of images bonus") + val exceptCities = + opt[List[String]](name = "except-cities", descr = "except cities") + val newObjectRating = + opt[Double](name = "new-object-rating", descr = "new object rating") + val newAuthorObjectRating = opt[Double]( + name = "new-author-object-rating", + descr = "new author object rating" + ) + val numberOfAuthorsBonus = opt[Boolean]( + name = "number-of-authors-bonus", + descr = "number of authors bonus" + ) + val numberOfImagesBonus = opt[Boolean]( + name = "number-of-images-bonus", + descr = "number of images bonus" + ) val baseRate = opt[Double](name = "base-rate", descr = "base rate") val gallery = opt[Boolean](name = "gallery", descr = "gallery") val fillLists = opt[Boolean](name = "fill-lists", descr = "fill lists") val wrongIds = opt[Boolean](name = "wrong-ids", descr = "report wrong ids") - val missingIds = opt[Boolean](name = "missing-ids", descr = "report missing ids") - val multipleIds = opt[Boolean](name = "multiple-ids", descr = "report multiple ids") - val lowRes = opt[Boolean](name = "low-res", descr = "report low resolution photos") - val specialNominations = opt[Boolean](name = "special-nominations", descr = "report special nominations") - val regionalStat = opt[Boolean](name = "regional-stat", descr = "report regional statistics") - val regionalDetails = opt[Boolean](name = "regional-details", descr = "report regional detailed statistics") - val authorsStat = opt[Boolean](name = "authors-stat", descr = "report authors statistics") - val regionalGallery = opt[Boolean](name = "regional-gallery", descr = "report regional gallery") - val missingGallery = opt[Boolean](name = "missing-gallery", descr = "report missing galleries") - val placeDetection = opt[Boolean](name = "place-detection", descr = "report place detection") - val newMonuments = opt[Boolean](name = "new-monuments", descr = "new monuments") - val rateInputDistribution = opt[Boolean](name = "rate-input-distribution", descr = "rate input distribution") - val mostPopularMonuments = opt[Boolean](name = "most-popular-monuments", descr = "most popular monuments") + val missingIds = + opt[Boolean](name = "missing-ids", descr = "report missing ids") + val multipleIds = + opt[Boolean](name = "multiple-ids", descr = "report multiple ids") + val lowRes = + opt[Boolean](name = "low-res", descr = "report low resolution photos") + val specialNominations = opt[Boolean]( + name = "special-nominations", + descr = "report special nominations" + ) + val regionalStat = + opt[Boolean](name = "regional-stat", descr = "report regional statistics") + val regionalDetails = opt[Boolean]( + name = "regional-details", + descr = "report regional detailed statistics" + ) + val authorsStat = + opt[Boolean](name = "authors-stat", descr = "report authors statistics") + val regionalGallery = + opt[Boolean](name = "regional-gallery", descr = "report regional gallery") + val missingGallery = + opt[Boolean](name = "missing-gallery", descr = "report missing galleries") + val placeDetection = + opt[Boolean](name = "place-detection", descr = "report place detection") + val newMonuments = + opt[Boolean](name = "new-monuments", descr = "new monuments") + val rateInputDistribution = opt[Boolean]( + name = "rate-input-distribution", + descr = "rate input distribution" + ) + val mostPopularMonuments = opt[Boolean]( + name = "most-popular-monuments", + descr = "most popular monuments" + ) val minMpx = opt[Float](name = "min-mpx", descr = "minimum megapixels") - val previousYearsGallery = opt[Boolean](name = "prev-years-gallery", descr = "previous years gallery") + val previousYearsGallery = + opt[Boolean](name = "prev-years-gallery", descr = "previous years gallery") verify() } @@ -104,4 +145,4 @@ object StatParams { previousYearsGallery = conf.previousYearsGallery.getOrElse(false) ) } -} \ No newline at end of file +} diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/Statistics.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/Statistics.scala index 7ee8b8dc..6e7a4707 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/Statistics.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/Statistics.scala @@ -79,9 +79,9 @@ class Statistics(contest: Contest, config: Option[StatConfig] = None) = this(contest, startYear, monumentQuery, imageQuery, imageQueryWiki, bot, config.getOrElse(StatConfig(contest.campaign))) - val currentYear = contest.year + private val currentYear = contest.year - val contests = (startYear.getOrElse(currentYear) to currentYear).map(y => contest.copy(year = y)) + private val contests = (startYear.getOrElse(currentYear) to currentYear).map(y => contest.copy(year = y)) /** * Fetches contest data @@ -138,11 +138,11 @@ class Statistics(contest: Contest, }.failed.map(println) } - def articleStatistics(monumentDb: MonumentDB) = { + def articleStatistics(monumentDb: MonumentDB): Unit = { println(Stats.withArticles(monumentDb).asWiki("Article Statistics").asWiki) } - def toMassMessage(users: Iterable[String]) = { + def toMassMessage(users: Iterable[String]): Iterable[String] = { users.map(name => s"{{#target:User talk:$name}}") } @@ -164,7 +164,7 @@ object Statistics { ) } - def main(args: Array[String]) { + def main(args: Array[String]): Unit = { val cfg = StatParams.parse(args) val contest = getContest(cfg) diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/AuthorsStat.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/AuthorsStat.scala index a6a945ea..209181a9 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/AuthorsStat.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/AuthorsStat.scala @@ -14,10 +14,15 @@ class AuthorsStat(val uploadImages: Boolean = false) { val charts = new Charts() def authorsStat(data: ContestStat, bot: MwBot, gallery: Boolean) = { - new AuthorMonuments(data, gallery = gallery, commons = Some(bot)).updateWiki(bot) + new AuthorMonuments(data, gallery = gallery, commons = Some(bot)) + .updateWiki(bot) } - def authorsContributed(imageDbs: Seq[ImageDB], totalImageDb: Option[ImageDB], monumentDb: Option[MonumentDB]) = { + def authorsContributed( + imageDbs: Seq[ImageDB], + totalImageDb: Option[ImageDB], + monumentDb: Option[MonumentDB] + ): String = { val table = authorsContributedTable(imageDbs, totalImageDb, monumentDb) @@ -25,7 +30,34 @@ class AuthorsStat(val uploadImages: Boolean = false) { header + table.asWiki + userImages.getOrElse("") } - def authorsContributedTable(imageDbs: Seq[ImageDB], totalImageDb: Option[ImageDB], monumentDb: Option[MonumentDB]): Table = { + def authorsContributedPerRegion(imageDb: ImageDB, bot: MwBot) = { + imageDb.monumentDb.map { monumentDb => + monumentDb.regionIds.map { regionId => + val country = monumentDb.contest.country + val regionName = country.regionName(regionId) + val authors = + imageDb + .authorsByRegion(regionId) + .toSeq + .sorted + .map(_.replace("Участник:", "")) + .filterNot(_.isBlank) + val listPage = + "Commons:Wiki Loves Monuments in Ukraine/Автори " + regionName + val text = authors + .map(author => s"# {{#target:User_talk:$author|}}") + .mkString("\n") + bot.page(listPage).edit(text) + } + } + } + + def authorsContributedTable( + imageDbs: Seq[ImageDB], + totalImageDb: Option[ImageDB], + monumentDb: Option[MonumentDB], + listAuthors: Boolean = true + ): Table = { val contest = monumentDb.map(_.contest).getOrElse(imageDbs.head.contest) val categoryName = contest.imagesCategory @@ -36,7 +68,9 @@ class AuthorsStat(val uploadImages: Boolean = false) { val dataset = new DefaultCategoryDataset() - val yearDbs = yearSeq.flatMap { year => imageDbsByYear(year).headOption } + val yearDbs = yearSeq.flatMap { year => + imageDbsByYear(year).headOption + } val dbs = totalImageDb.toSeq ++ yearDbs val columns = Seq("Region") ++ @@ -44,20 +78,33 @@ class AuthorsStat(val uploadImages: Boolean = false) { yearSeq.map(_.toString) val perRegion = monumentDb.fold(Seq.empty[Seq[String]]) { db => - val country = db.contest.country - db.regionIds.map { regionId => - val regionName = country.regionName(regionId) - - val shortRegionName = regionName - .replaceAll("область", "") - .replaceAll("Автономна Республіка", "АР") - - yearDbs.zipWithIndex.foreach { case (yearDb, i) => - dataset.addValue(yearDb.authorsByRegion(regionId).size, yearSeq(i), shortRegionName) - } - - Seq(regionName) ++ dbs.map(_.authorsByRegion(regionId).size.toString) + val country = db.contest.country + db.regionIds.map { regionId => + val regionName = country.regionName(regionId) + + val shortRegionName = regionName + .replaceAll("область", "") + .replaceAll("Автономна Республіка", "АР") + + yearDbs.zipWithIndex.foreach { case (yearDb, i) => + dataset.addValue( + yearDb.authorsByRegion(regionId).size, + yearSeq(i), + shortRegionName + ) } + + Seq(regionName) ++ + totalImageDb.map { db => + val count = db.authorsByRegion(regionId).size.toString + if (listAuthors) { + val listPage = + "Commons:Wiki Loves Monuments in Ukraine/Автори " + regionName + s"[[$listPage|$count]]" + } else count + } ++ + yearDbs.map(_.authorsByRegion(regionId).size.toString) + } } val totalData = Seq("Total") ++ dbs.map(_.authors.size.toString) @@ -69,20 +116,38 @@ class AuthorsStat(val uploadImages: Boolean = false) { val ids = yearSeq.map(year => imageDbsByYear(year).head.authors) val idsSize = ids.map(_.size) - userImages = Some(authorsStatImages(filenamePrefix, categoryName, yearSeq, dataset, ids, idsSize, uploadImages)) + userImages = Some( + authorsStatImages( + filenamePrefix, + categoryName, + yearSeq, + dataset, + ids, + idsSize, + uploadImages + ) + ) new Table(columns, rows, "Authors contributed") } - def authorsImages(byAuthor: Map[String, Seq[Image]], monumentDb: Option[MonumentDB]): String = { + def authorsImages( + byAuthor: Map[String, Seq[Image]], + monumentDb: Option[MonumentDB] + ): String = { val sections = byAuthor - .mapValues(images => monumentDb.fold(images)(db => images.filter(_.monumentId.fold(false)(db.ids.contains)))) + .mapValues(images => + monumentDb.fold(images)(db => + images.filter(_.monumentId.fold(false)(db.ids.contains)) + ) + ) .collect { case (user, images) if images.nonEmpty => val userLink = s"[[User:$user|$user]]" val header = s"== $userLink ==\n" - val descriptions = images.map(i => i.mpx + " МПкс (" + i.resolution.getOrElse("") +")") + val descriptions = images + .map(i => i.mpx + " МПкс (" + i.resolution.getOrElse("") + ")") header + Image.gallery(images.map(_.title), descriptions) } @@ -91,14 +156,14 @@ class AuthorsStat(val uploadImages: Boolean = false) { } def authorsStatImages( - filenamePrefix: String, - categoryName: String, - yearSeq: Seq[Int], - dataset: DefaultCategoryDataset, - ids: Seq[Set[String]], - idsSize: Seq[Int], - uploadImages: Boolean = true - ) = { + filenamePrefix: String, + categoryName: String, + yearSeq: Seq[Int], + dataset: DefaultCategoryDataset, + ids: Seq[Set[String]], + idsSize: Seq[Int], + uploadImages: Boolean = true + ) = { val images = s"\n[[File:${filenamePrefix}AuthorsByYearTotal.png|$categoryName, Authors by year overall|left]]" + @@ -111,17 +176,31 @@ class AuthorsStat(val uploadImages: Boolean = false) { val chart = charts.createChart(dataset, "Регіон") val byRegionFile = filenamePrefix + "AuthorsByYear" charts.saveCharts(chart, byRegionFile, 900, 1200) - MwBot.fromHost(MwBot.commons).page(byRegionFile + ".png").upload(byRegionFile + ".png") + MwBot + .fromHost(MwBot.commons) + .page(byRegionFile + ".png") + .upload(byRegionFile + ".png") - val chartTotal = charts.createChart(charts.createTotalDataset(yearSeq, idsSize), "") + val chartTotal = + charts.createChart(charts.createTotalDataset(yearSeq, idsSize), "") val chartTotalFile = filenamePrefix + "AuthorsByYearTotal.png" charts.saveAsPNG(chartTotal, chartTotalFile, 900, 200) MwBot.fromHost(MwBot.commons).page(chartTotalFile).upload(chartTotalFile) val intersectionFile = filenamePrefix + "AuthorsByYearPie" - charts.intersectionDiagram("Унікальність авторів за роками", intersectionFile, yearSeq, ids, 900, 800) - MwBot.fromHost(MwBot.commons).page(intersectionFile + ".png").upload(intersectionFile + ".png") + charts.intersectionDiagram( + "Унікальність авторів за роками", + intersectionFile, + yearSeq, + ids, + 900, + 800 + ) + MwBot + .fromHost(MwBot.commons) + .page(intersectionFile + ".png") + .upload(intersectionFile + ".png") } images } diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/MonumentDbStat.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/MonumentDbStat.scala index 7c639dcf..c2f6a464 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/MonumentDbStat.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/MonumentDbStat.scala @@ -10,7 +10,15 @@ class MonumentDbStat { val format = NumberFormat.getPercentInstance val columns = Seq( - "country", "lang", "total", "name", "address", "coordinates", "image", "commonscat", "article" + "country", + "lang", + "total", + "name", + "address", + "coordinates", + "image", + "commonscat", + "article" ) def getStat(monumentDbs: Seq[MonumentDB]) = { @@ -32,7 +40,10 @@ class MonumentDbStat { def withPercentage(value: Int) = s"$value (${format.format(value.toDouble / total.toDouble)})" - Seq(country, lang, total.toString, + Seq( + country, + lang, + total.toString, withPercentage(name), withPercentage(address), withPercentage(coordinates), @@ -48,5 +59,3 @@ class MonumentDbStat { } } - - diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/Output.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/Output.scala index b5c56b97..e0f705de 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/Output.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/Output.scala @@ -410,6 +410,7 @@ object Output { val regionalStat = toc + idsStat + authorsContributed + category bot.page(s"Commons:$categoryName/Regional statistics").edit(regionalStat, Some("updating")) + authorsStat.authorsContributedPerRegion(stat.totalImageDb.get, bot) } def newMonuments(stat: ContestStat) = { diff --git a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/ReporterRegistry.scala b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/ReporterRegistry.scala index fcb94db3..522f5d91 100644 --- a/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/ReporterRegistry.scala +++ b/scalawiki-wlx/src/main/scala/org/scalawiki/wlx/stat/reports/ReporterRegistry.scala @@ -39,7 +39,7 @@ class ReporterRegistry(stat: ContestStat, cfg: StatConfig)(implicit ec: Executio * Outputs current year reports. * */ - def currentYear() = { + def currentYear(): Unit = { for (imageDb <- currentYearImageDb) { if (cfg.regionalGallery && stat.totalImageDb.isEmpty) { @@ -87,7 +87,7 @@ class ReporterRegistry(stat: ContestStat, cfg: StatConfig)(implicit ec: Executio } } - def allYears() = { + def allYears(): Unit = { for (imageDb <- totalImageDb) { if (cfg.fillLists) { ImageFiller.fillLists(monumentDb.get, imageDb) diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/AuthorsContributedSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/AuthorsContributedSpec.scala index d66624bb..533cd1a2 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/AuthorsContributedSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/AuthorsContributedSpec.scala @@ -14,14 +14,25 @@ class AuthorsContributedSpec extends Specification { def monument(id: String, name: String) = new Monument(id = id, name = name, listConfig = Some(WlmUa)) - def monuments(n: Int, regionId: String, namePrefix: String, startId: Int = 1): Seq[Monument] = - (startId until startId + n).map(i => monument(s"$regionId-xxx-000$i", namePrefix + i)) + def monuments( + n: Int, + regionId: String, + namePrefix: String, + startId: Int = 1 + ): Seq[Monument] = + (startId until startId + n).map(i => + monument(s"$regionId-xxx-000$i", namePrefix + i) + ) "authorsContributed" should { "work on no monuments" in { val output = new AuthorsStat val monumentDb = new MonumentDB(contest, Seq.empty) - val table = output.authorsContributedTable(Seq.empty, Some(new ImageDB(contest, Seq.empty, monumentDb)), Some(monumentDb)) + val table = output.authorsContributedTable( + Seq.empty, + Some(new ImageDB(contest, Seq.empty, monumentDb)), + Some(monumentDb) + ) table.headers === Seq("Region", "0 years total") table.data === Seq(Seq("Total", "0")) @@ -30,13 +41,19 @@ class AuthorsContributedSpec extends Specification { "work on no images" in { val output = new AuthorsStat - val monumentDb = new MonumentDB(contest, + val monumentDb = new MonumentDB( + contest, monuments(2, "01", "Crimea") ++ monuments(5, "05", "Podillya") ++ monuments(7, "07", "Volyn") ) - val table = output.authorsContributedTable(Seq.empty, Some(new ImageDB(contest, Seq.empty, monumentDb)), Some(monumentDb)) + val table = output.authorsContributedTable( + Seq.empty, + Some(new ImageDB(contest, Seq.empty, monumentDb)), + Some(monumentDb), + listAuthors = false + ) table.headers === Seq("Region", "0 years total") table.data === Seq( @@ -51,20 +68,46 @@ class AuthorsContributedSpec extends Specification { val output = new AuthorsStat val images = Seq( - Image("File:Img11.jpg", monumentIds = List("01-xxx-0001"), author = Some("FromCrimea")), - Image("File:Img51.jpg", monumentIds = List("05-xxx-0001"), author = Some("FromPodillya1")), - Image("File:Img52.jpg", monumentIds = List("05-xxx-0001"), author = Some("FromPodillya2")), - Image("File:Img71.jpg", monumentIds = List("07-xxx-0001"), author = Some("FromVolyn")), - Image("File:Img12.jpg", monumentIds = List("01-xxx-0001"), author = Some("FromCrimea")) + Image( + "File:Img11.jpg", + monumentIds = List("01-xxx-0001"), + author = Some("FromCrimea") + ), + Image( + "File:Img51.jpg", + monumentIds = List("05-xxx-0001"), + author = Some("FromPodillya1") + ), + Image( + "File:Img52.jpg", + monumentIds = List("05-xxx-0001"), + author = Some("FromPodillya2") + ), + Image( + "File:Img71.jpg", + monumentIds = List("07-xxx-0001"), + author = Some("FromVolyn") + ), + Image( + "File:Img12.jpg", + monumentIds = List("01-xxx-0001"), + author = Some("FromCrimea") + ) ) - val monumentDb = new MonumentDB(contest, + val monumentDb = new MonumentDB( + contest, monuments(2, "01", "Crimea") ++ monuments(5, "05", "Podillya") ++ monuments(7, "07", "Volyn") ) - val table = output.authorsContributedTable(Seq.empty, Some(new ImageDB(contest, images, monumentDb)), Some(monumentDb)) + val table = output.authorsContributedTable( + Seq.empty, + Some(new ImageDB(contest, images, monumentDb)), + Some(monumentDb), + listAuthors = false + ) table.headers === Seq("Region", "0 years total") table.data === Seq( @@ -79,38 +122,83 @@ class AuthorsContributedSpec extends Specification { val output = new AuthorsStat val images1 = Seq( - Image("File:Img11y1f1.jpg", monumentIds = List("01-xxx-0001"), author = Some("FromCrimea")), - Image("File:Img11y1f2.jpg", monumentIds = List("01-xxx-0001"), author = Some("FromCrimea")), - Image("File:Img51y1f1.jpg", monumentIds = List("05-xxx-0001"), author = Some("FromPodillya1")), - Image("File:Img51y1f2.jpg", monumentIds = List("05-xxx-0001"), author = Some("FromPodillya2")), - Image("File:Img71y1f1.jpg", monumentIds = List("07-xxx-0001"), author = Some("FromVolyn")) + Image( + "File:Img11y1f1.jpg", + monumentIds = List("01-xxx-0001"), + author = Some("FromCrimea") + ), + Image( + "File:Img11y1f2.jpg", + monumentIds = List("01-xxx-0001"), + author = Some("FromCrimea") + ), + Image( + "File:Img51y1f1.jpg", + monumentIds = List("05-xxx-0001"), + author = Some("FromPodillya1") + ), + Image( + "File:Img51y1f2.jpg", + monumentIds = List("05-xxx-0001"), + author = Some("FromPodillya2") + ), + Image( + "File:Img71y1f1.jpg", + monumentIds = List("07-xxx-0001"), + author = Some("FromVolyn") + ) ) val images2 = Seq( - Image("File:Img11y2f1.jpg", monumentIds = List("01-xxx-0001"), author = Some("FromCrimea1")), - Image("File:Img12y2f1.jpg", monumentIds = List("01-xxx-0002"), author = Some("FromCrimea2")), - Image("File:Img52y2f1.jpg", monumentIds = List("05-xxx-0002"), author = Some("FromPodillya1")), - Image("File:Img52y2f1.jpg", monumentIds = List("05-xxx-0002"), author = Some("FromPodillya2")), - Image("File:Img72y2f1.jpg", monumentIds = List("07-xxx-0002"), author = Some("FromVolyn")) + Image( + "File:Img11y2f1.jpg", + monumentIds = List("01-xxx-0001"), + author = Some("FromCrimea1") + ), + Image( + "File:Img12y2f1.jpg", + monumentIds = List("01-xxx-0002"), + author = Some("FromCrimea2") + ), + Image( + "File:Img52y2f1.jpg", + monumentIds = List("05-xxx-0002"), + author = Some("FromPodillya1") + ), + Image( + "File:Img52y2f1.jpg", + monumentIds = List("05-xxx-0002"), + author = Some("FromPodillya2") + ), + Image( + "File:Img72y2f1.jpg", + monumentIds = List("07-xxx-0002"), + author = Some("FromVolyn") + ) ) - val monumentDb = new MonumentDB(contest, + val monumentDb = new MonumentDB( + contest, monuments(2, "01", "Crimea") ++ monuments(5, "05", "Podillya") ++ monuments(7, "07", "Volyn") ) val table = output.authorsContributedTable( - Seq(images1, images2).zipWithIndex.map { case (images, i) => new ImageDB(Contest.WLMUkraine(2014 + i), images, monumentDb) }, + Seq(images1, images2).zipWithIndex.map { case (images, i) => + new ImageDB(Contest.WLMUkraine(2014 + i), images, monumentDb) + }, Some(new ImageDB(contest, images1 ++ images2, monumentDb)), - Some(monumentDb)) + Some(monumentDb), + listAuthors = false + ) table.headers === Seq("Region", "2 years total", "2015", "2014") table.data === Seq( Seq("Автономна Республіка Крим", "3") ++ Seq("1", "2").reverse, - Seq("Вінницька область", "2") ++ Seq( "2", "2").reverse, - Seq("Волинська область", "1") ++ Seq( "1", "1").reverse, - Seq("Total", "6") ++ Seq( "4", "5").reverse + Seq("Вінницька область", "2") ++ Seq("2", "2").reverse, + Seq("Волинська область", "1") ++ Seq("1", "1").reverse, + Seq("Total", "6") ++ Seq("4", "5").reverse ) } } diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/SpecialNominationsSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/SpecialNominationsSpec.scala index 4ba83cec..48adcdd7 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/SpecialNominationsSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/SpecialNominationsSpec.scala @@ -13,83 +13,114 @@ class SpecialNominationsSpec extends Specification { "load all" in { val expected = Seq( - new SpecialNomination("Музичні пам'ятки в Україні", - Some("WLM-рядок"), - Seq("Template:WLM-music-navbar")), - new SpecialNomination("Пам'ятки дерев'яної архітектури України", - Some("WLM-рядок"), - Seq("Template:WLM Дерев'яна архітектура")), - new SpecialNomination("Замки і фортеці України", - Some("WLM-рядок"), - Seq("Template:WLM замки і фортеці")), + new SpecialNomination( + "Музичні пам'ятки в Україні", + Some("WLM-рядок"), + Seq("Template:WLM-music-navbar") + ), + new SpecialNomination( + "Пам'ятки дерев'яної архітектури України", + Some("WLM-рядок"), + Seq("Template:WLM Дерев'яна архітектура") + ), + new SpecialNomination( + "Замки і фортеці України", + Some("WLM-рядок"), + Seq("Template:WLM замки і фортеці") + ), new SpecialNomination( "Кримськотатарські пам'ятки в Україні", Some("WLM-рядок"), Seq( - "Вікіпедія:Вікі любить пам'ятки/Кримськотатарські пам'ятки в Україні")), + "Вікіпедія:Вікі любить пам'ятки/Кримськотатарські пам'ятки в Україні" + ) + ), new SpecialNomination( "Пам'ятки національно-визвольної боротьби", Some("WLM-рядок"), Seq( - "Вікіпедія:Вікі любить пам'ятки/Пам'ятки національно-визвольної боротьби")), + "Вікіпедія:Вікі любить пам'ятки/Пам'ятки національно-визвольної боротьби" + ) + ), new SpecialNomination( "Грецькі пам'ятки в Україні", Some("WLM-рядок"), - Seq("Вікіпедія:Вікі любить пам'ятки/Грецькі пам'ятки в Україні")), + Seq("Вікіпедія:Вікі любить пам'ятки/Грецькі пам'ятки в Україні") + ), new SpecialNomination( "Вірменські пам'ятки в Україні", Some("WLM-рядок"), - Seq("Вікіпедія:Вікі любить пам'ятки/Вірменські пам'ятки в Україні")), - new SpecialNomination("Бібліотеки", - Some("WLM-рядок"), - Seq("Вікіпедія:Вікі любить пам'ятки/Бібліотеки")), + Seq("Вікіпедія:Вікі любить пам'ятки/Вірменські пам'ятки в Україні") + ), + new SpecialNomination( + "Бібліотеки", + Some("WLM-рядок"), + Seq("Вікіпедія:Вікі любить пам'ятки/Бібліотеки") + ), new SpecialNomination( "Українські пам'ятки Першої світової війни", Some("WLM-рядок"), Seq( - "Вікіпедія:Вікі любить пам'ятки/Українські пам'ятки Першої світової війни")), + "Вікіпедія:Вікі любить пам'ятки/Українські пам'ятки Першої світової війни" + ) + ), new SpecialNomination( "Цивільні споруди доби Гетьманщини", Some("WLM-рядок"), - Seq("Template:WLM цивільні споруди доби Гетьманщини")), - new SpecialNomination("Млини", - Some("WLM-рядок"), - Seq("Template:WLM млини та вітряки"), - Seq(2019, 2020, 2021)), - new SpecialNomination("Єврейська спадщина", - Some("WLM-рядок"), - Seq("Template:WLM єврейська спадщина"), - Seq(2019, 2020, 2021)), - new SpecialNomination("Віа Регіа", - Some("ВЛП-рядок"), - Nil, - Seq(2020, 2021), - Nil), - new SpecialNomination("Квіти України", - Some("WLM-рядок"), - Seq("Template:WLM Квіти України"), - Seq(2021)), - new SpecialNomination("Національно-визвольні", - Some("WLM-рядок"), - Seq("Template:WLM національно-визвольні"), - Seq(2021)), - new SpecialNomination("Пам'ятки Подесення", - Some("WLM-рядок"), - Seq("Template:WLM Пам'ятки Подесення"), - Seq(2021)), - new SpecialNomination("Аерофото", - None, - Nil, - Seq(2021), - Nil, - Some("WLM2021-UA-Aero")) + Seq("Template:WLM цивільні споруди доби Гетьманщини") + ), + new SpecialNomination( + "Млини", + Some("WLM-рядок"), + Seq("Template:WLM млини та вітряки"), + Seq(2019, 2020, 2021) + ), + new SpecialNomination( + "Єврейська спадщина", + Some("WLM-рядок"), + Seq("Template:WLM єврейська спадщина"), + Seq(2019, 2020, 2021) + ), + new SpecialNomination( + "Віа Регіа", + Some("ВЛП-рядок"), + Nil, + Seq(2020, 2021), + Nil + ), + new SpecialNomination( + "Квіти України", + Some("WLM-рядок"), + Seq("Template:WLM Квіти України"), + Seq(2021) + ), + new SpecialNomination( + "Національно-визвольні", + Some("WLM-рядок"), + Seq("Template:WLM національно-визвольні"), + Seq(2021) + ), + new SpecialNomination( + "Пам'ятки Подесення", + Some("WLM-рядок"), + Seq("Template:WLM Пам'ятки Подесення"), + Seq(2021) + ), + new SpecialNomination( + "Аерофото", + None, + Nil, + Seq(2021), + Nil, + Some("WLM2021-UA-Aero") + ) ) SpecialNomination .load("wlm_ua.conf") .map(_.copy(cities = Nil)) - .filterNot(_.years == Seq(2022)) - .map(sn => sn.copy(years = sn.years.filterNot(_ == 2022))) === expected + .filterNot(sn => sn.years == Seq(2022) || sn.years == Seq(2023)) + .map(sn => sn.copy(years = sn.years.filterNot(_ >= 2022))) === expected } } } diff --git a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/StatParamsSpec.scala b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/StatParamsSpec.scala index 8e833683..f54b1b60 100644 --- a/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/StatParamsSpec.scala +++ b/scalawiki-wlx/src/test/scala/org/scalawiki/wlx/stat/StatParamsSpec.scala @@ -35,7 +35,7 @@ class StatParamsSpec extends Specification { } "start year" in { - val cfg = StatParams.parse(Seq("--campaign", "wle-ua", "-y", "2017", "--startyear", "2012")) + val cfg = StatParams.parse(Seq("--campaign", "wle-ua", "-y", "2017", "--start-year", "2012")) cfg === StatConfig("wle-ua", 2012 to 2017) }