Change data structure for meteo adding AggregateKey and AggregateValue
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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")
|
||||
|
||||
@@ -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 -> {
|
||||
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,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user