amber_client/
lib.rs

1pub mod app_config;
2pub mod rest_client;
3
4use anyhow::Result;
5use chrono::NaiveDate;
6use csv::WriterBuilder;
7use std::process;
8
9use rest_client::{PriceData, RenewablesData, RestClient, SiteDetails, UsageData};
10
11/// Function to get and return only the users Site ID.
12pub async fn get_user_site_id(base_url: String, auth_token: String) -> Result<String> {
13    let user_site_data = get_site_data(base_url, auth_token).await?;
14    let user_site_id = user_site_data[0].id.clone();
15    Ok(user_site_id)
16}
17
18/// Function to get the Site data
19pub async fn get_site_data(base_url: String, auth_token: String) -> Result<Vec<SiteDetails>> {
20    let sites_url = format!("{}/sites", base_url);
21    let mut user_site_details = RestClient::new_client(sites_url, auth_token.clone());
22    let user_site_data = user_site_details.get_site_data().await?;
23
24    Ok(user_site_data)
25}
26
27// Current
28// https://api.amber.com.au/v1/sites/SITE_ID/prices/current?resolution=30
29
30// next
31// https://api.amber.com.au/v1/sites/SITE_ID/prices/current?next=1&resolution=30
32
33// previous
34// https://api.amber.com.au/v1/sites/SITE_ID/prices/current?previous=1&resolution=30'
35
36/// Function to get a window of prices. Based the users input.
37pub async fn get_prices(
38    base_url: String,
39    auth_token: String,
40    site_id: String,
41    window: String,
42) -> Result<Vec<PriceData>> {
43    let price_url = format!(
44        "{}/sites/{}/prices/{}?&resolution=30",
45        base_url, site_id, window
46    );
47    let mut current_price_details = RestClient::new_client(price_url, auth_token.clone());
48    let current_price_data = current_price_details.get_price_data().await?;
49
50    Ok(current_price_data)
51}
52
53// get historical usage
54// https://api.amber.com.au/v1/sites/SITE_ID/usage?startDate=2023-12-18&endDate=2023-12-19&resolution=30'
55
56/// Function to retrieve historical price data based on a date range supplied by the user.
57pub async fn get_usage_by_date(
58    base_url: String,
59    auth_token: String,
60    site_id: String,
61    start_date: String,
62    end_date: String,
63) -> Result<Vec<UsageData>> {
64    let start_date = parse_date_naive(start_date).await?;
65    let end_date = parse_date_naive(end_date).await?;
66    let usage_data_url = format!(
67        "{}/sites/{}/usage?startDate={}&endDate={}&resolution=30'",
68        base_url, site_id, start_date, end_date
69    );
70    let mut usage_details = RestClient::new_client(usage_data_url, auth_token.clone());
71    let usage_data = usage_details.get_usage_data().await?;
72    Ok(usage_data)
73}
74
75// https://api.amber.com.au/v1/state/STATE/renewables/current=30'
76// also supports "previous=" and "next=" , "/renewables/current?previous=1&resolution=30"
77
78/// Function to get percentage of renewables used in the grid for a given state and a given window.
79pub async fn get_renewables(
80    base_url: String,
81    auth_token: String,
82    state: String,
83    window: String,
84) -> Result<Vec<RenewablesData>> {
85    let price_url = format!(
86        "{}/state/{}/renewables/{}?&resolution=30",
87        base_url, state, window
88    );
89    let renewables_request = RestClient::new_client(price_url, auth_token.clone());
90    let renewables_data = renewables_request.get_renewables_data().await?;
91    Ok(renewables_data)
92}
93
94/// Function to validate the user has supplied the date in the correct format and that
95/// the date is a valid calender date.
96/// Will exit the application of the date format is wrong or invalid.
97pub async fn parse_date_naive(date: String) -> Result<String> {
98    let naive_date = match NaiveDate::parse_from_str(&date, "%Y-%m-%d") {
99        Ok(date) => date,
100        Err(_error) => {
101            eprintln!("Date must be in the format of year-month-day/yyyy-mm-dd, input of {}, does not match requirements", date);
102            eprintln!("Can not querity Amber Api, exiting.");
103            //FIXME: unwind stack correctly
104            // this is not ideal, as it will not unwind the stack and makes it hard to test
105            // See sysexits.h for exit code 65: "EX_DATAERR".
106            process::exit(65);
107        }
108    };
109
110    let valid_date = naive_date.to_string();
111    Ok(valid_date)
112}
113
114/// CVS writer for historical data
115pub async fn write_data_as_csv_to_file(file_name: String, data: Vec<UsageData>) -> Result<()> {
116    // We must use the builder to disable auto header generation.
117    // See: https://docs.rs/csv/latest/csv/struct.Writer.html#structs
118    // struct felid that causes this is "pub tariff_information: TariffInformation"
119    let mut writer = WriterBuilder::new()
120        .has_headers(false)
121        .from_path(file_name.clone())?;
122
123    println!("Writing to file: {}", file_name);
124
125    // Write headers for usage data
126    writer.write_record([
127        "Type",
128        "duration",
129        "date",
130        "end_date",
131        "quality",
132        "kwh",
133        "nem_time",
134        "per_kwh",
135        "channel_type",
136        "channel_identifier",
137        "cost",
138        "renewables",
139        "spot_per_kwh",
140        "start_time",
141        "spike_status",
142        "tariff_information",
143        "descriptor",
144    ])?;
145
146    println!("Writing dataset headers to file...");
147
148    println!("Startng to write records to file...");
149    for data_point in data {
150        writer.serialize(data_point)?;
151        writer.flush()?;
152    }
153    println!("Finished writing records to file");
154    Ok(())
155}