Change data structure for meteo adding AggregateKey and AggregateValue

This commit is contained in:
Guntis Smaukstelis
2023-04-26 16:37:11 +03:00
parent 3cd223dddd
commit 1bf41973a7
7 changed files with 161 additions and 62 deletions
+3 -1
View File
@@ -18,8 +18,10 @@ libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.2.9",
"com.typesafe" % "config" % "1.4.1",
"org.scala-lang" % "scala-reflect" % "2.13.10",
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-generic-extras" % circeVersion,
"io.circe" %% "circe-optics" % circeVersion,
+112
View File
@@ -0,0 +1,112 @@
package parse
import io.circe._
import io.circe.syntax.EncoderOps
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
sealed trait AggregateValue
object AggregateValue {
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHmm")
case class tempMax(value: Double) extends AggregateValue
case class tempMaxList(value: Map[LocalDateTime, Double]) extends AggregateValue
case class tempMin(value: Double) extends AggregateValue
case class tempMinList(value: Map[LocalDateTime, Double]) extends AggregateValue
case class tempAvg(value: Double) extends AggregateValue
case class tempAvgList(value: Map[LocalDateTime, Double]) extends AggregateValue
case class precipitationSum(value: Double) extends AggregateValue
case class precipitationList(value: Map[LocalDateTime, Double]) extends AggregateValue
implicit val encodeAggregateValue: Encoder[AggregateValue] = Encoder.instance {
case tm: tempMax => Json.fromDoubleOrNull(tm.value)
case tml: tempMaxList => tml.value.map { case (dateTime, value) =>
Json.obj(dateFormatter.format(dateTime) -> Json.fromDoubleOrNull(value))
}.toList.asJson
case tmin: tempMin => Json.fromDoubleOrNull(tmin.value)
case tml: tempMinList => tml.value.map { case (dateTime, value) =>
Json.obj(dateFormatter.format(dateTime) -> Json.fromDoubleOrNull(value))
}.toList.asJson
case tavg: tempAvg => Json.fromDoubleOrNull(tavg.value)
case tml: tempAvgList => tml.value.map { case (dateTime, value) =>
Json.obj(dateFormatter.format(dateTime) -> Json.fromDoubleOrNull(value))
}.toList.asJson
case psum: precipitationSum => Json.fromDoubleOrNull(psum.value)
case plist: precipitationList => plist.value.map { case (dateTime, value) =>
Json.obj(dateFormatter.format(dateTime) -> Json.fromDoubleOrNull(value))
}.toList.asJson
}
def getKeys: List[String] = {
val runtimeMirror = scala.reflect.runtime.currentMirror
val weatherParamClassSymbol = runtimeMirror.classSymbol(classOf[AggregateValue])
// Get all case classes that extend the WeatherParameter trait
val weatherParameterCases = weatherParamClassSymbol.knownDirectSubclasses.map(_.name.toString).toList
weatherParameterCases
}
}
sealed trait AggregateKey
object AggregateKey {
def stringToAggregateParam(strParam: String): Option[AggregateKey] = strParam match {
case "tempMax" => Some(AggregateKey.tempMax)
case "tempMaxList" => Some(AggregateKey.tempMaxList)
case "tempMin" => Some(AggregateKey.tempMin)
case "tempMinList" => Some(AggregateKey.tempMinList)
case "tempAvg" => Some(AggregateKey.tempAvg)
case "tempAvgList" => Some(AggregateKey.tempAvgList)
case "precipitationSum" => Some(AggregateKey.precipitationSum)
case "precipitationList" => Some(AggregateKey.precipitationList)
// case "windSpeedAvg" => Some(AggregateKey.windSpeedAvg)
// case "windGustMax" => Some(AggregateKey.windGustMax)
// case "snowThicknessAvg" => Some(AggregateKey.snowThicknessAvg)
// case "dewPointAvg" => Some(AggregateKey.dewPointAvg)
// case "airHumidityAvg" => Some(AggregateKey.airHumidityAvg)
case _ => None
}
def getKeys: List[String] = {
val runtimeMirror = scala.reflect.runtime.currentMirror
val weatherParamClassSymbol = runtimeMirror.classSymbol(classOf[AggregateKey])
// Get all case classes that extend the WeatherParameter trait
val weatherParameterCases = weatherParamClassSymbol.knownDirectSubclasses.map(_.name.toString).toList
weatherParameterCases
}
case object tempMax extends AggregateKey
case object tempMaxList extends AggregateKey
case object tempMin extends AggregateKey
case object tempMinList extends AggregateKey
case object tempAvg extends AggregateKey
case object tempAvgList extends AggregateKey
case object precipitationSum extends AggregateKey
case object precipitationList extends AggregateKey
// case object windSpeedAvg extends AggregateKey
//
// case object windGustMax extends AggregateKey
//
// case object snowThicknessAvg extends AggregateKey
//
// case object dewPointAvg extends AggregateKey
//
// case object airHumidityAvg extends AggregateKey
}
+3 -2
View File
@@ -2,6 +2,7 @@ package parse
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import io.circe.syntax.EncoderOps
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -16,8 +17,8 @@ object Main {
for {
lines <- db.DBService.getInRange(from, to)
parsed <- IO.pure(Parser.queryData(lines, List("Liepāja", "Rēzekne", "randomstr"), AggregateMeteo.tempAvg))
_ <- IO.println(parsed)
parsed <- IO.pure(Parser.queryData(lines, List("Liepāja", "Rēzekne", "randomstr"), AggregateKey.tempMax))
_ <- IO.println(parsed.asJson)
} yield ()
}
+1 -42
View File
@@ -1,7 +1,7 @@
package parse
import java.time.LocalDateTime
import scala.reflect.runtime.universe.{termNames, typeOf}
import scala.reflect.runtime.universe._
case class WeatherStationData(
@@ -27,42 +27,6 @@ case class MeteoData(
sunshineDuration: Option[Double],
)
sealed trait AggregateMeteo
object AggregateMeteo {
case object tempMax extends AggregateMeteo
case object tempMin extends AggregateMeteo
case object tempAvg extends AggregateMeteo
case object precipitationSum extends AggregateMeteo
case object windSpeedAvg extends AggregateMeteo
case object windGustMax extends AggregateMeteo
case object snowThicknessAvg extends AggregateMeteo
case object dewPointAvg extends AggregateMeteo
case object airHumidityAvg extends AggregateMeteo
}
object Meteo {
def stringToAggregateParam(strParam: String): Option[AggregateMeteo] = strParam match {
case "tempMax" => Some(AggregateMeteo.tempMax)
case "tempMin" => Some(AggregateMeteo.tempMin)
case "tempAvg" => Some(AggregateMeteo.tempAvg)
case "precipitationSum" => Some(AggregateMeteo.precipitationSum)
case "windSpeedAvg" => Some(AggregateMeteo.windSpeedAvg)
case "windGustMax" => Some(AggregateMeteo.windGustMax)
case "snowThicknessAvg" => Some(AggregateMeteo.snowThicknessAvg)
case "dewPointAvg" => Some(AggregateMeteo.dewPointAvg)
case "airHumidityAvg" => Some(AggregateMeteo.airHumidityAvg)
case _ => None
}
}
object MeteoData {
def fromDoubles(data: List[Option[Double]]): Option[MeteoData] = data match {
case List(
@@ -102,11 +66,6 @@ object MeteoData {
val paramCount = constructor.paramLists.flatten.size
paramCount
}
def getKeys: List[String] = {
val constructor = typeOf[MeteoData].decl(termNames.CONSTRUCTOR).asMethod
constructor.paramLists.flatten.map(_.name.toString)
}
}
//val cityList: List[String] = List("Ainaži", "Alūksne", "Bauska", "Dagda", "Daugavgrīva", "Daugavpils", "Dobele", "Gulbene", "Jelgava", "Kalnciems", "Kolka", "Kuldīga", "Lielpēči", "Liepāja", "Madona", "Mērsrags", "Pāvilosta", "Piedruja", "Priekuļi", "Rēzekne", "Rīga", "Rucava", "Rūjiena", "Saldus", "Sigulda", "Sīļi", "Skrīveri", "Skulte", "Stende", "Ventspils", "Vičaki", "Zīlāni", "Zosēni", "Ainaži", "Alūksne", "Bauska", "Dagda", "Daugavgrīva", "Daugavpils", "Dobele", "Gulbene", "Jelgava", "Kalnciems", "Kolka", "Kuldīga", "Lielpēči", "Liepāja", "Madona", "Mērsrags", "Pāvilosta", "Piedruja", "Priekuļi", "Rēzekne", "Rīga", "Rucava", "Rūjiena", "Saldus", "Sigulda", "Sīļi", "Skrīveri", "Skulte", "Stende", "Ventspils", "Vičaki", "Zīlāni", "Zosēni")
+35 -11
View File
@@ -10,6 +10,7 @@ object Parser {
def parseTimestamp(timestampStr: String): Option[LocalDateTime] = {
val formatter = DateTimeFormatter.ofPattern("yyyydd.MM HH:mm")
// TODO figure out what to do with hardcoded year. Proly fetched data should be also modified to inlcude year
Try(LocalDateTime.parse(s"2023${timestampStr.trim}", formatter)).toEither match {
case Right(timestamp) => Some(timestamp)
case Left(_) => None
@@ -35,22 +36,45 @@ object Parser {
private def aggregateLines(
lines: List[String],
cities: List[String],
aggregator: AggregateMeteo,
): Map[String, Double] = {
aggregator: AggregateKey,
): Map[String, AggregateValue] = {
val weatherByCity = lines
.flatMap(parseLine)
.filter(line => cities.contains(line.city))
.groupBy(_.city)
aggregator match {
case AggregateMeteo.tempAvg => weatherByCity.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.tempMax).max }
case AggregateMeteo.tempAvg => weatherByCity.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.tempMin).min }
case AggregateMeteo.tempAvg => weatherByCity.map { case (city, weatherData) => city -> {
val avgList = weatherData.flatMap(_.meteo.tempAvg)
avgList.sum / avgList.length
case AggregateKey.tempMax => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempMax(weatherData.flatMap(_.meteo.tempMax).max) }
case AggregateKey.tempMaxList => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempMaxList(weatherData.collect {
case wd if wd.meteo.tempMax.isDefined => wd.timestamp -> wd.meteo.tempMax.get
}.toMap) }
case AggregateKey.tempMin => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempMin(weatherData.flatMap(_.meteo.tempMin).min) }
case AggregateKey.tempMinList => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempMinList(weatherData.collect {
case wd if wd.meteo.tempMin.isDefined => wd.timestamp -> wd.meteo.tempMin.get
}.toMap)}
case AggregateKey.tempAvg => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempAvg({
val avgList = weatherData.flatMap(_.meteo.tempAvg)
avgList.sum / avgList.length
})}
case AggregateKey.tempAvgList => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.tempAvgList(weatherData.collect {
case wd if wd.meteo.tempAvg.isDefined => wd.timestamp -> wd.meteo.tempAvg.get
}.toMap)}
case AggregateKey.precipitationSum => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.precipitationSum(weatherData.flatMap(_.meteo.precipitation).sum)}
case AggregateKey.precipitationList => weatherByCity.map { case (city, weatherData) => city ->
AggregateValue.precipitationList(weatherData.collect {
case wd if wd.meteo.precipitation.isDefined => wd.timestamp -> wd.meteo.precipitation.get
}.toMap)
}
}
case AggregateMeteo.precipitationSum => weatherByCity.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.precipitation).sum }
// TODO add here other aggregateParams
}
}
@@ -59,8 +83,8 @@ object Parser {
def queryData(
data: List[String],
cities: List[String],
aggregator: AggregateMeteo,
): Map[String, Double] = {
aggregator: AggregateKey,
): Map[String, AggregateValue] = {
aggregateLines(data, cities, aggregator)
}
}
+4 -3
View File
@@ -4,9 +4,9 @@ import cats.effect._
import cats.implicits.toTraverseOps
import db.DBService
import fetch.FetchService
import parse.{MeteoData, Parser}
import parse.{AggregateKey, AggregateValue, Parser}
import server.ValidateRoutes.{Aggregate, CityList, DateTimeRange, ValidDate}
import io.circe.{Json, Printer}
import io.circe.{Encoder, Json, Printer}
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.server.Router
@@ -31,6 +31,7 @@ object Server extends IOApp {
DBService.getInRange(from, to)
.map(Parser.queryData(_, cities, aggregate))
.flatMap(result => Ok(result.asJson.pretty))
// .flatMap(result => Ok("make json encoder"))
// http://localhost:3000/fetch/date/20230423
case GET -> Root / "fetch" / "date" / ValidDate(date) =>
@@ -64,7 +65,7 @@ object Server extends IOApp {
// http://localhost:3000/help
case GET -> Root / "help" =>
Ok(MeteoData.getKeys.asJson.pretty)
Ok(AggregateKey.getKeys.asJson.pretty)
}
private val httpApp = Router("/" -> appRoutes).orNotFound
+3 -3
View File
@@ -1,6 +1,6 @@
package server
import parse.{AggregateMeteo, Meteo}
import parse.AggregateKey
import java.time.{LocalDate, LocalDateTime}
import java.time.format.DateTimeFormatter
@@ -36,8 +36,8 @@ object ValidateRoutes {
}
object Aggregate {
def unapply(str: String): Option[AggregateMeteo] = {
Meteo.stringToAggregateParam(str)
def unapply(str: String): Option[AggregateKey] = {
AggregateKey.stringToAggregateParam(str)
}
}
}