This is some shit
This commit is contained in:
parent
1a64f23eae
commit
05b7a81139
5 changed files with 313 additions and 10 deletions
|
@ -30,5 +30,6 @@ gleam_json = ">= 1.0.1 and < 2.0.0"
|
|||
envoy = ">= 1.0.1 and < 2.0.0"
|
||||
mist = ">= 1.2.0 and < 2.0.0"
|
||||
aurinko_common = { path = "../common" }
|
||||
gleamy_structures = ">= 1.0.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "aurinko_common", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../common" },
|
||||
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
|
||||
{ name = "biscotto", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "biscotto", source = "hex", outer_checksum = "A1CFEA1686FA8ABDE90B76E22775FF29EE8156A64DAC327F48141A68951D662C" },
|
||||
{ name = "envoy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "CFAACCCFC47654F7E8B75E614746ED924C65BD08B1DE21101548AC314A8B6A41" },
|
||||
|
@ -9,7 +10,6 @@ packages = [
|
|||
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
|
||||
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
|
||||
{ name = "form_coder", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "form_coder", source = "hex", outer_checksum = "FA27C97AADF66A79E4EEE78D30A50C18660A9FAFD91C27A5BC780DBC753CB8AF" },
|
||||
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
|
||||
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
|
||||
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
|
||||
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
|
||||
|
@ -17,6 +17,7 @@ packages = [
|
|||
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
|
||||
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
|
||||
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
|
||||
{ name = "gleamy_structures", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleamy_structures", source = "hex", outer_checksum = "FF1B7600123B2B7C15E91BEBE6F417B4003928BF4282EF01A74A792B4ED6985A" },
|
||||
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
|
||||
{ name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" },
|
||||
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
|
||||
|
@ -24,7 +25,6 @@ packages = [
|
|||
{ name = "htmgrrrl", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "htmerl"], otp_app = "htmgrrrl", source = "hex", outer_checksum = "983492567967DAA64776E005B9E70353368B14E6F87D543E01308B48A7A0398F" },
|
||||
{ name = "logging", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "FCB111401BDB4703A440A94FF8CC7DA521112269C065F219C2766998333E7738" },
|
||||
{ name = "lustre", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "43642C0602D3E2D6FEC3E24173D68A1F8E646969B53A2B0A5EB61238DDA739C4" },
|
||||
{ name = "lustre_ui", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_ui", source = "hex", outer_checksum = "FA1F9E89D89CDD5DF376ED86ABA8A38441CB2E664CD4D402F22A49DA4D7BB56D" },
|
||||
{ name = "marceau", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "5188D643C181EE350D8A20A3BDBD63AF7B6C505DE333CFBE05EF642ADD88A59B" },
|
||||
{ name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" },
|
||||
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
|
||||
|
@ -35,6 +35,7 @@ packages = [
|
|||
]
|
||||
|
||||
[requirements]
|
||||
aurinko_common = { path = "../common" }
|
||||
birl = { version = ">= 1.7.1 and < 2.0.0" }
|
||||
biscotto = { version = ">= 1.0.0 and < 2.0.0" }
|
||||
envoy = { version = ">= 1.0.1 and < 2.0.0" }
|
||||
|
@ -45,9 +46,9 @@ gleam_httpc = { version = ">= 2.2.0 and < 3.0.0" }
|
|||
gleam_json = { version = ">= 1.0.1 and < 2.0.0" }
|
||||
gleam_otp = { version = ">= 0.10.0 and < 1.0.0" }
|
||||
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
|
||||
gleamy_structures = { version = ">= 1.0.0 and < 2.0.0"}
|
||||
htmgrrrl = { version = ">= 0.3.0 and < 1.0.0" }
|
||||
lustre = { version = ">= 4.3.0 and < 5.0.0" }
|
||||
lustre_ui = { version = ">= 0.6.0 and < 1.0.0"}
|
||||
mist = { version = ">= 1.2.0 and < 2.0.0" }
|
||||
sqlight = { version = ">= 0.9.0 and < 1.0.0" }
|
||||
wisp = { version = ">= 0.15.0 and < 1.0.0" }
|
||||
|
|
|
@ -2,6 +2,7 @@ import birl
|
|||
import biscotto
|
||||
import form_coder
|
||||
import gleam/dynamic
|
||||
import gleam/float
|
||||
import gleam/http
|
||||
import gleam/http/request
|
||||
import gleam/httpc
|
||||
|
@ -21,6 +22,18 @@ pub type APIError {
|
|||
ScrapeError
|
||||
}
|
||||
|
||||
pub type ProductionInfo {
|
||||
ProductionInfo(
|
||||
co2: Float,
|
||||
duration: Int,
|
||||
ecu_sign: String,
|
||||
last_power: Float,
|
||||
lifetime: Float,
|
||||
meter_flag: Int,
|
||||
today: Float,
|
||||
)
|
||||
}
|
||||
|
||||
pub type ModuleIDs {
|
||||
ModuleIDs(sid: String, vids: List(String))
|
||||
}
|
||||
|
@ -75,12 +88,29 @@ pub fn login(username: String, password: String) {
|
|||
|
||||
pub fn get_production(cookies: biscotto.CookieJar) {
|
||||
let assert Ok(req) = request.from_uri(production_info_url())
|
||||
req
|
||||
|> request.set_method(http.Post)
|
||||
|> set_common_headers()
|
||||
|> biscotto.with_cookies(cookies)
|
||||
|> httpc.send()
|
||||
|> result.map_error(RequestError)
|
||||
use resp <- result.try(
|
||||
req
|
||||
|> request.set_method(http.Post)
|
||||
|> set_common_headers()
|
||||
|> biscotto.with_cookies(cookies)
|
||||
|> httpc.send()
|
||||
|> result.map_error(RequestError),
|
||||
)
|
||||
|
||||
json.decode(
|
||||
resp.body,
|
||||
dynamic.decode7(
|
||||
ProductionInfo,
|
||||
dynamic.field("co2", float_string_decoder),
|
||||
dynamic.field("duration", dynamic.int),
|
||||
dynamic.field("ecu_sign", dynamic.string),
|
||||
dynamic.field("last_power", float_string_decoder),
|
||||
dynamic.field("lifetime", float_string_decoder),
|
||||
dynamic.field("meter_flag", dynamic.int),
|
||||
dynamic.field("today", float_string_decoder),
|
||||
),
|
||||
)
|
||||
|> result.map_error(DecodeError)
|
||||
}
|
||||
|
||||
pub fn get_module_ids(cookies: biscotto.CookieJar) {
|
||||
|
@ -238,3 +268,11 @@ fn module_power_url() {
|
|||
path: root_path <> ajax_view_root <> "/getViewPowerByViewAjax",
|
||||
)
|
||||
}
|
||||
|
||||
fn float_string_decoder(str: dynamic.Dynamic) {
|
||||
use value <- result.try(dynamic.string(str))
|
||||
float.parse(value)
|
||||
|> result.replace_error([
|
||||
dynamic.DecodeError("a string representing a float", value, []),
|
||||
])
|
||||
}
|
||||
|
|
264
backend/src/aurinko/updater.gleam
Normal file
264
backend/src/aurinko/updater.gleam
Normal file
|
@ -0,0 +1,264 @@
|
|||
import ap_systems/api
|
||||
import ap_systems/module_power
|
||||
import birl
|
||||
import birl/duration
|
||||
import biscotto
|
||||
import gleam/dict
|
||||
import gleam/dynamic
|
||||
import gleam/list
|
||||
import gleam/option
|
||||
import gleam/order
|
||||
import gleam/otp/actor
|
||||
import gleam/result
|
||||
import gleamy/red_black_tree_set.{type Set}
|
||||
|
||||
const login_cookie_expiry = 86_400
|
||||
|
||||
pub type Message {
|
||||
Update
|
||||
}
|
||||
|
||||
pub type ProductionInfo =
|
||||
api.ProductionInfo
|
||||
|
||||
pub type ModuleIDs =
|
||||
api.ModuleIDs
|
||||
|
||||
pub type MomentaryPower =
|
||||
module_power.MomentaryPower
|
||||
|
||||
pub type ModulePower {
|
||||
ModulePower(id: String, power: dict.Dict(birl.Time, MomentaryPower))
|
||||
}
|
||||
|
||||
pub type ModuleDataset {
|
||||
ModuleDataset(times: Set(birl.Time), datas: dict.Dict(String, ModulePower))
|
||||
}
|
||||
|
||||
pub type Dataset {
|
||||
Dataset(production_info: ProductionInfo, modules: ModuleDataset)
|
||||
}
|
||||
|
||||
pub type State {
|
||||
InitialState(
|
||||
username: String,
|
||||
password: String,
|
||||
error: option.Option(UpdateError),
|
||||
)
|
||||
LoadedState(
|
||||
username: String,
|
||||
password: String,
|
||||
cookies: biscotto.CookieJar,
|
||||
cookies_acquired: birl.Time,
|
||||
ids: ModuleIDs,
|
||||
dataset: option.Option(Dataset),
|
||||
error: option.Option(UpdateError),
|
||||
)
|
||||
}
|
||||
|
||||
pub type UpdateError {
|
||||
APIError(err: api.APIError)
|
||||
DecodeError(err: dynamic.DecodeError)
|
||||
}
|
||||
|
||||
pub fn start(username: String, password: String) {
|
||||
actor.start(InitialState(username, password, option.None), handle_message)
|
||||
}
|
||||
|
||||
fn handle_message(message: Message, state: State) -> actor.Next(Message, State) {
|
||||
let next =
|
||||
with_loaded_state(
|
||||
state,
|
||||
fn(username, password, cookies, cookies_acquired, ids, dataset) {
|
||||
case message {
|
||||
Update -> {
|
||||
use #(cookies, cookies_acquired, new_dataset) <- result.try(update(
|
||||
username,
|
||||
password,
|
||||
cookies,
|
||||
cookies_acquired,
|
||||
ids,
|
||||
dataset,
|
||||
))
|
||||
|
||||
let new_state =
|
||||
LoadedState(
|
||||
username,
|
||||
password,
|
||||
cookies,
|
||||
cookies_acquired,
|
||||
ids,
|
||||
option.Some(new_dataset),
|
||||
option.None,
|
||||
)
|
||||
|
||||
Ok(actor.continue(new_state))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
case next {
|
||||
Ok(next) -> next
|
||||
Error(err) -> {
|
||||
let new_state = case state {
|
||||
InitialState(username, password, ..) ->
|
||||
InitialState(username, password, error: option.Some(err))
|
||||
LoadedState(
|
||||
username,
|
||||
password,
|
||||
cookies,
|
||||
cookies_acquired,
|
||||
ids,
|
||||
dataset,
|
||||
..,
|
||||
) ->
|
||||
LoadedState(
|
||||
username,
|
||||
password,
|
||||
cookies,
|
||||
cookies_acquired,
|
||||
ids,
|
||||
dataset,
|
||||
option.Some(err),
|
||||
)
|
||||
}
|
||||
actor.continue(new_state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_loaded_state(
|
||||
state: State,
|
||||
fun: fn(
|
||||
String,
|
||||
String,
|
||||
biscotto.CookieJar,
|
||||
birl.Time,
|
||||
ModuleIDs,
|
||||
option.Option(Dataset),
|
||||
) ->
|
||||
Result(actor.Next(Message, State), UpdateError),
|
||||
) {
|
||||
case state {
|
||||
InitialState(username, password, ..) -> {
|
||||
use loaded_state <- result.try(
|
||||
init(username, password) |> result.map_error(APIError),
|
||||
)
|
||||
fun(
|
||||
username,
|
||||
password,
|
||||
loaded_state.0,
|
||||
loaded_state.1,
|
||||
loaded_state.2,
|
||||
loaded_state.3,
|
||||
)
|
||||
}
|
||||
LoadedState(username, password, cookies, cookies_acquired, ids, dataset, ..) ->
|
||||
fun(username, password, cookies, cookies_acquired, ids, dataset)
|
||||
}
|
||||
}
|
||||
|
||||
fn login(username: String, password: String) {
|
||||
use cookies <- result.try(api.login(username, password))
|
||||
Ok(#(cookies, birl.utc_now()))
|
||||
}
|
||||
|
||||
fn init(username: String, password: String) {
|
||||
use #(cookies, cookies_acquired) <- result.try(login(username, password))
|
||||
use module_ids <- result.try(api.get_module_ids(cookies))
|
||||
|
||||
Ok(#(cookies, cookies_acquired, module_ids, option.None))
|
||||
}
|
||||
|
||||
fn update(
|
||||
username: String,
|
||||
password: String,
|
||||
cookies: biscotto.CookieJar,
|
||||
cookies_acquired: birl.Time,
|
||||
module_ids: ModuleIDs,
|
||||
dataset: option.Option(Dataset),
|
||||
) {
|
||||
let now = birl.utc_now()
|
||||
let max_lifetime = duration.seconds(login_cookie_expiry)
|
||||
let should_renew = case
|
||||
duration.compare(birl.difference(now, cookies_acquired), max_lifetime)
|
||||
{
|
||||
order.Gt | order.Eq -> True
|
||||
_ -> False
|
||||
}
|
||||
|
||||
let cookies_result = case should_renew {
|
||||
False -> Ok(#(cookies, cookies_acquired))
|
||||
True -> login(username, password)
|
||||
}
|
||||
|
||||
use #(cookies, cookies_acquired) <- result.try(
|
||||
cookies_result |> result.map_error(APIError),
|
||||
)
|
||||
|
||||
use production_info <- result.try(
|
||||
api.get_production(cookies) |> result.map_error(APIError),
|
||||
)
|
||||
use module_datas <- result.try(
|
||||
api.get_module_power(cookies, module_ids) |> result.map_error(APIError),
|
||||
)
|
||||
use parsed_module_datas <- result.try(
|
||||
module_power.decode_api_data(module_datas)
|
||||
|> result.map_error(DecodeError),
|
||||
)
|
||||
|
||||
let new_dataset = case dataset {
|
||||
option.Some(existing_dataset) ->
|
||||
Dataset(..existing_dataset, production_info: production_info)
|
||||
option.None ->
|
||||
Dataset(
|
||||
production_info,
|
||||
ModuleDataset(times: new_ordered_set(), datas: dict.new()),
|
||||
)
|
||||
}
|
||||
|
||||
let new_dataset =
|
||||
Dataset(
|
||||
..new_dataset,
|
||||
modules: add_module_datasets(new_dataset.modules, parsed_module_datas),
|
||||
)
|
||||
|
||||
Ok(#(cookies, cookies_acquired, new_dataset))
|
||||
}
|
||||
|
||||
fn add_module_datasets(
|
||||
to: ModuleDataset,
|
||||
data: module_power.CombinedDailyPower,
|
||||
) -> ModuleDataset {
|
||||
let to =
|
||||
list.fold(data.times, to, fn(acc, time) {
|
||||
ModuleDataset(..acc, times: red_black_tree_set.insert(acc.times, time))
|
||||
})
|
||||
|
||||
ModuleDataset(
|
||||
..to,
|
||||
datas: dict.fold(
|
||||
data.modules,
|
||||
to.datas,
|
||||
fn(acc, id, module: module_power.ModuleDailyPower) {
|
||||
let module_power =
|
||||
result.unwrap(
|
||||
dict.get(acc, module.id),
|
||||
ModulePower(id: id, power: dict.new()),
|
||||
)
|
||||
let module_power =
|
||||
ModulePower(
|
||||
..module_power,
|
||||
power: dict.merge(module_power.power, module.power),
|
||||
)
|
||||
|
||||
dict.insert(acc, id, module_power)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_ordered_set() -> Set(birl.Time) {
|
||||
red_black_tree_set.new(birl.compare)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
Loading…
Reference in a new issue