Parser accepts querying data, server reads from parser
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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}"))
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user