Parser accepts querying data, server reads from parser

This commit is contained in:
Guntis Smaukstelis
2023-04-18 17:18:45 +03:00
parent 260b6ef23a
commit 0e60f94835
6 changed files with 94 additions and 26 deletions
-1
View File
@@ -15,7 +15,6 @@ libraryDependencies ++= Seq(
"org.http4s" %% "http4s-blaze-server" % "0.23.9", "org.http4s" %% "http4s-blaze-server" % "0.23.9",
"org.http4s" %% "http4s-blaze-client" % "0.23.13", "org.http4s" %% "http4s-blaze-client" % "0.23.13",
"org.http4s" %% "http4s-circe" % http4sVersion, "org.http4s" %% "http4s-circe" % http4sVersion,
"org.http4s" %% "http4s-jdk-http-client" % "0.8.0",
"ch.qos.logback" % "logback-classic" % "1.2.9", "ch.qos.logback" % "logback-classic" % "1.2.9",
"com.typesafe" % "config" % "1.4.1", "com.typesafe" % "config" % "1.4.1",
+2
View File
@@ -13,6 +13,7 @@ import java.nio.file.{Files, Paths}
import scala.concurrent.ExecutionContext.global import scala.concurrent.ExecutionContext.global
object FetchData extends IOApp.Simple { object FetchData extends IOApp.Simple {
// TODO wrap config in IO
private val config = ConfigFactory.load() private val config = ConfigFactory.load()
private val basicCredentials = BasicCredentials(config.getString("username"), config.getString("password")) private val basicCredentials = BasicCredentials(config.getString("username"), config.getString("password"))
private val baseUrl = Uri.unsafeFromString(config.getString("url")) // 20220831_1330.csv private val baseUrl = Uri.unsafeFromString(config.getString("url")) // 20220831_1330.csv
@@ -20,6 +21,7 @@ object FetchData extends IOApp.Simple {
def saveToFile(fileName: String, content: String): IO[Unit] = { def saveToFile(fileName: String, content: String): IO[Unit] = {
val path = Paths.get(s"data/$fileName") val path = Paths.get(s"data/$fileName")
// TODO redeemWith instead of flatMap
IO(Files.writeString(path, content)).attempt.flatMap { IO(Files.writeString(path, content)).attempt.flatMap {
case Right(_) => IO(println(s"write: $fileName")) case Right(_) => IO(println(s"write: $fileName"))
case Left(error) => IO(println(s"Write file '$fileName' failed with error: ${error.getMessage}")) case Left(error) => IO(println(s"Write file '$fileName' failed with error: ${error.getMessage}"))
+1
View File
@@ -8,6 +8,7 @@ object FileName {
private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
def generateLastHour(): List[String] = { def generateLastHour(): List[String] = {
// TODO IO.timer() IOTimer
val now = LocalDateTime.now val now = LocalDateTime.now
generate(now.minusHours(1), now) generate(now.minusHours(1), now)
} }
+37 -1
View File
@@ -24,9 +24,45 @@ case class MeteoData(
atmPressure: Option[Double], atmPressure: Option[Double],
dewPoint: Option[Double], dewPoint: Option[Double],
airHumidity: Option[Double], airHumidity: Option[Double],
sunshineDuration: Option[Double] 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 { object MeteoData {
def fromDoubles(data: List[Option[Double]]): Option[MeteoData] = data match { def fromDoubles(data: List[Option[Double]]): Option[MeteoData] = data match {
case List( case List(
+40 -14
View File
@@ -6,6 +6,23 @@ import java.time.format.DateTimeFormatter
import scala.io.Source import scala.io.Source
import scala.util.Try import scala.util.Try
/*
import cats.effect.{IO, Resource}
import java.io.File
def readFile(file: File): IO[String] =
IO(scala.io.Source.fromFile(file).mkString).handleErrorWith(_ => IO.pure(""))
def readFiles(dir: File): IO[List[(String, String)]] =
IO(dir.listFiles.toList)
.flatMap(files =>
files.traverse { file =>
readFile(file).map((file.getName, _))
}
)
.handleErrorWith(_ => IO.pure(List.empty))
*/
object Parser { object Parser {
val data_path = "/Users/guntissmaukstelis/sandbox/hello/data/" val data_path = "/Users/guntissmaukstelis/sandbox/hello/data/"
@@ -63,6 +80,26 @@ object Parser {
def readFromVariable(str: String): List[String] = str.split("\n").toList // Data.csv def readFromVariable(str: String): List[String] = str.split("\n").toList // Data.csv
def queryData(from: LocalDateTime, to: LocalDateTime, cities: List[String], aggregator: AggregateMeteo): Map[String, Double] = {
val res = getFilesInRange(from, to)
.flatMap(readFromFile)
.flatMap(parseLine)
.filter(line => cities.contains(line.city))
.groupBy(_.city)
// res.foreach(println)
aggregator match {
case AggregateMeteo.tempAvg => res.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.tempMax).max }
case AggregateMeteo.tempAvg => res.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.tempMin).min }
case AggregateMeteo.tempAvg => res.map { case (city, weatherData) => city -> {
val avgList = weatherData.flatMap(_.meteo.tempAvg)
avgList.sum / avgList.length
}}
case AggregateMeteo.precipitationSum => res.map { case (city, weatherData) => city -> weatherData.flatMap(_.meteo.precipitation).sum }
// TODO add here other aggregateParams
}
}
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
println("================ start parser") println("================ start parser")
@@ -70,20 +107,9 @@ object Parser {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm") val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
val start = LocalDateTime.parse("20230409_2200", formatter) val start = LocalDateTime.parse("20230409_2200", formatter)
val end = LocalDateTime.parse("20230501_1230", formatter) val end = LocalDateTime.parse("20230501_1230", formatter)
val files = getFilesInRange(start, end)
val weatherStationData = files
.flatMap(readFromFile)
.flatMap(parseLine)
// weatherStationData.foreach(println)
val liepajaWeather = weatherStationData.filter(_.city == "Liepāja")
val maxTempLiepaja = liepajaWeather.flatMap(_.meteo.tempMax).max
val minTempLiepaja = liepajaWeather.flatMap(_.meteo.tempMin).min
val avgTemps = liepajaWeather.flatMap(_.meteo.tempAvg)
val avgTempLiepaja = avgTemps.sum / avgTemps.size
println(s"Liepaja max: $maxTempLiepaja, min: $minTempLiepaja, avg: $avgTempLiepaja")
// liepaja.foreach(println)
val parsed = queryData(start, end, List("Liepāja", "Rēzekne", "randomstr"), AggregateMeteo.tempAvg)
// parsed.foreach(println)
println(parsed.toString())
} }
} }
+14 -10
View File
@@ -6,18 +6,19 @@ import org.http4s.dsl.io._
import org.http4s.implicits._ import org.http4s.implicits._
import org.http4s.server.Router import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder import org.http4s.server.blaze.BlazeServerBuilder
import parse.{Meteo, Parser}
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import scala.util.Try import scala.util.Try
object Server extends IOApp { object Server extends IOApp {
private val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm") private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
private val appRoutes = HttpRoutes.of[IO] { private val appRoutes = HttpRoutes.of[IO] {
// http://localhost:3000/202304131030-202304132030/riga,liepaja/tempAvg // http://localhost:3000/20230409_2200-20230501_1230/Liepāja,Rēzekne/tempAvg
case GET -> Root / timestampRange / cities / weatherParam => case GET -> Root / timestampRange / cities / aggregate =>
// TODO proly better to Validated with chained errors // TODO proly better to Validated with chained errors
val res = for { val parsedArguments = for {
(from, to) <- timestampRange.split("-").toList (from, to) <- timestampRange.split("-").toList
.map(str => Try(LocalDateTime.parse(str, formatter)).toOption) match { .map(str => Try(LocalDateTime.parse(str, formatter)).toOption) match {
case List(Some(from), Some(to)) => Some(from, to) case List(Some(from), Some(to)) => Some(from, to)
@@ -27,13 +28,16 @@ object Server extends IOApp {
case list => Some(list) case list => Some(list)
case Nil => None case Nil => None
} }
param <- if (weatherParam.isEmpty) None else Some(weatherParam) aggregate <- Meteo.stringToAggregateParam(aggregate)
}
// TODO yield to parser method which accepts those args
yield s"Timestamp From: $from, Timestamp To: $to, Cities: ${cityList.mkString(", ")}, Weather Param: $param"
res match { }
case Some(responseTxt) => Ok(responseTxt) yield (from, to, cityList, aggregate)
parsedArguments match {
case Some((from, to, cityList, aggregate)) => {
val resultData = Parser.queryData(from, to, cityList, aggregate)
Ok(resultData.toString())
}
case _ => BadRequest(s"Invalid request format") case _ => BadRequest(s"Invalid request format")
} }
} }