)")
)
- private val rangePattern = "([a-zA-ZäöüÄÖÜ]+)([\\[\\S\\s0-9+\\]]+)?\\(([0-9]+)\\/([0-9,\\*]*)(\\/[\\S\\s\\/0-9+]*)?\\)".r
+ private val rangePattern = "([a-zA-ZäöüÄÖÜ]+)([\\[\\S\\s0-9+\\]]+)?\\(([devmax\\/|devmin\\/|median\\/|min\\/|max\\/|avg\\/|sum\\/]+)?([0-9,\\*]+)\\/([0-9,\\*]*)(\\/[\\S\\s\\/0-9+]*)?\\)".r
def apply(formel: String): TeamRegel = {
- def defaultMax(max: String): Int = if (max.equals("*")) 0 else max
+ def default(cnt: String): Int = if (cnt.equals("*")) 0 else try cnt.intValue catch {case _ => 0}
val regeln = formel.split(",").map(_.trim).filter(_.nonEmpty).toList
val mappedRules: List[TeamRegel] = regeln.flatMap {
- case rangePattern(rulename, grouper, min, max, extrateams) =>
+ case rangePattern(rulename, grouper, aggFn, min, max, extrateams) =>
+ val aggFun = TeamAggreateFun(aggFn)
val extraTeamsDef = if (extrateams == null) "" else extrateams
val grouperDef = if (grouper == null) "" else grouper
rulename match {
- case `vereinGesamt` => Some(TeamRegelVereinGesamt(min, defaultMax(max), extraTeamsDef, grouperDef))
- case `verbandGesamt` => Some(TeamRegelVerbandGesamt(min, defaultMax(max), extraTeamsDef, grouperDef))
- case `vereinGeraet` => Some(TeamRegelVereinGeraet(min, defaultMax(max), extraTeamsDef, grouperDef))
- case `verbandGeraet` => Some(TeamRegelVerbandGeraet(min, defaultMax(max), extraTeamsDef, grouperDef))
+ case `vereinGesamt` => Some(TeamRegelVereinGesamt(default(min), default(max), extraTeamsDef, grouperDef, aggFun))
+ case `verbandGesamt` => Some(TeamRegelVerbandGesamt(default(min), default(max), extraTeamsDef, grouperDef, aggFun))
+ case `vereinGeraet` => Some(TeamRegelVereinGeraet(default(min), default(max), extraTeamsDef, grouperDef, aggFun))
+ case `verbandGeraet` => Some(TeamRegelVerbandGeraet(default(min), default(max), extraTeamsDef, grouperDef, aggFun))
case _ => None
}
case "Keine Teams" =>None
@@ -80,11 +85,12 @@ sealed trait TeamRegel {
* Per rule means, that each rule can have its own definition about whether a split or a group of a criterion is required.
* @param wertungen list of wertungen, containing athlets and its assignment to a program, team, etc., where each
* rule can extract its criterion to apply a splitting- or grouping- rule.
- * @return
+ * @return List[pgm,sex,List[teams]]
*/
def extractTeamsWithDefaultGouping(wertungen: Iterable[WertungView]): List[(String,String,List[Team])] = {
+ val hasNoExplicitTeams = !wertungen.exists(w => w.team != 0)
wertungen
- .filter(w => w.team != 0)
+ .filter(w => hasNoExplicitTeams || w.team != 0)
.toList
.groupBy(w => (pgmGrouperText(w), sexGrouperText(w)))
.map( gr => (gr._1._1, gr._1._2, extractTeams(gr._2)))
@@ -114,41 +120,43 @@ case class TeamRegelList(regeln: List[TeamRegel], name: Option[String] = None) e
override def toRuleName: String = name.getOrElse(regeln.map(_.toRuleName).sorted.mkString(", "))
}
-case class TeamRegelVereinGeraet(min: Int, max: Int, extraTeamsDef: String, grouperDef: String) extends TeamRegel {
+case class TeamRegelVereinGeraet(min: Int, max: Int, extraTeamsDef: String, grouperDef: String, aggregateFun: TeamAggreateFun) extends TeamRegel {
private val extrateams = parseExtrateams(extraTeamsDef)
override def getExtrateams: List[String] = extrateams
private val grouperDefs = parseGrouperDefs(grouperDef)
override def getGrouperDefs: List[Set[String]] = grouperDefs
- override def toFormel: String = s"VereinGerät$grouperDef($min/${if (max > 0) max else "*"}$extraTeamsDef)"
- override def toRuleName: String = s"""Vereins-Team Rangliste $grouperDef (beste $min Gerätewertungen${if (max > 0) s" aus $max" else ""})"""
+ override def toFormel: String = s"VereinGerät$grouperDef(${aggregateFun.toFormelPart}${if (min > 0) min else "*"}/${if (max > 0) max else "*"}$extraTeamsDef)"
+ override def toRuleName: String = s"""Vereins-Team Rangliste $grouperDef (${aggregateFun.toDescriptionPart} ${if (min == 0) "allen" else s"besten $min"}${if (max > 0) s" von max $max" else ""} Gerätewertungen)"""
override def extractTeams(wertungen: Iterable[WertungView]): List[Team] = {
val extraTeams = extractExtraTeams(wertungen)
+ val hasNoExplicitTeams = !wertungen.exists(w => w.team != 0)
wertungen
- .filter(w => w.team != 0)
+ .filter(w => hasNoExplicitTeams || w.team != 0)
.toList
.groupBy(w => w.getTeamName(extraTeams))
.flatMap { team =>
val (teamname, teamwertungen) = team
val athletCount = teamwertungen.map(w => w.athlet.id).toSet.size
- if (athletCount >= min && (max == 0 || athletCount <= max)) {
+ if (((min == 0 && athletCount > 0) || athletCount >= min) && (max == 0 || athletCount <= max)) {
+ val takeCnt = if (min == 0) athletCount else min
val perDisciplinWertungen: Map[Disziplin, List[WertungView]] = teamwertungen
.groupBy(w => w.wettkampfdisziplin.disziplin)
.flatMap { case (disciplin, wtgs) =>
val relevantDisciplineValues = wtgs
- .filter(_.resultat.endnote > 0)
+ //.filter(_.resultat.endnote > 0)
.groupBy(_.resultat.endnote).toList
- .sortBy(_._1).reverse.take(min)
+ .sortBy(_._1).reverse.take(takeCnt)
.flatMap(_._2)
if (relevantDisciplineValues.isEmpty) None else Some((disciplin, relevantDisciplineValues))
}
val perDisciplinCountingWertungen: Map[Disziplin, List[WertungView]] = perDisciplinWertungen
.flatMap { case (disciplin, wtgs) =>
val relevantDisciplineValues = wtgs
- .filter(_.resultat.endnote > 0)
- .sortBy(_.resultat.endnote).reverse.take(min)
+ //.filter(_.resultat.endnote > 0)
+ .sortBy(_.resultat.endnote).reverse.take(takeCnt)
if (relevantDisciplineValues.isEmpty) None else Some((disciplin, relevantDisciplineValues))
}
val limitedTeamwertungen = if (max > 0) teamwertungen else {
@@ -157,7 +165,7 @@ case class TeamRegelVereinGeraet(min: Int, max: Int, extraTeamsDef: String, grou
allRelevantWertungen.contains(w.athlet)
}
}
- List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perDisciplinCountingWertungen, perDisciplinWertungen))
+ List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perDisciplinCountingWertungen, perDisciplinWertungen, aggregateFun))
} else {
List.empty
}
@@ -166,27 +174,29 @@ case class TeamRegelVereinGeraet(min: Int, max: Int, extraTeamsDef: String, grou
override def teamsAllowed: Boolean = true
}
-case class TeamRegelVereinGesamt(min: Int, max: Int, extraTeamsDef: String, grouperDef: String) extends TeamRegel {
+case class TeamRegelVereinGesamt(min: Int, max: Int, extraTeamsDef: String, grouperDef: String, aggregateFun: TeamAggreateFun) extends TeamRegel {
private val extrateams = parseExtrateams(extraTeamsDef)
override def getExtrateams: List[String] = extrateams
private val grouperDefs = parseGrouperDefs(grouperDef)
override def getGrouperDefs: List[Set[String]] = grouperDefs
- override def toFormel: String = s"VereinGesamt$grouperDef($min/${if (max > 0) max else "*"}$extraTeamsDef)"
+ override def toFormel: String = s"VereinGesamt$grouperDef(${aggregateFun.toFormelPart}${if (min > 0) min else "*"}/${if (max > 0) max else "*"}$extraTeamsDef)"
- override def toRuleName: String = s"""Vereins-Team Rangliste $grouperDef (beste $min Gesamtwertungen${if (max > 0) s" aus $max" else ""})"""
+ override def toRuleName: String = s"""Vereins-Team Rangliste $grouperDef (${aggregateFun.toDescriptionPart} ${if (min == 0) "allen" else s"besten $min"}${if (max > 0) s" von max $max" else ""} Gesamtwertungen)"""
override def extractTeams(wertungen: Iterable[WertungView]): List[Team] = {
val extraTeams = extractExtraTeams(wertungen)
+ val hasNoExplicitTeams = !wertungen.exists(w => w.team != 0)
wertungen
- .filter(w => w.team != 0)
+ .filter(w => hasNoExplicitTeams || w.team != 0)
.toList
.groupBy(w => w.getTeamName(extraTeams))
.flatMap { team =>
val (teamname, teamwertungen) = team
val athletCount = teamwertungen.map(w => w.athlet.id).toSet.size
- if (athletCount >= min && (max == 0 || athletCount <= max)) {
+ if (((min == 0 && athletCount > 0) || athletCount >= min) && (max == 0 || athletCount <= max)) {
+ val takeCnt = if (min == 0) athletCount else min
val perAthletWertungen = teamwertungen
.groupBy(w => w.athlet)
.map{ case (athlet, wtgs) =>
@@ -194,7 +204,7 @@ case class TeamRegelVereinGesamt(min: Int, max: Int, extraTeamsDef: String, grou
(athlet, wtgs, wtgsum)
}
.toList
- .sortBy(_._3).reverse.take(min) // sortiert auf Gesamtresultat
+ .sortBy(_._3).reverse.take(takeCnt) // sortiert auf Gesamtresultat
.flatMap(_._2) // mit wertungen die Disziplin-Map aufbauen
.groupBy(w => w.wettkampfdisziplin.disziplin)
val limitedTeamwertungen = if (max > 0) teamwertungen else {
@@ -203,7 +213,7 @@ case class TeamRegelVereinGesamt(min: Int, max: Int, extraTeamsDef: String, grou
allRelevantWertungen.contains
}
}
- List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perAthletWertungen, perAthletWertungen))
+ List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perAthletWertungen, perAthletWertungen, aggregateFun))
} else {
List.empty
}
@@ -213,42 +223,44 @@ case class TeamRegelVereinGesamt(min: Int, max: Int, extraTeamsDef: String, grou
override def teamsAllowed: Boolean = true
}
-case class TeamRegelVerbandGeraet(min: Int, max: Int, extraTeamsDef: String, grouperDef: String) extends TeamRegel {
+case class TeamRegelVerbandGeraet(min: Int, max: Int, extraTeamsDef: String, grouperDef: String, aggregateFun: TeamAggreateFun) extends TeamRegel {
private val extrateams = parseExtrateams(extraTeamsDef)
override def getExtrateams: List[String] = extrateams
private val grouperDefs = parseGrouperDefs(grouperDef)
override def getGrouperDefs: List[Set[String]] = grouperDefs
- override def toFormel: String = s"VerbandGerät$grouperDef($min/${if (max > 0) max else "*"}$extraTeamsDef)"
+ override def toFormel: String = s"VerbandGerät$grouperDef(${aggregateFun.toFormelPart}${if (min > 0) min else "*"}/${if (max > 0) max else "*"}$extraTeamsDef)"
- override def toRuleName: String = s"""Verbands-Team Rangliste $grouperDef (beste $min Gerätewertungen${if (max > 0) s" aus $max" else ""})"""
+ override def toRuleName: String = s"""Verbands-Team Rangliste $grouperDef (${aggregateFun.toDescriptionPart} ${if (min == 0) "allen" else s"besten $min"}${if (max > 0) s" von max $max" else ""} Gerätewertungen)"""
override def extractTeams(wertungen: Iterable[WertungView]): List[Team] = {
val extraTeams = extractExtraTeams(wertungen)
+ val hasNoExplicitTeams = !wertungen.exists(w => w.team != 0)
wertungen
- .filter(w => w.team != 0)
+ .filter(w => hasNoExplicitTeams || w.team != 0)
.toList
.groupBy(w => w.getTeamName(extraTeams))
.flatMap { team =>
val (teamname, teamwertungen) = team
val athletCount = teamwertungen.map(w => w.athlet.id).toSet.size
- if (athletCount >= min && (max == 0 || athletCount <= max)) {
+ if (((min == 0 && athletCount > 0) || athletCount >= min) && (max == 0 || athletCount <= max)) {
+ val takeCnt = if (min == 0) athletCount else min
val perDisciplinWertungen: Map[Disziplin, List[WertungView]] = teamwertungen
.groupBy(w => w.wettkampfdisziplin.disziplin)
.flatMap { case (disciplin, wtgs) =>
val relevantDisciplineValues = wtgs
- .filter(_.resultat.endnote > 0)
+ //.filter(_.resultat.endnote > 0)
.groupBy(_.resultat.endnote).toList
- .sortBy(_._1).reverse.take(min)
+ .sortBy(_._1).reverse.take(takeCnt)
.flatMap(_._2)
if (relevantDisciplineValues.isEmpty) None else Some((disciplin, relevantDisciplineValues))
}
val perDisciplinCountingWertungen: Map[Disziplin, List[WertungView]] = perDisciplinWertungen
.flatMap { case (disciplin, wtgs) =>
val relevantDisciplineValues = wtgs
- .filter(_.resultat.endnote > 0)
- .sortBy(_.resultat.endnote).reverse.take(min)
+ //.filter(_.resultat.endnote > 0)
+ .sortBy(_.resultat.endnote).reverse.take(takeCnt)
if (relevantDisciplineValues.isEmpty) None else Some((disciplin, relevantDisciplineValues))
}
val limitedTeamwertungen = if (max > 0) teamwertungen else {
@@ -258,7 +270,7 @@ case class TeamRegelVerbandGeraet(min: Int, max: Int, extraTeamsDef: String, gro
allRelevantWertungen.contains(w.athlet)
}
}
- List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perDisciplinCountingWertungen, perDisciplinWertungen))
+ List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perDisciplinCountingWertungen, perDisciplinWertungen, aggregateFun))
} else {
List.empty
}
@@ -267,26 +279,28 @@ case class TeamRegelVerbandGeraet(min: Int, max: Int, extraTeamsDef: String, gro
override def teamsAllowed: Boolean = true
}
-case class TeamRegelVerbandGesamt(min: Int, max: Int, extraTeamsDef: String, grouperDef: String) extends TeamRegel {
+case class TeamRegelVerbandGesamt(min: Int, max: Int, extraTeamsDef: String, grouperDef: String, aggregateFun: TeamAggreateFun) extends TeamRegel {
private val extrateams = parseExtrateams(extraTeamsDef)
override def getExtrateams: List[String] = extrateams
private val grouperDefs = parseGrouperDefs(grouperDef)
override def getGrouperDefs: List[Set[String]] = grouperDefs
- override def toFormel: String = s"VerbandGesamt$grouperDef($min/${if (max > 0) max else "*"}$extraTeamsDef)"
- override def toRuleName: String = s"""Verbands-Team Rangliste $grouperDef (beste $min Gesamtwertungen${if (max > 0) s" aus $max" else ""})"""
+ override def toFormel: String = s"VerbandGesamt$grouperDef(${aggregateFun.toFormelPart}${if (min > 0) min else "*"}/${if (max > 0) max else "*"}$extraTeamsDef)"
+ override def toRuleName: String = s"""Verbands-Team Rangliste $grouperDef (${aggregateFun.toDescriptionPart} ${if (min == 0) "allen" else s"besten $min"}${if (max > 0) s" von max $max" else ""} Gesamtwertungen)"""
override def extractTeams(wertungen: Iterable[WertungView]): List[Team] = {
val extraTeams = extractExtraTeams(wertungen)
+ val hasNoExplicitTeams = !wertungen.exists(w => w.team != 0)
wertungen
- .filter(w => w.team != 0)
+ .filter(w => hasNoExplicitTeams || w.team != 0)
.toList
.groupBy(w => w.getTeamName(extraTeams))
.flatMap { team =>
val (teamname, teamwertungen) = team
val athletCount = teamwertungen.map(w => w.athlet.id).toSet.size
- if (athletCount >= min && (max == 0 || athletCount <= max)) {
+ if (((min == 0 && athletCount > 0) || athletCount >= min) && (max == 0 || athletCount <= max)) {
+ val takeCnt = if (min == 0) athletCount else min
val perAthletWertungen = teamwertungen
.groupBy(w => w.athlet)
.map{ case (athlet, wtgs) =>
@@ -294,7 +308,7 @@ case class TeamRegelVerbandGesamt(min: Int, max: Int, extraTeamsDef: String, gro
(athlet, wtgs, wtgsum)
}
.toList
- .sortBy(_._3).reverse.take(min) // sortiert auf Gesamtresultat
+ .sortBy(_._3).reverse.take(takeCnt) // sortiert auf Gesamtresultat
.flatMap(_._2) // mit wertungen die Disziplin-Map aufbauen
.groupBy(w => w.wettkampfdisziplin.disziplin)
val limitedTeamwertungen = if (max > 0) teamwertungen else {
@@ -303,7 +317,7 @@ case class TeamRegelVerbandGesamt(min: Int, max: Int, extraTeamsDef: String, gro
allRelevantWertungen.contains
}
}
- List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perAthletWertungen, perAthletWertungen))
+ List(Team(s"${teamname}", toRuleName, limitedTeamwertungen, perAthletWertungen, perAthletWertungen, aggregateFun))
} else {
List.empty
}
diff --git a/src/main/scala/ch/seidel/kutu/domain/package.scala b/src/main/scala/ch/seidel/kutu/domain/package.scala
index 17a2fe8d..2b2f026a 100644
--- a/src/main/scala/ch/seidel/kutu/domain/package.scala
+++ b/src/main/scala/ch/seidel/kutu/domain/package.scala
@@ -10,10 +10,11 @@ import java.net.URLEncoder
import java.nio.file.{Files, LinkOption, Path, StandardOpenOption}
import java.sql.{Date, Timestamp}
import java.text.{ParseException, SimpleDateFormat}
-import java.time.{LocalDate, LocalDateTime, LocalTime, Period, ZoneId}
+import java.time._
import java.util.UUID
import java.util.concurrent.TimeUnit
import scala.concurrent.duration.Duration
+import scala.math.BigDecimal.RoundingMode
package object domain {
implicit def dbl2Str(d: Double): String = f"${d}%2.3f"
@@ -93,7 +94,7 @@ package object domain {
Integer.parseInt(c)
true
} catch {
- case _:NumberFormatException => false
+ case _: NumberFormatException => false
}
}
@@ -198,6 +199,7 @@ package object domain {
val ep = easyprint
if (ep.matches(".*\\s,\\.;.*")) s""""$ep"""" else ep
}
+
def compare(o: DataObject): Int = easyprint.compare(o.easyprint)
}
@@ -226,12 +228,15 @@ package object domain {
case class CompoundGrouper(groupers: Seq[DataObject]) extends DataObject { //GenericGrouper(groupers.map(_.easyprint).mkString(","))
override def easyprint: String = groupers.map(_.easyprint).mkString(",")
}
+
case class GenericGrouper(name: String) extends DataObject {
override def easyprint: String = name
}
case class TurnerGeschlecht(geschlecht: String) extends DataObject {
override def easyprint = geschlecht.toLowerCase() match {
+ case "m,w" => "TuTi"
+ case "w,m" => "TuTi"
case "m" => "Turner"
case "w" => "Turnerinnen"
case "f" => "Turnerinnen"
@@ -347,7 +352,116 @@ package object domain {
def updatedWith(athlet: Athlet) = AthletView(athlet.id, athlet.js_id, athlet.geschlecht, athlet.name, athlet.vorname, athlet.gebdat, athlet.strasse, athlet.plz, athlet.ort, verein.map(v => v.copy(id = athlet.verein.getOrElse(0L))), athlet.activ)
}
- case class Team(name: String, rulename: String, wertungen: List[WertungView], countingWertungen: Map[Disziplin, List[WertungView]], relevantWertungen: Map[Disziplin, List[WertungView]]) extends DataObject {
+ object TeamAggreateFun {
+ def apply(text: String): TeamAggreateFun = {
+ if (text == null) Sum else text.toLowerCase() match {
+ case "avg/" => Avg
+ case "median/" => Med
+ case "min/" => Min
+ case "max/" => Max
+ case "devmin/" => DevMin
+ case "devmax/" => DevMax
+ case _ => Sum
+ }
+ }
+ }
+
+ sealed trait TeamAggreateFun {
+ def sum(xs: Iterable[Resultat]): Resultat = if (xs.nonEmpty) xs.reduce(_ + _) else Resultat(0, 0, 0)
+ def max(xs: Iterable[Resultat]): Resultat = if (xs.nonEmpty) xs.reduce(_.max(_)) else Resultat(0, 0, 0)
+ def min(xs: Iterable[Resultat]): Resultat = if (xs.nonEmpty) xs.reduce(_.min(_)) else Resultat(0, 0, 0)
+ def mean(xs: Iterable[Resultat]): Resultat = if (xs.nonEmpty) sum(xs) / xs.size else Resultat(0, 0, 0)
+ def median(xs: Iterable[Resultat]): Resultat = xs match {
+ case Nil => Resultat(0,0,0)
+ case x::Nil => x
+ case _ =>
+ val l = xs.toList.sortBy(_.endnote)
+ val i = Math.max(1, l.size / 2)
+ if (l.size % 2 == 0) {
+ (l(i-1) + l(i)) / 2
+ } else {
+ l(i)
+ }
+ }
+
+ def variance(xs: Iterable[Resultat]): Resultat = {
+ if (xs.nonEmpty) {
+ val avg = mean(xs)
+
+ mean(xs
+ .map(_ - avg)
+ .map(_.pow(2))
+ )
+ } else Resultat(0, 0, 0)
+ }
+
+ def stdDev(xs: Iterable[Resultat]): Resultat = xs match {
+ case Nil => Resultat(0,0,0)
+ case x::Nil => x
+ case _ => variance(xs).sqrt
+ }
+
+ def apply(results: Iterable[Resultat]): Resultat
+ def sortFactor = 1
+ def toFormelPart: String = ""
+
+ def toDescriptionPart: String
+ }
+
+ case object Sum extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = sum(results)
+
+ def toDescriptionPart: String = "Summe aus"
+ }
+
+ case object Avg extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = mean(results)
+
+ override def toFormelPart: String = "avg/"
+
+ def toDescriptionPart: String = "⌀ aus"
+ }
+
+ case object Med extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = median(results)
+
+ override def toFormelPart: String = "median/"
+
+ def toDescriptionPart: String = "Median aus"
+ }
+
+ case object Max extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = max(results)
+
+ override def toFormelPart: String = "max/"
+
+ def toDescriptionPart: String = "höchste aus"
+ }
+
+ case object Min extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = min(results)
+
+ override def toFormelPart: String = "min/"
+
+ def toDescriptionPart: String = "niedrigste aus"
+ }
+
+ case object DevMin extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = stdDev(results)
+ override def sortFactor = -1
+ override def toFormelPart: String = "devmin/"
+
+ def toDescriptionPart: String = "kleinste Abweichung aus"
+ }
+
+ case object DevMax extends TeamAggreateFun {
+ override def apply(results: Iterable[Resultat]): Resultat = stdDev(results)
+ override def toFormelPart: String = "devmax/"
+
+ def toDescriptionPart: String = "grösste Abweichung aus"
+ }
+
+ case class Team(name: String, rulename: String, wertungen: List[WertungView], countingWertungen: Map[Disziplin, List[WertungView]], relevantWertungen: Map[Disziplin, List[WertungView]], aggregateFun: TeamAggreateFun) extends DataObject {
val diszList: Map[Disziplin, Int] = countingWertungen.map { t =>
val disz = t._1
val ord = t._2.find(_.wettkampfdisziplin.disziplin == t._1).map(_.wettkampfdisziplin.ord).getOrElse(999)
@@ -355,18 +469,20 @@ package object domain {
}
val perDisciplinResults: Map[Disziplin, List[Resultat]] = countingWertungen
- .map{ case (disciplin, wtg) => (disciplin, wtg
- .map(w => if (w.showInScoreList) w.resultat else Resultat(0,0,0)))
+ .map { case (disciplin, wtg) => (disciplin, wtg
+ .map(w => if (w.showInScoreList) w.resultat else Resultat(0, 0, 0)))
}
- val perDisciplinSums = perDisciplinResults.map{ case (disciplin, results) => (disciplin, results.reduce(_+_)) }
- val sum = if (perDisciplinSums.values.nonEmpty) perDisciplinSums.values.reduce(_+_) else Resultat(0,0,0)
- val avg = if (perDisciplinSums.values.nonEmpty) sum / perDisciplinResults.keySet.size else Resultat(0,0,0)
+ //val perDisciplinSums = perDisciplinResults.map{ case (disciplin, results) => (disciplin, aggregateFun(results)) }
+ //val sum = aggregateFun(perDisciplinSums.values)
+ //val avg = Avg(perDisciplinSums.values)
val blockrows = wertungen.map(_.athlet).distinct.size
+
def isRelevantResult(disziplin: Disziplin, member: AthletView): Boolean = {
relevantWertungen(disziplin).find(_.athlet.equals(member)).exists(w => perDisciplinResults(disziplin).exists(r => r.endnote.equals(w.resultat.endnote)))
}
+
override def easyprint: String = "Team " + name
}
@@ -382,7 +498,7 @@ package object domain {
case _ => false
}
}
-
+
object Wertungsrichter {
def apply(): Wertungsrichter = Wertungsrichter(0, 0, "", "", "", None, "", "", "", None, activ = true)
}
@@ -446,13 +562,17 @@ package object domain {
case class SimpleDurchgang(id: Long, wettkampfId: Long, title: String, name: String, durchgangtype: DurchgangType, ordinal: Int, planStartOffset: Long, effectiveStartTime: Option[java.sql.Timestamp], effectiveEndTime: Option[java.sql.Timestamp]) extends DataObject {
override def easyprint = if (name.equals(title)) name else s"$title: $name"
+
def effectivePlanStart(wkDate: LocalDate): LocalDateTime = LocalDateTime.of(wkDate, LocalTime.MIDNIGHT).plusNanos(planStartOffset * 1000_000L)
}
case class Durchgang(id: Long, wettkampfId: Long, title: String, name: String, durchgangtype: DurchgangType, ordinal: Int, planStartOffset: Long, effectiveStartTime: Option[java.sql.Timestamp], effectiveEndTime: Option[java.sql.Timestamp], planEinturnen: Long, planGeraet: Long, planTotal: Long) extends DataObject {
override def easyprint = if (name.equals(title)) name else s"$title: $name"
+
def effectivePlanStart(wkDate: LocalDate): LocalDateTime = LocalDateTime.of(wkDate, LocalTime.MIDNIGHT).plusNanos(planStartOffset * 1000_000L)
- def effectivePlanFinish(wkDate: LocalDate): LocalDateTime = LocalDateTime.of(wkDate, LocalTime.MIDNIGHT).plusNanos((planStartOffset + (if (planStartOffset > 0) planTotal else 24000*3600)) * 1000_000L)
+
+ def effectivePlanFinish(wkDate: LocalDate): LocalDateTime = LocalDateTime.of(wkDate, LocalTime.MIDNIGHT).plusNanos((planStartOffset + (if (planStartOffset > 0) planTotal else 24000 * 3600)) * 1000_000L)
+
def toAggregator(other: Durchgang) = Durchgang(0, wettkampfId, title, title, durchgangtype, Math.min(ordinal, other.ordinal), Math.min(planStartOffset, planStartOffset), effectiveStartTime, effectiveEndTime, Math.max(planEinturnen, other.planEinturnen), Math.max(planGeraet, other.planGeraet), Math.max(planTotal, other.planTotal))
}
@@ -483,26 +603,27 @@ package object domain {
"Kür", "LK1", "LK2", "LK3", "LK4"
)
}
+
object Altersklasse {
// file:///C:/Users/Roland/Downloads/Turn10-2018_Allgemeine%20Bestimmungen.pdf
val akExpressionTurn10 = "AK7-18,AK24,AK30-100/5"
val altersklassenTurn10 = Seq(
- 6,7,8,9,10,11,12,13,14,15,16,17,18,24,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 24, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100
).map(i => ("AK", Seq(), i))
// see https://www.dtb.de/fileadmin/user_upload/dtb.de/Passwesen/Wettkampfordnung_DTB_2021.pdf
val akDTBExpression = "AK6,AK18,AK22,AK25"
val altersklassenDTB = Seq(
- 6,18,22,25
+ 6, 18, 22, 25
).map(i => ("AK", Seq(), i))
// see https://www.dtb.de/fileadmin/user_upload/dtb.de/TURNEN/Standards/PDFs/Rahmentrainingskonzeption-GTm_inklAnlagen_19.11.2020.pdf
val akDTBPflichtExpression = "AK8-9,AK11-19/2"
val altersklassenDTBPflicht = Seq(
- 7,8,9,11,13,15,17,19
+ 7, 8, 9, 11, 13, 15, 17, 19
).map(i => ("AK", Seq(), i))
val akDTBKuerExpression = "AK13-19/2"
val altersklassenDTBKuer = Seq(
- 12,13,15,17,19
+ 12, 13, 15, 17, 19
).map(i => ("AK", Seq(), i))
val predefinedAKs = Map(
@@ -513,6 +634,7 @@ package object domain {
, ("DTB Kür" -> akDTBKuerExpression)
, ("Individuell" -> "")
)
+
def apply(altersgrenzen: Seq[(String, Seq[String], Int)]): Seq[Altersklasse] = {
if (altersgrenzen.isEmpty) {
Seq.empty
@@ -548,7 +670,7 @@ package object domain {
val intpattern = "([\\D\\s]*)([0-9]+)".r
val qualifierPattern = "(.*)\\(([\\D\\s]+)\\)".r
- def bez(b: String): (String,Seq[String]) = if(b.nonEmpty) {
+ def bez(b: String): (String, Seq[String]) = if (b.nonEmpty) {
b match {
case qualifierPattern(bezeichnung, qualifiers) => (bezeichnung, qualifiers.split("\\+").toSeq)
case bezeichnung: String => (bezeichnung, Seq())
@@ -556,13 +678,13 @@ package object domain {
} else ("", Seq())
klassenDef.split(",")
- .flatMap{
+ .flatMap {
case rangeStepPattern(bezeichnung, von, bis, stepsize) => Range.inclusive(von, bis, stepsize).map(i => (bez(bezeichnung), i))
case rangepattern(bezeichnung, von, bis) => (str2Int(von) to str2Int(bis)).map(i => (bez(bezeichnung), i))
case intpattern(bezeichnung, von) => Seq((bez(bezeichnung), str2Int(von)))
case _ => Seq.empty
}.toList
- .foldLeft(Seq[(String, Seq[String], Int)]()){(acc, item) =>
+ .foldLeft(Seq[(String, Seq[String], Int)]()) { (acc, item) =>
if (item._1._1.nonEmpty) {
acc :+ (item._1._1, item._1._2, item._2)
} else if (acc.nonEmpty) {
@@ -573,6 +695,7 @@ package object domain {
}
.sortBy(item => (item._1, item._3))
}
+
def apply(klassenDef: String, fallbackBezeichnung: String = "Altersklasse"): Seq[Altersklasse] = {
apply(parseGrenzen(klassenDef, fallbackBezeichnung))
}
@@ -581,15 +704,19 @@ package object domain {
case class Altersklasse(bezeichnung: String, alterVon: Int, alterBis: Int, qualifiers: Seq[String]) extends DataObject {
val geschlechtQualifier = qualifiers.filter(q => Seq("M", "W").contains(q))
val programmQualifier = qualifiers.filter(q => !Seq("M", "W").contains(q))
+
def matchesAlter(alter: Int): Boolean =
((alterVon == 0 || alter >= alterVon) &&
(alterBis == 0 || alter <= alterBis))
+
def matchesGeschlecht(geschlecht: String): Boolean = {
geschlechtQualifier.isEmpty || geschlechtQualifier.contains(geschlecht)
}
+
def matchesProgramm(programm: ProgrammView): Boolean = {
programmQualifier.isEmpty || programm.programPath.exists(p => programmQualifier.contains(p.name))
}
+
override def easyprint: String = {
val q = if (qualifiers.nonEmpty) qualifiers.mkString("(", ",", ")") else ""
if (alterVon > 0 && alterBis > 0)
@@ -601,6 +728,7 @@ package object domain {
else
s"""$bezeichnung$q bis $alterBis"""
}
+
def easyprintShort: String = {
if (alterVon > 0 && alterBis > 0)
if (alterVon == alterBis)
@@ -624,13 +752,18 @@ package object domain {
case class Disziplin(id: Long, name: String) extends DataObject {
override def easyprint = name
+
def equalsOrPause(other: Disziplin) = math.abs(id) == math.abs(other.id)
+
def isPause: Boolean = id < 0
def asPause: Disziplin = Disziplin(id * -1, s"${name} Pause")
+
def harmless: Disziplin = Disziplin(math.abs(id), name)
+
def asNonPause: Disziplin = Disziplin(math.abs(id), name.replace(" Pause", ""))
- def normalizedOrdinal(dzl: List[Disziplin]) = dzl.indexOf(asNonPause)+1
+
+ def normalizedOrdinal(dzl: List[Disziplin]) = dzl.indexOf(asNonPause) + 1
}
trait Programm extends DataObject {
@@ -658,20 +791,20 @@ package object domain {
*
* Krits +===========================================+=========================================================
* aggregate ->|0 |1
- * +-------------------------------------------+---------------------------------------------------------
+ * +-------------------------------------------+---------------------------------------------------------
* riegenmode->|1 |2 / 3(+verein) |1 |2 / 3(+verein)
* Acts +===========================================+=========================================================
* Einteilung->| Sex,Pgm,Verein | Sex,Pgm,Jg(,Verein) | Sex,Pgm,Verein | Pgm,Sex,Jg(,Verein)
- * +--------------------+----------------------+----------------------+-----------------------------------
+ * +--------------------+----------------------+----------------------+-----------------------------------
* Teilnahme | 1/WK | 1/WK | <<=PgmCnt(Jg)/WK | 1/Pgm
- * +-------------------------------------------+----------------------------------------------------------
+ * +-------------------------------------------+----------------------------------------------------------
* Registration| 1/WK | 1/WK, Pgm/(Jg) | mind. 1, max 1/Pgm | 1/WK aut. Tn 1/Pgm
- * +-------------------------------------------+----------------------------------------------------------
+ * +-------------------------------------------+----------------------------------------------------------
* Beispiele | GeTu/KuTu/KuTuRi | Turn10® (BS/OS) | TG Allgäu (Pfl./Kür) | ATT (Kraft/Bewg)
- * +-------------------------------------------+----------------------------------------------------------
+ * +-------------------------------------------+----------------------------------------------------------
* Rangliste | Sex/Programm | Sex/Programm/Jg | Sex/Programm | Sex/Programm/Jg
- * | | Sex/Programm/AK | Sex/Programm/AK |
- * +===========================================+=========================================================
+ * | | Sex/Programm/AK | Sex/Programm/AK |
+ * +===========================================+=========================================================
*
*/
case class ProgrammRaw(id: Long, name: String, aggregate: Int, parentId: Long, ord: Int, alterVon: Int, alterBis: Int, uuid: String, riegenmode: Int) extends Programm
@@ -871,16 +1004,19 @@ package object domain {
// }
case class WettkampfView(id: Long, uuid: Option[String], datum: java.sql.Date, titel: String, programm: ProgrammView, auszeichnung: Int, auszeichnungendnote: scala.math.BigDecimal, notificationEMail: String, altersklassen: String, jahrgangsklassen: String, punktegleichstandsregel: String, rotation: String, teamrule: String) extends DataObject {
override def easyprint = f"$titel am $datum%td.$datum%tm.$datum%tY"
+
lazy val details: String = s"${programm.name}" +
s"${if (teamrule.nonEmpty && !teamrule.equals("Keine Teams")) ", " + teamrule else ""}" +
s"${if (altersklassen.nonEmpty) ", Altersklassen" else ""}" +
s"${if (jahrgangsklassen.nonEmpty) ", Jahrgangs Altersklassen" else ""}" +
""
+
def toWettkampf = Wettkampf(id, uuid, datum, titel, programm.id, auszeichnung, auszeichnungendnote, notificationEMail, Option(altersklassen), Option(jahrgangsklassen), Option(punktegleichstandsregel), Option(rotation), Option(teamrule))
}
case class WettkampfStats(uuid: String, wkid: Int, titel: String, finishAthletesCnt: Int, finishClubsCnt: Int, finishOnlineAthletesCnt: Int, finishOnlineClubsCnt: Int) extends DataObject {
}
+
case class WettkampfMetaData(uuid: String, wkid: Int, finishAthletesCnt: Int, finishClubsCnt: Int, finishOnlineAthletesCnt: Int, finishOnlineClubsCnt: Int,
finishDonationMail: Option[String], finishDonationAsked: Option[BigDecimal], finishDonationApproved: Option[BigDecimal]) extends DataObject {
}
@@ -934,19 +1070,33 @@ package object domain {
}
case class Resultat(noteD: scala.math.BigDecimal, noteE: scala.math.BigDecimal, endnote: scala.math.BigDecimal) extends DataObject {
- def +(r: Resultat) = Resultat(noteD + r.noteD, noteE + r.noteE, endnote + r.endnote)
- def +(r: BigDecimal) = Resultat(noteD + r, noteE + r, endnote + r)
+ def -(r: Resultat): Resultat = Resultat(noteD - r.noteD, noteE - r.noteE, endnote - r.endnote)
- def /(cnt: Int) = Resultat(noteD / cnt, noteE / cnt, endnote / cnt)
+ def +(r: Resultat): Resultat = Resultat(noteD + r.noteD, noteE + r.noteE, endnote + r.endnote)
- def *(cnt: Long) = Resultat(noteD * cnt, noteE * cnt, endnote * cnt)
- def *(cnt: BigDecimal) = Resultat(noteD * cnt, noteE * cnt, endnote * cnt)
+ def -(r: BigDecimal): Resultat = Resultat(noteD - r, noteE - r, endnote - r)
- lazy val formattedD = if (noteD > 0) f"${noteD}%4.2f" else ""
- lazy val formattedE = if (noteE > 0) f"${noteE}%4.2f" else ""
- lazy val formattedEnd = if (endnote > 0) f"${endnote}%6.2f" else ""
+ def +(r: BigDecimal): Resultat = Resultat(noteD + r, noteE + r, endnote + r)
- override def easyprint = f"${formattedD}%6s${formattedE}%6s${formattedEnd}%6s"
+ def /(cnt: Int): Resultat = Resultat(noteD / cnt, noteE / cnt, endnote / cnt)
+
+ def *(cnt: Long): Resultat = Resultat(noteD * cnt, noteE * cnt, endnote * cnt)
+
+ def *(cnt: BigDecimal): Resultat = Resultat(noteD * cnt, noteE * cnt, endnote * cnt)
+
+ def max(other: Resultat): Resultat = Resultat(noteD.max(other.noteD), noteE.max(other.noteE), endnote.max(other.endnote))
+ def min(other: Resultat): Resultat = Resultat(noteD.min(other.noteD), noteE.min(other.noteE), endnote.min(other.endnote))
+ def pow(exponent: Int): Resultat = Resultat(noteD.pow(exponent), noteE.pow(exponent), endnote.pow(exponent))
+ def sqrt: Resultat = Resultat(
+ BigDecimal.decimal(Math.sqrt(noteD.toDouble)).setScale(noteD.scale, RoundingMode.HALF_UP),
+ BigDecimal.decimal(Math.sqrt(noteE.toDouble)).setScale(noteE.scale, RoundingMode.HALF_UP),
+ BigDecimal.decimal(Math.sqrt(endnote.toDouble)).setScale(endnote.scale, RoundingMode.HALF_UP))
+
+ lazy val formattedD: String = if (noteD > 0) f"${noteD}%4.2f" else ""
+ lazy val formattedE: String = if (noteE > 0) f"${noteE}%4.2f" else ""
+ lazy val formattedEnd: String = if (endnote > 0) f"${endnote}%6.2f" else ""
+
+ override def easyprint: String = f"${formattedD}%6s${formattedE}%6s${formattedEnd}%6s"
}
case class Wertung(id: Long, athletId: Long, wettkampfdisziplinId: Long, wettkampfId: Long, wettkampfUUID: String, noteD: Option[scala.math.BigDecimal], noteE: Option[scala.math.BigDecimal], endnote: Option[scala.math.BigDecimal], riege: Option[String], riege2: Option[String], team: Option[Int]) extends DataObject {
@@ -1015,22 +1165,26 @@ package object domain {
trait ResultRow {
val athletId: Option[Long] = None
val sum: Resultat
+ lazy val avg = Avg(resultate.map(_.sum).filter(r => r.endnote > 0))
val rang: Resultat
val auszeichnung: Boolean
val resultate: IndexedSeq[LeafRow] = IndexedSeq()
val divider: Int = 1
}
+
/**
* Single Result of a row
- * @param title Discipline name
+ *
+ * @param title Discipline name
* @param sum
* @param rang
- * @param auszeichnung true, of best score in that discipline
+ * @param auszeichnung true, if best score in that discipline
*/
case class LeafRow(title: String, sum: Resultat, rang: Resultat, auszeichnung: Boolean) extends DataRow with ResultRow
/**
* Row of results per each discipline of one athlet/team
+ *
* @param athlet
* @param resultate
* @param sum
@@ -1050,7 +1204,9 @@ package object domain {
sealed trait NotenModus {
def getDifficultLabel: String = "D"
+
def getExecutionLabel: String = "E"
+
def selectableItems: Option[List[String]] = None
def validated(dnote: Double, enote: Double, wettkampfDisziplin: WettkampfdisziplinView): (Double, Double)
@@ -1279,6 +1435,7 @@ package object domain {
override val caption = s"Athlet/-In korrigieren: Von ${existing.extendedprint} zu ${expected.extendedprint}"
def isSexChange: Boolean = existing.geschlecht != expected.geschlecht
+
def isGebDatChange: Boolean = !existing.gebdat.equals(expected.gebdat)
def applyLocalChange: Athlet = existing.copy(
diff --git a/src/main/scala/ch/seidel/kutu/renderer/WettkampfOverviewToHtmlRenderer.scala b/src/main/scala/ch/seidel/kutu/renderer/WettkampfOverviewToHtmlRenderer.scala
index 178ff990..0e97730f 100644
--- a/src/main/scala/ch/seidel/kutu/renderer/WettkampfOverviewToHtmlRenderer.scala
+++ b/src/main/scala/ch/seidel/kutu/renderer/WettkampfOverviewToHtmlRenderer.scala
@@ -1,7 +1,7 @@
package ch.seidel.kutu.renderer
import ch.seidel.kutu.Config
-import ch.seidel.kutu.Config.{homedir, remoteBaseUrl, remoteHostOrigin}
+import ch.seidel.kutu.Config.{getRemoteHosts, homedir, remoteBaseUrl, remoteHostOrigin}
import ch.seidel.kutu.KuTuApp.enc
import ch.seidel.kutu.domain._
import ch.seidel.kutu.renderer.PrintUtil._
@@ -313,6 +313,11 @@ trait WettkampfOverviewToHtmlRenderer {
} else {
List((("", "", ""), wertungen))
}
+ // group
+ // List[(pgm, sex), teams]
+ val teamGroups = teams.extractTeamsWithDefaultGouping(wertungen)
+ val allAthletesInTeams = teamGroups.flatMap(_._3.flatMap(_.wertungen.map(_.athlet))).distinct
+
val groupedTeams = groupedWertungen.toList.flatMap {
case (group, wertungen) => teams.extractTeamsWithDefaultGouping(wertungen).groupBy(x => (x._1, x._2)).flatMap {
case (grouper, teamgroup) => teamgroup.flatMap(_._3).groupBy(_.rulename).map {
@@ -320,7 +325,6 @@ trait WettkampfOverviewToHtmlRenderer {
val (pgm, ak, sex) = group
val pgmValue = if(grouper._1.contains(pgm)) grouper._1 else pgm + grouper._1
val sexValue = if(grouper._2.contains(sex)) grouper._2 else sex + grouper._2
- //(rulename, pgmValue, ak, sexValue, teams.size, teams.map(team => s"${team.name} (${team.blockrows})").sorted.mkString(", "))
(rulename, pgmValue, ak, sexValue, teams.size, teams.map(team =>
s"""${team.name} (${team.blockrows})
|
@@ -334,26 +338,38 @@ trait WettkampfOverviewToHtmlRenderer {
case (rulename, list) =>
(rulename, list.sortBy(t => s"${t._1}${t._2}${t._3}"))
}
-
+ val hasNoExplicitTeamAssignements = !wertungen.exists(_.team > 0)
+ val unassignedAtletesTeam = Team("Ohne gültige Zuweisung", "", wertungen.filter(w => hasNoExplicitTeamAssignements || w.team > 0).filter(a => !allAthletesInTeams.contains(a.athlet)), Map.empty, Map.empty, Sum)
val teamsSections = groupedTeams.keySet.toList.sorted.map {
case name =>
val groupMerges = teamsIndex(name).getGrouperDefs.filter(d => d.exists(_.trim.nonEmpty))
+ val mergeRules = if (groupMerges.nonEmpty) {
+ s"""
+ |Explizite Gruppenzusammenfassungen:
+ |${teamsIndex(name).getGrouperDefs.filter(d => d.exists(_.trim.nonEmpty)).map(d => d.mkString("- ", "+", "
")).mkString("
")}
+ |"""
+ } else ""
s"""$name
- |${if (groupMerges.nonEmpty) s"""
- |Explizite Gruppenzusammenfassungen:
- |${teamsIndex(name).getGrouperDefs.filter(d => d.exists(_.trim.nonEmpty)).map(d => d.mkString("- ", "+", "
")).mkString("
")}
- |""" else ""}
- |
+ |$mergeRules
|
|
| Prog./Kat. | AK | Geschlecht | Anzahl Teams | Teams |
- |
- |${
- groupedTeams(name).map {
- case (_, pgm: String, ak: String, sex: String, teamssize: Int, teams: String) =>
- s"$pgm | $ak | $sex | $teamssize | ${teams.replace(",", " ")} |
"
- }.mkString
- }
+ |
+ |
+ ${ groupedTeams(name).map {
+ case (_, pgm: String, ak: String, sex: String, teamssize: Int, teams: String) =>
+ s"$pgm | $ak | $sex | $teamssize | ${teams.replace(",", " ")} |
"
+ }.mkString
+ }
+ ${if (unassignedAtletesTeam.wertungen.nonEmpty) {
+ val uat = s"""- ${unassignedAtletesTeam.name} (${unassignedAtletesTeam.blockrows})
+ |
+ | ${unassignedAtletesTeam.wertungen.map(w => (w.athlet, w.wettkampfdisziplin.programm.name)).distinct.map(a => s"- ${a._1.easyprint} (${a._2})
").mkString("")}
+ |
+ |
+ |""".stripMargin
+ s"Leere (nicht einer Regel entsprechenden) Zuordnungen | | | 1 | $uat |
"
+ } else ""}
|
|""".stripMargin
}.mkString("\n")
diff --git a/src/main/scala/ch/seidel/kutu/view/DefaultRanglisteTab.scala b/src/main/scala/ch/seidel/kutu/view/DefaultRanglisteTab.scala
index 57c96f0e..9ecf2976 100644
--- a/src/main/scala/ch/seidel/kutu/view/DefaultRanglisteTab.scala
+++ b/src/main/scala/ch/seidel/kutu/view/DefaultRanglisteTab.scala
@@ -88,7 +88,11 @@ abstract class DefaultRanglisteTab(wettkampfmode: BooleanProperty, override val
def print(printer: Printer): Unit = {
PrintUtil.printWebContent(webView.engine, printer, PageOrientation.Portrait)
}
-
+
+ def resetFilterPresets(combos: Seq[ComboBox[FilterBy]], scorelistKind: ScoreListKind): Unit = {
+
+ }
+
def populate(groupers: List[FilterBy]): Seq[ComboBox[FilterBy]] = {
val gr1Model = ObservableBuffer.from(groupers)
val kindModel = ObservableBuffer.from(Seq[ScoreListKind](Einzelrangliste, Teamrangliste, Kombirangliste))
@@ -312,8 +316,11 @@ abstract class DefaultRanglisteTab(wettkampfmode: BooleanProperty, override val
refreshRangliste(buildGrouper)
}
cbKind.onAction = _ => {
- if(!restoring)
+ if(!restoring) {
+ restoring = true
+ resetFilterPresets(combs, cbKind.value.value)
refreshRangliste(buildGrouper)
+ }
}
val btnPrint = PrintUtil.btnPrintFuture(text.value, getSaveAsFilenameDefault, true,
diff --git a/src/main/scala/ch/seidel/kutu/view/RanglisteTab.scala b/src/main/scala/ch/seidel/kutu/view/RanglisteTab.scala
index 7690f764..e422d2b4 100644
--- a/src/main/scala/ch/seidel/kutu/view/RanglisteTab.scala
+++ b/src/main/scala/ch/seidel/kutu/view/RanglisteTab.scala
@@ -6,14 +6,14 @@ import ch.seidel.kutu.Config._
import ch.seidel.kutu.ConnectionStates
import ch.seidel.kutu.KuTuApp.handleAction
import ch.seidel.kutu.data._
-import ch.seidel.kutu.domain.{Altersklasse, Durchgang, KutuService, TeamRegel, WertungView, WettkampfView, encodeFileName, isNumeric}
+import ch.seidel.kutu.domain.{Altersklasse, Durchgang, KutuService, TeamRegel, WertungView, WettkampfView, encodeFileName}
import ch.seidel.kutu.renderer.PrintUtil.FilenameDefault
import scalafx.Includes.when
import scalafx.beans.binding.Bindings
import scalafx.beans.property.BooleanProperty
import scalafx.event.ActionEvent
import scalafx.scene.Node
-import scalafx.scene.control.{Button, Label, TextField}
+import scalafx.scene.control.{Button, ComboBox, Label, TextField}
import scalafx.scene.layout.{BorderPane, Priority, VBox}
import scala.concurrent.Await
@@ -221,27 +221,38 @@ class RanglisteTab(wettkampfmode: BooleanProperty, wettkampf: WettkampfView, ove
btnPublikationFreigeben)
}
+ override def resetFilterPresets(combos: Seq[ComboBox[FilterBy]], scoreListKind: ScoreListKind): Unit = {
+ val team = groupers.find(p => p.isInstanceOf[ByTeamRule] && p.groupname.startsWith("Wettkampf"))
+ scoreListKind match {
+ case Teamrangliste if team.nonEmpty =>
+ combos(1).selectionModel.value.select(team.get)
+ combos(2).selectionModel.value.clearSelection()
+ combos(3).selectionModel.value.clearSelection()
+
+ case _ =>
+ val akg = groupers.find(p => p.isInstanceOf[ByAltersklasse] && p.groupname.startsWith("Wettkampf"))
+ val jakg = groupers.find(p => p.isInstanceOf[ByJahrgangsAltersklasse] && p.groupname.startsWith("Wettkampf"))
+ if (akg.nonEmpty) {
+ combos(1).selectionModel.value.select(ByProgramm(programmText))
+ combos(2).selectionModel.value.select(akg.get)
+ combos(3).selectionModel.value.select(ByGeschlecht())
+ } else if (jakg.nonEmpty) {
+ combos(1).selectionModel.value.select(ByProgramm(programmText))
+ combos(2).selectionModel.value.select(jakg.get)
+ combos(3).selectionModel.value.select(ByGeschlecht())
+ } else {
+ combos(1).selectionModel.value.select(ByProgramm(programmText))
+ combos(2).selectionModel.value.select(ByGeschlecht())
+ }
+ }
+ }
override def isPopulated = {
val combos = populate(groupers)
- val akg = groupers.find(p => p.isInstanceOf[ByAltersklasse] && p.groupname.startsWith("Wettkampf"))
- val jakg = groupers.find(p => p.isInstanceOf[ByJahrgangsAltersklasse] && p.groupname.startsWith("Wettkampf"))
- val team = groupers.find(p => p.isInstanceOf[ByTeamRule] && p.groupname.startsWith("Wettkampf"))
- if (akg.nonEmpty) {
- combos(1).selectionModel.value.select(ByProgramm(programmText))
- combos(2).selectionModel.value.select(akg.get)
- combos(3).selectionModel.value.select(ByGeschlecht())
- } else if (jakg.nonEmpty) {
- combos(1).selectionModel.value.select(ByProgramm(programmText))
- combos(2).selectionModel.value.select(jakg.get)
- combos(3).selectionModel.value.select(ByGeschlecht())
- } else if (team.nonEmpty) {
- combos(1).selectionModel.value.select(team.get)
- } else {
- combos(1).selectionModel.value.select(ByProgramm(programmText))
- combos(2).selectionModel.value.select(ByGeschlecht())
- }
+ val team = groupers.find(p => p.isInstanceOf[ByTeamRule] && p.groupname.startsWith("Wettkampf"))
+ val kind: ScoreListKind = if (getData.exists(_.team > 0) || team.nonEmpty) Teamrangliste else Einzelrangliste
+ resetFilterPresets(combos, kind)
true
}
diff --git a/src/test/scala/ch/seidel/kutu/domain/PackageSpec.scala b/src/test/scala/ch/seidel/kutu/domain/PackageSpec.scala
index 86a4e572..94ed9de1 100644
--- a/src/test/scala/ch/seidel/kutu/domain/PackageSpec.scala
+++ b/src/test/scala/ch/seidel/kutu/domain/PackageSpec.scala
@@ -3,6 +3,7 @@ package ch.seidel.kutu.domain
import ch.seidel.kutu.base.KuTuBaseSpec
import java.time.LocalDate
+import scala.math.BigDecimal.RoundingMode
class PackageSpec extends KuTuBaseSpec {
"GeTuWettkampf" should {
@@ -118,4 +119,115 @@ class PackageSpec extends KuTuBaseSpec {
assert(ar.matchesAthlet().==(true))
}
}
+ "TeamAggreateFun avg" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.08),
+ Resultat(1, 8.00, 0.12),
+ Resultat(2, 8.50, 0.06),
+ Resultat(3, 9.50, 0.23)
+ )
+ val results2 = List(
+ Resultat(1, 8.00, 0.10),
+ Resultat(1, 8.20, 0.12),
+ Resultat(2, 8.50, 0.15),
+ Resultat(2, 9.10, 0.20)
+ )
+ assert(TeamAggreateFun("avg/")(results1).endnote.<(TeamAggreateFun("avg/")(results2).endnote))
+ assert(TeamAggreateFun("avg/")(results1).==(Resultat(1.5, 8.25, 0.1225)))
+ assert(TeamAggreateFun("avg/")(results2).==(Resultat(1.5, 8.45, 0.1425)))
+ }
+ "TeamAggreateFun median even" in {
+ val results1 = List(
+ Resultat(0, 7.30, 0.06),
+ Resultat(1, 8.00, 0.08),
+ Resultat(2, 8.50, 0.12),
+ Resultat(3, 9.50, 0.23)
+ )
+ assert(TeamAggreateFun("median/")(results1).==(Resultat(1.5, 8.25, 0.10)))
+ }
+ "TeamAggreateFun median odd" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.02),
+ Resultat(0, 7.30, 0.06),
+ Resultat(1, 8.00, 0.08),
+ Resultat(2, 8.50, 0.12),
+ Resultat(3, 9.50, 0.23)
+ )
+ assert(TeamAggreateFun("median/")(results1).==(Resultat(1, 8.00, 0.08)))
+ }
+ "TeamAggreateFun min" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.08),
+ Resultat(1, 8.00, 0.12),
+ Resultat(2, 8.50, 0.06),
+ Resultat(3, 9.50, 0.23)
+ )
+ val results2 = List(
+ Resultat(1, 8.00, 0.10),
+ Resultat(1, 8.20, 0.12),
+ Resultat(2, 8.50, 0.15),
+ Resultat(2, 9.10, 0.20)
+ )
+ assert(TeamAggreateFun("min/")(results1).endnote.<(TeamAggreateFun("min/")(results2).endnote))
+ assert(TeamAggreateFun("min/")(results1).==(Resultat(0, 7.00, 0.06)))
+ assert(TeamAggreateFun("min/")(results2).==(Resultat(1, 8.00, 0.10)))
+ }
+ "TeamAggreateFun max" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.08),
+ Resultat(1, 8.00, 0.12),
+ Resultat(2, 8.50, 0.06),
+ Resultat(3, 9.50, 0.23)
+ )
+ val results2 = List(
+ Resultat(1, 8.00, 0.10),
+ Resultat(1, 8.20, 0.12),
+ Resultat(2, 8.50, 0.15),
+ Resultat(2, 9.10, 0.20)
+ )
+ assert(TeamAggreateFun("max/")(results1).endnote.>(TeamAggreateFun("max/")(results2).endnote))
+ assert(TeamAggreateFun("max/")(results1).==(Resultat(3, 9.50, 0.23)))
+ assert(TeamAggreateFun("max/")(results2).==(Resultat(2, 9.10, 0.20)))
+ }
+ "TeamAggreateFun devmin" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.08),
+ Resultat(1, 8.00, 0.12),
+ Resultat(2, 8.50, 0.06),
+ Resultat(3, 9.50, 0.23)
+ )
+ val results2 = List(
+ Resultat(1, 8.00, 0.10),
+ Resultat(1, 8.20, 0.12),
+ Resultat(2, 8.50, 0.15),
+ Resultat(2, 9.10, 0.20)
+ )
+ assert(TeamAggreateFun("devmin/")(results1).endnote.>(TeamAggreateFun("devmin/")(results2).endnote))
+ assert(TeamAggreateFun("devmin/")(results1).==(Resultat(1.12, 0.9014, 0.06571720)))
+ assert(TeamAggreateFun("devmin/")(results2).==(Resultat(0.50, 0.4153, 0.03766630)))
+ }
+ "TeamAggreateFun devmin2" in {
+ val results1 = List(
+ Resultat(0, 7.00, 0.10),
+ Resultat(1, 8.00, 0.22),
+ Resultat(2, 8.50, 0.21),
+ Resultat(3, 9.50, 0.07)
+ )
+ val results2 = List(
+ Resultat(1, 8.00, 0.47),
+ Resultat(1, 8.25, 0.42),
+ Resultat(2, 8.10, 0.31),
+ Resultat(2, 7.90, 0.26),
+ Resultat(2, 7.80, 0.76),
+ Resultat(2, 8.30, 0.90),
+ Resultat(1, 8.10, 0.47),
+ Resultat(1, 8.60, 0.42),
+ Resultat(2, 8.45, 0.31),
+ Resultat(2, 8.10, 0.26),
+ Resultat(2, 8.10, 0.76),
+ Resultat(2, 8.25, 0.90)
+ )
+ assert(TeamAggreateFun("devmin/")(results1).==(Resultat(1.12, 0.9014, 0.06595)))
+ assert(TeamAggreateFun("devmin/")(results2).noteE.setScale(2, RoundingMode.HALF_DOWN).==(Resultat(0, 0.21, 0).noteE))
+ }
}
\ No newline at end of file
diff --git a/src/test/scala/ch/seidel/kutu/domain/TeamRegelTest.scala b/src/test/scala/ch/seidel/kutu/domain/TeamRegelTest.scala
index 213c7a38..fcca2ab3 100644
--- a/src/test/scala/ch/seidel/kutu/domain/TeamRegelTest.scala
+++ b/src/test/scala/ch/seidel/kutu/domain/TeamRegelTest.scala
@@ -51,4 +51,9 @@ class TeamRegelTest extends AnyWordSpec with Matchers {
assert( regel.toFormel == "VereinGerät[K5+K6+K7/KH+KD](3/*)" )
}
+ "VereinGerät(avg/2/*)" in {
+ val regel = TeamRegel("VereinGerät[K5+K6+K7/KH+KD](avg/2/*)")
+ assert( regel.teamsAllowed == true )
+ assert( regel.toFormel == "VereinGerät[K5+K6+K7/KH+KD](avg/2/*)" )
+ }
}
diff --git a/src/test/scala/ch/seidel/kutu/load/competition/SimulationBottmingenD1.scala b/src/test/scala/ch/seidel/kutu/load/competition/SimulationBottmingenD1.scala
index b3263cf2..f1329cee 100644
--- a/src/test/scala/ch/seidel/kutu/load/competition/SimulationBottmingenD1.scala
+++ b/src/test/scala/ch/seidel/kutu/load/competition/SimulationBottmingenD1.scala
@@ -39,17 +39,51 @@ class SimulationBottmingenD1 extends Simulation {
var durchgangListIdx = Map[String, Int]()
var geraetListIdx = Map[String, Int]()
+
val loadAndSaveDurchgaenge = http("get durchgaenge")
.get(s"/api/durchgang/$competition")
.check(
jsonPath("$").exists,
- jsonPath("$").ofType[Seq[Any]].find.saveAs("durchgaenge"))
+ jsonPath("$[*]").findAll.saveAs("durchgaenge"))
val loadAndSaveGeraete = http("get geraete")
.get(s"/api/durchgang/$competition/geraete")
.check(
jsonPath("$").exists,
- jsonPath("$").ofType[Seq[Any]].find.saveAs("geraete"))
+ jsonPath("$[*]").ofType[Map[String,Any]].findAll.saveAs("geraete"))
+
+ val loadAndSaveSteps = http("get steps")
+ .get("/api/durchgang/" + competition + "/#{durchgang}/#{geraet}")
+ .check(
+ status.is(200),
+ jsonPath("$").exists,
+ jsonPath("$[*]").findAll.saveAs("steps"))
+
+ val connectWSUserToDurchgang = ws("openSocketDG")
+ .wsName("#{sessionDgUser}")
+ .connect(s"/api/durchgang/$competition/#{durchgang}/ws?clientid=#{sessionUserId}")
+ .onConnected(
+ exec(ws("sendMessage").wsName("#{sessionDgUser}")
+ .sendText("keepAlive")
+ .await(5 seconds)(ws.checkTextMessage("check1")
+ .check(regex("Connection established(.*)").saveAs("wsgreetingmessage"))
+ .silent)
+ )
+ )
+ val closeWSUserFromDurchgang = ws("closeConnectionDG").wsName("#{sessionDgUser}").close
+
+ val connectWSUserToAll = ws("openSocketAll")
+ .wsName("#{sessionDgUser}-all")
+ .connect(s"/api/durchgang/$competition/all/ws?clientid=#{sessionUserId}")
+ .onConnected(
+ exec(ws("sendMessage").wsName("#{sessionDgUser}-all")
+ .sendText("keepAlive")
+ .await(5 seconds)(ws.checkTextMessage("check1")
+ .check(regex("Connection established(.*)").saveAs("wsgreetingmessage"))
+ .silent)
+ )
+ )
+ val closeWSUserFromAll = ws("closeConnectionAll").wsName("#{sessionDgUser}-all").close
def chooseDurchgang(session: Session) = {
val list = session("durchgaenge").as[Vector[Any]]
@@ -62,48 +96,14 @@ class SimulationBottmingenD1 extends Simulation {
}
def chooseGeraet(session: Session) = {
- val list = session("geraete").as[Vector[Map[String, Int]]].toList
+ val list = session("geraete").as[Vector[Map[String, Any]]].toList
val listIdx = geraetListIdx.getOrElse(session("durchgang").as[String], 0)
- val randomEntry = list(listIdx)("id")
+ val randomEntry: Int = list(listIdx)("id").asInstanceOf[Int]
geraetListIdx = geraetListIdx.updated(session("durchgang").as[String], if (listIdx < list.size - 1) listIdx + 1 else 0)
println(s"random choosed geraet: $randomEntry")
session.set("geraet", randomEntry.toString)
}
- val connectWSUserToDurchgang = ws("openSocketDG")
- .wsName("${sessionDgUser}")
- .connect("/api/durchgang/" + competition + "/${durchgang}/ws?clientid=${sessionUserId}")
- .onConnected(
- exec(ws("sendMessage").wsName("${sessionDgUser}")
- .sendText("keepAlive")
- .await(5 seconds)(ws.checkTextMessage("check1")
- .check(regex("Connection established(.*)").saveAs("wsgreetingmessage"))
- .silent)
- )
- )
- val connectWSUserToAll = ws("openSocketAll")
- .wsName("${sessionDgUser}-all")
- .connect("/api/durchgang/" + competition + "/all/ws?clientid=${sessionUserId}")
- .onConnected(
- exec(ws("sendMessage").wsName("${sessionDgUser}-all")
- .sendText("keepAlive")
- .await(5 seconds)(ws.checkTextMessage("check1")
- .check(regex("Connection established(.*)").saveAs("wsgreetingmessage"))
- .silent)
- )
- )
-
- val closeWSUserFromDurchgang = ws("closeConnectionDG").wsName("${sessionDgUser}").close
- val closeWSUserFromAll = ws("closeConnectionAll").wsName("${sessionDgUser}-all").close
-
- val getSteps = {
- exec(http("get steps")
- .get("/api/durchgang/" + competition + "/${durchgang}/${geraet}")
- .check(
- jsonPath("$").exists,
- jsonPath("$").ofType[Seq[Any]].find.saveAs("steps")))
- }
-
def chooseDurchgangWSConnection(session: Session) = {
val dg = session("durchgang").as[String]
val sessionDgUser = s"${dg}-${session.userId}"
@@ -122,13 +122,14 @@ class SimulationBottmingenD1 extends Simulation {
val diveToWertungen = commonDGInitializer
.exec(connectWSUserToAll)
- .exec(getSteps)
- .foreach("${steps}", "step") {
+ .exec(loadAndSaveSteps)
+ .foreach("#{steps}", "step") {
exec(http("get Wertungen")
- .get(s"/api/durchgang/$competition/${"${durchgang}"}/${"${geraet}"}/${"${step}"}")
+ .get(s"/api/durchgang/$competition/#{durchgang}/#{geraet}/#{step}")
.check(
+ status.is(200),
jsonPath("$").exists,
- jsonPath("$").ofType[Seq[Any]].find))
+ jsonPath("$[*]").count.gte(1)))
//.pause(5 minutes, 10 minutes)
.rendezVous(10)
}
@@ -138,32 +139,38 @@ class SimulationBottmingenD1 extends Simulation {
val collectWertungen = commonDGInitializer
.exec(http(s"start durchgang")
.post(s"/api/competition/$competition/start")
- .body(StringBody(s"""{"type":"StartDurchgangStation","wettkampfUUID":"$competition","durchgang":"${"${durchgangOriginal}"}"}""")))
+ .body(StringBody(s"""{"type":"StartDurchgangStation","wettkampfUUID":"$competition","durchgang":"#{durchgangOriginal}"}"""))
+ .check(
+ status.is(200)
+ ))
.exec(connectWSUserToDurchgang)
- .exec(getSteps)
- .foreach("${steps}", "step") {
+ .exec(loadAndSaveSteps)
+ .foreach("#{steps}", "step") {
exec(http("get Wertungen")
- .get(s"/api/durchgang/$competition/${"${durchgang}"}/${"${geraet}"}/${"${step}"}")
+ .get(s"/api/durchgang/$competition/#{durchgang}/#{geraet}/#{step}")
.check(
+ status.is(200),
jsonPath("$").exists,
jsonPath("$").ofType[Seq[Any]].find.saveAs("wertungen")))
.pause(1 minutes, 3 minutes)
- .foreach("${wertungen}", "wertung") {
+ .foreach("#{wertungen}", "w") {
exec(http("save wertung")
- .put(s"/api/durchgang/$competition/${"${durchgang}"}/${"${geraet}"}/${"${step}"}")
- .body(StringBody("${wertung.wertung.jsonStringify()}"))
+ .put(s"/api/durchgang/$competition/#{durchgang}/#{geraet}/#{step}")
+ .body(StringBody("#{w.wertung.jsonStringify()}"))
.check(status.is(200))
)
.pause(5 seconds, 20 seconds)
// .exec(http("finish durchgangstation")
- // .post(s"/api/durchgang/$competition/${"${durchgang}"}/finish")
- // .body(StringBody(s"""{"type":"FinishDurchgangStation","wettkampfUUID":"$competition","durchgang:"${"${durchgangOriginal}"}","geraet":${"${geraet}"},"step":${"${step}"}}""")))
+ // .post(s"/api/durchgang/$competition/#{durchgang}/finish")
+ // .body(StringBody(s"""{"type":"FinishDurchgangStation","wettkampfUUID":"$competition","durchgang:"${"${durchgangOriginal}"}","geraet":#{geraet},"step":#{stept}}""")))
}
.rendezVous(12)
.exec(http("finish step")
.post(s"/api/competition/$competition/finishedStep")
.body(StringBody(s"""{"type":"FinishDurchgangStep","wettkampfUUID":"$competition"}"""))
- .silent
+ .silent.check(
+ status.is(200)
+ )
)
}
.pause(20 seconds, 30 seconds)
@@ -178,16 +185,26 @@ class SimulationBottmingenD1 extends Simulation {
http("get font roboto-regular").get("/assets/fonts/roboto-regular.woff2"),
http("get font roboto-medium").get("/assets/fonts/roboto-medium.woff2"),
http("get font roboto-bold").get("/assets/fonts/roboto-bold.woff2"),
- http("get competitions").get("/api/competition"),
+ http("get competitions").get("/api/competition").check(
+ status.is(200)
+ ),
http("check jwt-token expired")
.options("/api/isTokenExpired")))
- .exec(http("startlist").get(s"/api/report/$competition/startlist"))
+ .exec(http("startlist").get(s"/api/report/$competition/startlist").check(
+ status.is(200)
+ ))
.exec(BrowseResults.loadAndSaveDurchgaenge)
- .exec(http("startlist").get(s"/api/report/$competition/startlist"))
+ .exec(http("startlist").get(s"/api/report/$competition/startlist").check(
+ status.is(200)
+ ))
.exec(BrowseResults.loadAndSaveGeraete)
- .exec(http("startlist").get(s"/api/report/$competition/startlist"))
+ .exec(http("startlist").get(s"/api/report/$competition/startlist").check(
+ status.is(200)
+ ))
.exec(BrowseResults.diveToWertungen)
- .exec(http("startlist").get(s"/api/report/$competition/startlist"))
+ .exec(http("startlist").get(s"/api/report/$competition/startlist").check(
+ status.is(200)
+ ))
.exec(BrowseResults.closeWSUserFromAll)