Skip to content

Commit

Permalink
-- 104.ua parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
djnzx committed Oct 7, 2023
1 parent d9fde0d commit f603c31
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 0 deletions.
32 changes: 32 additions & 0 deletions ce3/src/main/scala/_playground/Sketch11.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package _playground

import cats.effect.{IO, IOApp}
import cats.implicits._

import scala.concurrent.duration.DurationInt

object Sketch11 extends App {

val data = List(1, 2, 3, 4)

val s: String = data
.map(x => x.toString)
.reduce( (s1, s2) => s1 + ", "+ s2 )

val min = data.reduce( (x1, x2) => math.min(x1, x2) )
val max = data.reduce( (x1, x2) => math.max(x1, x2) )
val sum = data.reduce( (x1, x2) => x1 + x2 )
val prd = data.reduce( (x1, x2) => x1 * x2 )

val m1: Int = data.min
val m2: Option[Int] = data.minOption


println(s) // "1, 2, 3, 4"
println(min) // 1
println(max) // 4
println(sum) // 10
println(prd) // 24


}
173 changes: 173 additions & 0 deletions ce3/src/main/scala/gas104/DomainSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package gas104

import cats.effect.Concurrent
import cats.effect.IO
import cats.effect.Sync
import cats.effect.kernel.Async
import cats.effect.kernel.Resource
import cats.implicits._
import gas104.domain._
import gas104.domain.api._
import io.circe.parser
import io.circe.syntax.EncoderOps
import java.time.Instant
import java.time.LocalDateTime
import org.http4s.Response
import org.http4s.blaze.client.BlazeClientBuilder
import org.http4s.client.Client
import org.scalatest.Succeeded
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

class DomainSpec extends AnyFunSpec with Matchers {

describe("Row object") {

val rowObj: Row = Row(
"20.07.2023 07:00",
"0.81",
0,
1689800400,
)

val rowRaw =
"""
|{
| "dt" : "20.07.2023 07:00",
| "counter_reading" : "0.81",
| "consumption" : 0.0,
| "created_at_timestamp" : 1689800400
|}
|""".stripMargin.trim

it("encodes properly") {
rowObj.asJson.spaces2 shouldBe rowRaw
}

it("decodes properly") {
parser.decode[Row](rowRaw) shouldBe rowObj.asRight
}

}

describe("data") {

val dataObj: Data = Data(
error = None,
data = Seq(
Row(
"20.07.2023 07:00",
"0.81",
0,
1689800400,
),
Row(
"21.07.2023 07:00",
"0.81",
0,
1689886800,
)
)
)

val rawData =
"""
|{
| "error" : null,
| "data" : [
| {
| "dt" : "20.07.2023 07:00",
| "counter_reading" : "0.81",
| "consumption" : 0.0,
| "created_at_timestamp" : 1689800400
| },
| {
| "dt" : "21.07.2023 07:00",
| "counter_reading" : "0.81",
| "consumption" : 0.0,
| "created_at_timestamp" : 1689886800
| }
| ]
|}
|""".stripMargin.trim

it("encodes properly") {
dataObj.asJson.spaces2 shouldBe rawData
}

it("decodes properly") {
parser.decode[Data](rawData) shouldBe dataObj.asRight
}

}

describe("convertor") {

it("apiRow -> URow") {
val rowFrom104: Row = Row(
"20.07.2023 07:00",
"0.81",
0,
1689800400,
)

val uRowExpected = URow(
LocalDateTime.of(2023, 7, 20, 7, 0),
counter = 0.81,
delta = 0.0,
createdAt = Instant.parse("2023-07-19T21:00:00Z")
)

URow.from(rowFrom104) shouldBe uRowExpected
}

}

describe("instant playground") {
val ts: Long = 1696539600L
val instant: Instant = Instant.ofEpochSecond(ts) // 2023-10-05T21:00:00Z // "06.10.2023 07:00"

it("3") {
pprint.pprintln(instant)
}

}

describe("http") {

def body[F[_] : Concurrent](rs: Response[F]): F[String] =
rs.body
.through(fs2.text.utf8.decode[F])
.compile
.foldMonoid

def mkHttpClient[F[_]: Async]: Resource[F, Client[F]] =
BlazeClientBuilder[F].resource

def mkRequest[F[_]: Concurrent](client: Client[F]) = {
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
client.expect[Data](Htttp.rq[F])
}

def representData[F[_]: Sync](payload: Data): F[Unit] = payload.data
.traverse_ { x: Row =>
val r = URow.from(x)
val line = (r.dateTime, r.counter, r.delta)
Sync[F].delay(pprint.pprintln(line))
}

it("1") {
import cats.effect.unsafe.implicits.global
mkHttpClient[IO]
.use(mkRequest[IO])
.flatMap(representData[IO])
.unsafeRunSync()
}

it("stub tun trigger compilation") {
Succeeded
}

}

}
20 changes: 20 additions & 0 deletions ce3/src/main/scala/gas104/GasApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gas104

import cats.effect.IO
import cats.effect.IOApp
import io.circe.generic.AutoDerivation
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.{deriveConfiguredDecoder, deriveConfiguredEncoder}
import io.circe.syntax.EncoderOps
import io.circe.{Decoder, Encoder}

object GasApp extends IOApp.Simple {


override def run: IO[Unit] = IO{

println(

)
}
}
36 changes: 36 additions & 0 deletions ce3/src/main/scala/gas104/Htttp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gas104

import org.http4s.Headers
import org.http4s.MediaType
import org.http4s.Method
import org.http4s.Request
import org.http4s.RequestCookie
import org.http4s.UrlForm
import org.http4s.headers._
import org.http4s.implicits.http4sLiteralsSyntax

object Htttp {

object PhpSessionId {
def apply(value: String): RequestCookie = RequestCookie("PHPSESSID", value)
}

val uri = uri"https://ok.104.ua/ua/ajx/individual/meterage/history/remote"
val headers = Headers(
`Accept`(MediaType.application.json),
`Content-Type`(MediaType.application.`x-www-form-urlencoded`),
`Cookie`(PhpSessionId("")),
)

val payload = UrlForm(
"account_no" -> "", // 0800xxxxxx
"meter_no" -> "",
"period_type" -> "y",
"end_date" -> "2023-12-31T23:59:59.999Z"
)

def rq[F[_]] = Request[F](Method.POST, uri)
.withHeaders(headers)
.withEntity(payload)

}
64 changes: 64 additions & 0 deletions ce3/src/main/scala/gas104/domain.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package gas104

import io.circe.Decoder
import io.circe.Encoder
import io.circe.generic.AutoDerivation
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredDecoder
import io.circe.generic.extras.semiauto.deriveConfiguredEncoder
import io.scalaland.chimney.dsl.TransformerOps

import java.time.Instant
import java.time.LocalDateTime
import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder}
import java.time.temporal.ChronoField

object domain {

object api {

case class Row(
dt: String,
counterReading: String,
consumption: Double,
createdAtTimestamp: Long)

object Row {
implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames
implicit val encoder: Encoder[Row] = deriveConfiguredEncoder
implicit val decoder: Decoder[Row] = deriveConfiguredDecoder
}

case class Data(error: Option[String], data: Seq[Row])

object Data extends AutoDerivation

}

val gasDtFormatter: DateTimeFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.appendLiteral('.')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('.')
.appendValue(ChronoField.YEAR, 4)
.appendLiteral(' ')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.toFormatter

case class URow(dateTime: LocalDateTime, counter: Double, delta: Double, createdAt: Instant)
object URow {

def from(apiRow: api.Row): URow =
apiRow
.into[URow]
.withFieldComputed(_.dateTime, x => LocalDateTime.parse(x.dt, gasDtFormatter))
.withFieldComputed(_.counter, x => x.counterReading.toDouble)
.withFieldRenamed(_.consumption, _.delta)
.withFieldComputed(_.createdAt, x => Instant.ofEpochSecond(x.createdAtTimestamp))
.transform

}

}

0 comments on commit f603c31

Please sign in to comment.