├── .gitignore ├── .ocamlformat ├── CHANGES.md ├── LICENSE ├── README.md ├── carbon.opam ├── dune ├── dune-project ├── example ├── dune └── main.ml └── src ├── carbon.ml ├── carbon.mli ├── carbon_intf.ml ├── co2_signal.ml ├── co2_signal.mli ├── dune ├── fr.ml ├── fr.mli ├── gb.ml ├── gb.mli ├── http_client.ml ├── include ├── README.md ├── co2_signal_zones.ml ├── dune └── zones.json └── j.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | _opam 3 | .vscode 4 | .co2-token -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version=0.26.2 -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0 (2024-11-26) Cambridge 2 | 3 | - Update to eio.1.1 and cohttp-eio.6.0.0~beta2. 4 | - Use a TLS authenticator with ca-certs. 5 | 6 | ## 0.1.0 (2023-07-03) Cambridge 7 | 8 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2022-2023 Patrick Ferris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | carbon-intensity 2 | ---------------- 3 | 4 | Carbon Intensity is an OCaml client for querying various energy grid APIs to understand the energy generation mix. This enables programs like schedulers, energy monitors etc. to have a better understanding of their carbon intensity. 5 | 6 | The API provides geographic-specific services, which allow you to exploit more fine-grained APIs and then a generic `Intensity` interface for a global-oriented API. 7 | 8 | ### Integrated APIs 9 | 10 | - Great Britain: 11 | + https://www.carbonintensity.org.uk/ 12 | - France: 13 | + https://www.rte-france.com/eco2mix 14 | - Misc: 15 | + https://www.co2signal.com (requires API key) 16 | 17 | ### Usage 18 | 19 | A very simple use of the region specific API for Great Britain only requires the user to provide Eio's network capability. 20 | 21 | 22 | ```ocaml 23 | # Eio_main.run @@ fun env -> 24 | Mirage_crypto_rng_eio.run (module Mirage_crypto_rng.Fortuna) env @@ fun _ -> 25 | let gb = Carbon.Gb.v env#net in 26 | Carbon.Gb.get_intensity gb 27 | |> Eio.traceln "%a" Carbon.Gb.Intensity.pp;; 28 | +period: 2024-11-25T21:00Z - 2024-11-25T21:30Z 29 | +forecast: 99 gCO2/kWh 30 | +actual: 92 gCO2/kWh 31 | +index: low 32 | + 33 | - : unit = () 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /carbon.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | synopsis: "OCaml library for accessing various Carbon Intensity APIs" 3 | description: "Carbon provides access to various APIs to discover carbon intensity information for different countries." 4 | maintainer: ["patrick@sirref.org"] 5 | authors: ["Patrick Ferris"] 6 | license: "MIT" 7 | homepage: "https://github.com/geocaml/carbon-intensity" 8 | bug-reports: "https://github.com/geocaml/carbon-intensity/issues" 9 | depends: [ 10 | "dune" {>= "3.0"} 11 | "eio" {>= "1.1.0"} 12 | "ca-certs" 13 | "cohttp-eio" {>= "6.0.0~beta2"} 14 | "ezjsonm" 15 | "ptime" 16 | "tls-eio" {>= "1.0.4"} 17 | "uri" 18 | "mirage-crypto-rng-eio" {with-test} 19 | "mdx" {with-test} 20 | "eio_main" {with-test} 21 | "odoc" {with-doc} 22 | ] 23 | build: [ 24 | ["dune" "subst"] {dev} 25 | [ 26 | "dune" 27 | "build" 28 | "-p" 29 | name 30 | "-j" 31 | jobs 32 | "--promote-install-files=false" 33 | "@install" 34 | "@runtest" {with-test} 35 | "@doc" {with-doc} 36 | ] 37 | ["dune" "install" "-p" name "--create-install-files" name] 38 | ] 39 | dev-repo: "git+https://github.com/geocaml/carbon-intensity.git" -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (mdx 2 | (package carbon) 3 | (libraries 4 | eio 5 | fmt 6 | eio_main 7 | eio.unix 8 | mirage-crypto-rng 9 | mirage-crypto-rng-eio 10 | carbon) 11 | (files README.md)) 12 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.11) 2 | (using mdx 0.4) 3 | (name carbon) 4 | -------------------------------------------------------------------------------- /example/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (libraries carbon mirage-crypto-rng-eio eio_main)) 4 | -------------------------------------------------------------------------------- /example/main.ml: -------------------------------------------------------------------------------- 1 | let co2_signal env = 2 | match Eio.Path.(load (env#fs / ".co2-token")) with 3 | | exception Eio.Io _ -> () 4 | | api_key -> 5 | let co2_signal = Carbon.Co2_signal.v ~api_key env#net in 6 | Carbon.Co2_signal.get_intensity ~zone:Carbon.Co2_signal.Zone.dk_dk1 7 | co2_signal 8 | |> Eio.traceln "INDIA:@.%a" Carbon.Co2_signal.Intensity.pp 9 | 10 | let gb env = 11 | let gb = Carbon.Gb.v env#net in 12 | Carbon.Gb.get_intensity gb |> Eio.traceln "GB:@.%a" Carbon.Gb.Intensity.pp 13 | 14 | let fr env = 15 | let fr = Carbon.Fr.v env#net in 16 | Carbon.Fr.get fr |> Eio.traceln "FR:@.%d" 17 | 18 | let () = 19 | Eio_main.run @@ fun env -> 20 | Mirage_crypto_rng_eio.run (module Mirage_crypto_rng.Fortuna) env @@ fun _ -> 21 | co2_signal env; 22 | gb env; 23 | fr env 24 | -------------------------------------------------------------------------------- /src/carbon.ml: -------------------------------------------------------------------------------- 1 | module Gb = Gb 2 | module Fr = Fr 3 | module Co2_signal = Co2_signal 4 | 5 | module Intensity = struct 6 | module Gb = struct 7 | type t = Gb.t 8 | 9 | let get_intensity (t : Gb.t) = 10 | let i = Gb.get_intensity t in 11 | Gb.Intensity.actual i 12 | end 13 | 14 | module Fr = struct 15 | type t = Fr.t 16 | 17 | let get_intensity t = Some (Fr.get t) 18 | end 19 | 20 | module Co2_signal (C : sig 21 | val zone : Zone.t 22 | end) = 23 | struct 24 | type t = Co2_signal.t 25 | 26 | let get_intensity t = 27 | let i = Co2_signal.get_intensity ~zone:C.zone t in 28 | Some (Co2_signal.Intensity.intensity i) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /src/carbon.mli: -------------------------------------------------------------------------------- 1 | (** [Carbon] is a library for inquiring information about the {b carbon intensity} 2 | of energy grids around the world. It integrates various APIs to be able to 3 | do this. Some require API keys others do not. 4 | 5 | Carbon intensity values are measured in {e grams of carbon dioxide equivalent 6 | per kilowatt hour}. Carbon dioxide equivalent (CO{_ 2}-eq) is a unit for measuring 7 | the impact of various {e greenhouse gases}. This makes it possible to compare say 8 | methane and CO{_2} for their {e global-warming potential}. 9 | 10 | A killowatt hour is a measurement of energy use. 1kWh is the energy used by an appliance 11 | with a 1kW power rating for one hour. 12 | 13 | So far there are two APIs available for use: 14 | 15 | {ul {- {!Gb} uses {{: https://carbonintensity.org.uk} carbonintensity.org.uk} which does 16 | not require any API key, but it only works for Great Britain.} 17 | {- {!Co2_signal} uses {{: https://www.co2signal.com} co2signal.com} which {e does} require 18 | an API key but has many more countries available.}} 19 | *) 20 | 21 | (** {2 Low-level API Access} *) 22 | 23 | module Gb = Gb 24 | module Fr = Fr 25 | module Co2_signal = Co2_signal 26 | 27 | (** {2 Generic Interface}*) 28 | 29 | module Intensity : sig 30 | module Gb : Carbon_intf.Intensity with type t = Gb.t 31 | module Fr : Carbon_intf.Intensity with type t = Fr.t 32 | 33 | module Co2_signal (_ : sig 34 | val zone : Co2_signal.Zone.t 35 | end) : Carbon_intf.Intensity with type t = Co2_signal.t 36 | 37 | (** The generic interface for [Co2_signal] requires you to provide 38 | a specific zone to use. For example: 39 | {[ 40 | module France = Co2_signal (struct let zone = Co2_signal.Zone.fr end) 41 | ]} 42 | *) 43 | end 44 | -------------------------------------------------------------------------------- /src/carbon_intf.ml: -------------------------------------------------------------------------------- 1 | module type Intensity = sig 2 | type t 3 | (** A configuration parameter for the API *) 4 | 5 | val get_intensity : t -> int option 6 | (** [get_intensity t] returns the current gCO2/kWh if available. *) 7 | end 8 | -------------------------------------------------------------------------------- /src/co2_signal.ml: -------------------------------------------------------------------------------- 1 | module Zone = Zone 2 | 3 | type t = { token : string; net : [ `Generic ] Eio.Net.ty Eio.Net.t } 4 | 5 | let v ~api_key net = 6 | { token = api_key; net :> [ `Generic ] Eio.Net.ty Eio.Net.t } 7 | 8 | module Endpoints = struct 9 | let base = "api.co2signal.com" 10 | let version = "v1" 11 | 12 | let uri ~path ~query = 13 | Uri.make ~scheme:"https" ~host:base ~path:(version ^ "/" ^ path) ~query () 14 | end 15 | 16 | let headers t = 17 | Http.Header.of_list 18 | [ 19 | ("Accept", "application/json"); 20 | ("auth-token", t.token); 21 | ("Host", Endpoints.base); 22 | ] 23 | 24 | module Intensity = struct 25 | type t = { 26 | zone : Zone.t; 27 | datetime : string; 28 | carbon_intensity : int; 29 | fossil_fuel_percentage : float; 30 | } 31 | 32 | let intensity t = t.carbon_intensity 33 | let datetime t = t.datetime 34 | let zone t = t.zone 35 | let pp_co2 ppf v = Fmt.pf ppf "%i gCO2/kWh" v 36 | 37 | let pp ppf t = 38 | Fmt.pf ppf 39 | "zone: %s@.datetime: %s@.intensity: %a@.fossil fuel percentage: %f" 40 | (Zone.to_string t.zone) t.datetime pp_co2 t.carbon_intensity 41 | t.fossil_fuel_percentage 42 | end 43 | 44 | let get_intensity ~zone t = 45 | let headers = headers t in 46 | let resource = 47 | Endpoints.uri ~path:"latest" 48 | ~query:[ ("countryCode", [ Zone.to_string zone ]) ] 49 | |> Uri.path_and_query 50 | in 51 | let data = 52 | Http_client.get_json ~net:t.net ~headers Endpoints.(base, resource) 53 | |> J.path [ "data" ] 54 | in 55 | Fmt.pr "%s" (Ezjsonm.value_to_string data); 56 | match 57 | ( J.find data [ "datetime" ], 58 | J.find data [ "carbonIntensity" ], 59 | J.find data [ "fossilFuelPercentage" ] ) 60 | with 61 | | Some date, Some ci, Some ffp -> 62 | { 63 | Intensity.zone; 64 | datetime = J.to_string date; 65 | carbon_intensity = J.to_int ci; 66 | fossil_fuel_percentage = J.to_float ffp; 67 | } 68 | | _ -> invalid_arg "Malformed JSON data for get_intensity" 69 | -------------------------------------------------------------------------------- /src/co2_signal.mli: -------------------------------------------------------------------------------- 1 | type t 2 | (** A configuration value for accessing the data *) 3 | 4 | module Zone = Zone 5 | (** Possible Zones as defined by the Electric Map API 6 | for more information see {{: https://static.electricitymaps.com/api/docs/index.html#zones} the zones API}. *) 7 | 8 | val v : api_key:string -> _ Eio.Net.t -> t 9 | (** [v api_key] constructs a new configuration value using the provided 10 | [api_key]. *) 11 | 12 | module Intensity : sig 13 | type t 14 | (** A value returned from the API for intensity data. *) 15 | 16 | val intensity : t -> int 17 | (** The CO{_2}-eq intensity (gCO{_2}-eq/kWh) *) 18 | 19 | val zone : t -> Zone.t 20 | (** The zone code for the data *) 21 | 22 | val datetime : t -> string 23 | (** The datetime for the value. *) 24 | 25 | val pp : t Fmt.t 26 | (** A pretty printer for intensity values. *) 27 | end 28 | 29 | val get_intensity : zone:Zone.t -> t -> Intensity.t 30 | (** [get_intensity ~net ~zone t] will try to get the intensity values for 31 | a particular zone by calling the API. *) 32 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name carbon) 3 | (public_name carbon) 4 | (libraries ptime ca-certs ezjsonm eio.unix tls-eio cohttp-eio uri)) 5 | 6 | (rule 7 | (deps ./include/zones.json ./include/co2_signal_zones.exe) 8 | (targets zone.ml zone.mli) 9 | (action 10 | (progn 11 | (with-stdout-to 12 | zone.ml 13 | (run ./include/co2_signal_zones.exe impl)) 14 | (with-stdout-to 15 | zone.mli 16 | (run ./include/co2_signal_zones.exe intf))))) 17 | 18 | (env 19 | (dev 20 | (flags 21 | (:standard -w -69)))) 22 | -------------------------------------------------------------------------------- /src/fr.ml: -------------------------------------------------------------------------------- 1 | type t = { net : [ `Generic ] Eio.Net.ty Eio.Net.t } 2 | 3 | let v net = { net :> [ `Generic ] Eio.Net.ty Eio.Net.t } 4 | let url_host = "odre.opendatasoft.com" 5 | let url_path = "/api/explore/v2.0/catalog/datasets/eco2mix-national-tr/records" 6 | let encode_value = Stringext.replace_all ~pattern:" " ~with_:"%20" 7 | 8 | let query_params = 9 | [ 10 | ("select", "taux_co2"); 11 | ("where", "taux_co2 is not null"); 12 | ("order_by", "date_heure desc"); 13 | ("limit", "1"); 14 | ] 15 | 16 | let extract_from_response json = 17 | json |> J.path [ "records" ] |> J.only 18 | |> J.path [ "record"; "fields"; "taux_co2" ] 19 | |> J.to_int 20 | 21 | let get { net } = 22 | let query_string = 23 | Printf.sprintf "%s?%s" url_path 24 | (List.map 25 | (fun (k, v) -> Printf.sprintf "%s=%s" k (encode_value v)) 26 | query_params 27 | |> String.concat "&") 28 | in 29 | Http_client.get_json ~net 30 | ~headers:(Http.Header.of_list [ ("Host", url_host) ]) 31 | (url_host, query_string) 32 | |> extract_from_response 33 | -------------------------------------------------------------------------------- /src/fr.mli: -------------------------------------------------------------------------------- 1 | type t 2 | (** A configuration value for accessing the data *) 3 | 4 | val v : _ Eio.Net.t -> t 5 | (** Creates a new configuration value with network access. *) 6 | 7 | val get : t -> int 8 | (** Get the newest data for carbon intensity in gCO2/kWh. *) 9 | -------------------------------------------------------------------------------- /src/gb.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2022 Geocaml Developers 3 | Distributed under the ISC license, see terms at the end of the file. 4 | ---------------------------------------------------------------------------*) 5 | type t = { net : [ `Generic ] Eio.Net.ty Eio.Net.t } 6 | 7 | let v net = { net :> [ `Generic ] Eio.Net.ty Eio.Net.t } 8 | 9 | module Endpoints = struct 10 | let ( // ) a b = a ^ "/" ^ b 11 | let base = "api.carbonintensity.org.uk" 12 | let intensity = "/intensity" 13 | 14 | let intensity_from ~from t = 15 | let from = Ptime.to_rfc3339 from in 16 | match t with 17 | | `Fw24 -> intensity // from // "fw24h" 18 | | `Fw48 -> intensity // "fw48h" 19 | | `Pt24 -> intensity // "pt24h" 20 | | `To iso -> intensity // Ptime.to_rfc3339 iso 21 | end 22 | 23 | module Period = struct 24 | type t = { from_ : string; to_ : string } 25 | 26 | let from t = t.from_ 27 | let to_ t = t.to_ 28 | let pp ppf t = Fmt.pf ppf "%s - %s" t.from_ t.to_ 29 | end 30 | 31 | module Error = struct 32 | type t = { code : string; message : string } 33 | end 34 | 35 | module Intensity = struct 36 | type index = [ `Very_low | `Low | `Moderate | `High | `Very_high ] 37 | 38 | type t = { 39 | period : Period.t; 40 | forecast : int; 41 | actual : int option; 42 | index : index; 43 | } 44 | 45 | let period t = t.period 46 | let forecast t = t.forecast 47 | let actual t = t.actual 48 | let index t = t.index 49 | 50 | let index_of_string = function 51 | | "very low" -> `Very_low 52 | | "low" -> `Low 53 | | "moderate" -> `Moderate 54 | | "high" -> `High 55 | | "very high" -> `Very_high 56 | | s -> invalid_arg s 57 | 58 | let index_to_string = function 59 | | `Very_low -> "very low" 60 | | `Low -> "low" 61 | | `Moderate -> "moderate" 62 | | `High -> "high" 63 | | `Very_high -> "very high" 64 | 65 | let of_json json = 66 | match (J.find json [ "from" ], J.find json [ "to" ]) with 67 | | Some from_, Some to_ -> ( 68 | let from_ = J.to_string from_ in 69 | let to_ = J.to_string to_ in 70 | match 71 | ( J.find json [ "intensity"; "forecast" ], 72 | J.find json [ "intensity"; "actual" ], 73 | J.find json [ "intensity"; "index" ] ) 74 | with 75 | | Some forecast, Some actual, Some index -> 76 | let forecast = J.to_int forecast in 77 | let actual = Option.map J.to_int (J.null_to_option actual) in 78 | let index = J.to_string index |> index_of_string in 79 | { period = { from_; to_ }; forecast; actual; index } 80 | | _ -> invalid_arg "JSON has malformed intensity object") 81 | | _ -> invalid_arg "JSON does not have keys `from' and `to'" 82 | 83 | let pp_gco2 ppf = function 84 | | Some v -> Fmt.pf ppf "%i gCO2/kWh" v 85 | | None -> Fmt.pf ppf "None" 86 | 87 | let pp ppf t = 88 | Fmt.pf ppf "period: %a@.forecast: %a@.actual: %a@.index: %s@." Period.pp 89 | t.period pp_gco2 (Some t.forecast) pp_gco2 t.actual 90 | (index_to_string t.index) 91 | end 92 | 93 | module Factors = struct 94 | type t = { 95 | biomass : int; 96 | coal : int; 97 | dutch_imports : int; 98 | french_imports : int; 99 | gas_combined_cycle : int; 100 | gas_open_cycle : int; 101 | hydro : int; 102 | irish_imports : int; 103 | nuclear : int; 104 | oil : int; 105 | other : int; 106 | pumped_storage : int; 107 | solar : int; 108 | wind : int; 109 | } 110 | end 111 | 112 | let headers = 113 | Http.Header.of_list 114 | [ ("Accept", "application/json"); ("Host", "api.carbonintensity.org.uk") ] 115 | 116 | let get_intensity t = 117 | Http_client.get_json ~headers ~net:t.net Endpoints.(base, intensity) 118 | |> J.path [ "data" ] 119 | |> Ezjsonm.get_list Intensity.of_json 120 | |> List.hd 121 | 122 | let get_intensity_period ~period:(from, to_) t = 123 | (Http_client.get_json ~headers ~net:t.net 124 | @@ Endpoints.(base, intensity_from ~from to_)) 125 | |> J.path [ "data" ] 126 | |> Ezjsonm.get_list Intensity.of_json 127 | 128 | (*--------------------------------------------------------------------------- 129 | Copyright (c) 2022 Patrick Ferris 130 | 131 | Permission to use, copy, modify, and/or distribute this software for any 132 | purpose with or without fee is hereby granted, provided that the above 133 | copyright notice and this permission notice appear in all copies. 134 | 135 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 136 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 137 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 138 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 139 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 140 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 141 | DEALINGS IN THE SOFTWARE. 142 | ---------------------------------------------------------------------------*) 143 | -------------------------------------------------------------------------------- /src/gb.mli: -------------------------------------------------------------------------------- 1 | (** {1 Energy Mix for Great Britain } 2 | 3 | This plugin provides a client for interacting with {{: https://www.carbonintensity.org.uk/} the carbon-intensity} API. 4 | This includes intelligent forecasting models as well as current and past energy mixes. 5 | *) 6 | 7 | type t 8 | (** A configuration value for accessing the data *) 9 | 10 | val v : _ Eio.Net.t -> t 11 | (** Creates a new configuration value with network access. *) 12 | 13 | (** {2 Data types} *) 14 | 15 | module Period : sig 16 | type t 17 | (** A period of time as returned from the API *) 18 | 19 | val from : t -> string 20 | (** Start of the period (in ISO8601) *) 21 | 22 | val to_ : t -> string 23 | (** End of the period (in ISO8601) *) 24 | end 25 | 26 | module Error : sig 27 | type t = { code : string; message : string } 28 | (** An error code from the API *) 29 | end 30 | 31 | module Intensity : sig 32 | type t 33 | 34 | type index = [ `Very_low | `Low | `Moderate | `High | `Very_high ] 35 | (** The index is a measure of the Carbon Intensity represented as a scale *) 36 | 37 | val period : t -> Period.t 38 | (** The period of time for which the Carbon Intensity information is valid. *) 39 | 40 | val forecast : t -> int 41 | (** The forecast Carbon Intensity for the period in units gCO2/kWh *) 42 | 43 | val actual : t -> int option 44 | (** The forecast Carbon Intensity for the period in units gCO2/kWh. This is 45 | optional as the data might be for the future. *) 46 | 47 | val index : t -> index 48 | (** Extracts the index for a given intensity *) 49 | 50 | val pp : t Fmt.t 51 | (** A simple pretty printer for intensity information *) 52 | end 53 | 54 | module Factors : sig 55 | type t 56 | end 57 | 58 | val get_intensity : t -> Intensity.t 59 | 60 | val get_intensity_period : 61 | period:Ptime.t * [ `Fw24 | `Fw48 | `Pt24 | `To of Ptime.t ] -> 62 | t -> 63 | Intensity.t list 64 | -------------------------------------------------------------------------------- /src/http_client.ml: -------------------------------------------------------------------------------- 1 | open Cohttp_eio 2 | 3 | let authenticator = 4 | match Ca_certs.authenticator () with 5 | | Ok x -> x 6 | | Error (`Msg m) -> 7 | Fmt.failwith "Failed to create system store X509 authenticator: %s" m 8 | 9 | let https ~authenticator = 10 | let tls_config = 11 | match Tls.Config.client ~authenticator () with 12 | | Error (`Msg msg) -> failwith ("tls configuration problem: " ^ msg) 13 | | Ok tls_config -> tls_config 14 | in 15 | fun uri raw -> 16 | let host = 17 | Uri.host uri 18 | |> Option.map (fun x -> Domain_name.(host_exn (of_string_exn x))) 19 | in 20 | Tls_eio.client_of_flow ?host tls_config raw 21 | 22 | let get_json ?headers ~net (host, resource) = 23 | Eio.Switch.run @@ fun sw -> 24 | let uri = Uri.make ~scheme:"https" ~host ~path:resource () in 25 | let client = Client.make ~https:(Some (https ~authenticator)) net in 26 | let _body, resp = Client.get ~sw client ?headers uri in 27 | let s = Eio.Flow.read_all resp in 28 | Ezjsonm.value_from_string s 29 | -------------------------------------------------------------------------------- /src/include/README.md: -------------------------------------------------------------------------------- 1 | Zones are from https://api.electricitymap.org/v3/zones downloaded on 20-06-2023 -------------------------------------------------------------------------------- /src/include/co2_signal_zones.ml: -------------------------------------------------------------------------------- 1 | let replace_hyphens n = String.split_on_char '-' n |> String.concat "_" 2 | 3 | let check_keyword = function 4 | | "do" -> "do_" 5 | | "for" -> "for_" 6 | | "in" -> "in_" 7 | | "to" -> "to_" 8 | | s -> s 9 | 10 | let read_zones () = 11 | let zones = 12 | In_channel.with_open_bin "./include/zones.json" @@ fun ic -> 13 | Ezjsonm.from_channel ic 14 | in 15 | match zones with 16 | | `O assoc -> 17 | let f (n, obj) = 18 | match obj with 19 | | `O [ ("zoneName", `String name) ] -> 20 | (n, Fmt.str "This is the zone %s" name) 21 | | `O [ ("countryName", `String country); ("zoneName", `String name) ] -> 22 | (n, Fmt.str "This is the zone %s in country %s" name country) 23 | | _ -> failwith "expected a zonename" 24 | in 25 | List.map f assoc 26 | | _ -> failwith "Expected dictionary" 27 | 28 | let impl () = 29 | let zones = read_zones () in 30 | let impl (name, _) = 31 | let ocaml_name = 32 | String.lowercase_ascii name |> replace_hyphens |> check_keyword 33 | in 34 | Fmt.str {|let %s = "%s"|} ocaml_name name 35 | in 36 | Fmt.pr "type t = string\n\n"; 37 | List.iter (fun v -> Fmt.pr "%s\n\n" (impl v)) zones; 38 | Fmt.pr "let of_string v = v\n\n"; 39 | Fmt.pr "let to_string v = v" 40 | 41 | let intf () = 42 | let zones = read_zones () in 43 | let impl (name, d) = 44 | let ocaml_name = 45 | String.lowercase_ascii name |> replace_hyphens |> check_keyword 46 | in 47 | Fmt.str {|val %s : t 48 | (** %s *)|} ocaml_name d 49 | in 50 | Fmt.pr "type t\n\n"; 51 | List.iter (fun v -> Fmt.pr "%s\n\n" (impl v)) zones; 52 | Fmt.pr "val of_string : string -> t\n\n"; 53 | Fmt.pr "val to_string : t -> string\n\n" 54 | 55 | let () = 56 | match Sys.argv.(1) with 57 | | "impl" -> impl () 58 | | "intf" -> intf () 59 | | m -> 60 | failwith 61 | ("Not a valid argument, should be either impl or intf but got " ^ m) 62 | -------------------------------------------------------------------------------- /src/include/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name co2_signal_zones) 3 | (libraries eio ezjsonm)) 4 | -------------------------------------------------------------------------------- /src/include/zones.json: -------------------------------------------------------------------------------- 1 | {"AD":{"zoneName":"Andorra"},"AE":{"zoneName":"United Arab Emirates"},"AF":{"zoneName":"Afghanistan"},"AG":{"zoneName":"Antigua and Barbuda"},"AL":{"zoneName":"Albania"},"AM":{"zoneName":"Armenia"},"AO":{"zoneName":"Angola"},"AR":{"zoneName":"Argentina"},"AT":{"zoneName":"Austria"},"AU":{"zoneName":"Australia"},"AU-NSW":{"countryName":"Australia","zoneName":"New South Wales"},"AU-NT":{"countryName":"Australia","zoneName":"Northern Territory"},"AU-QLD":{"countryName":"Australia","zoneName":"Queensland"},"AU-SA":{"countryName":"Australia","zoneName":"South Australia"},"AU-TAS":{"countryName":"Australia","zoneName":"Tasmania"},"AU-TAS-CBI":{"countryName":"Australia","zoneName":"Cape Barren Island"},"AU-TAS-FI":{"countryName":"Australia","zoneName":"Flinders Island"},"AU-TAS-KI":{"countryName":"Australia","zoneName":"King Island"},"AU-VIC":{"countryName":"Australia","zoneName":"Victoria"},"AU-WA":{"countryName":"Australia","zoneName":"Western Australia"},"AU-WA-RI":{"countryName":"Australia","zoneName":"Rottnest Island"},"AW":{"zoneName":"Aruba"},"AX":{"zoneName":"Åland Islands"},"AZ":{"zoneName":"Azerbaijan"},"BA":{"zoneName":"Bosnia and Herzegovina"},"BB":{"zoneName":"Barbados"},"BD":{"zoneName":"Bangladesh"},"BE":{"zoneName":"Belgium"},"BF":{"zoneName":"Burkina Faso"},"BG":{"zoneName":"Bulgaria"},"BH":{"zoneName":"Bahrain"},"BI":{"zoneName":"Burundi"},"BJ":{"zoneName":"Benin"},"BN":{"zoneName":"Brunei"},"BO":{"zoneName":"Bolivia"},"BR":{"zoneName":"Brazil"},"BR-CS":{"countryName":"Brazil","zoneName":"Central Brazil"},"BR-N":{"countryName":"Brazil","zoneName":"North Brazil"},"BR-NE":{"countryName":"Brazil","zoneName":"North-East Brazil"},"BR-S":{"countryName":"Brazil","zoneName":"South Brazil"},"BS":{"zoneName":"Bahamas"},"BT":{"zoneName":"Bhutan"},"BW":{"zoneName":"Botswana"},"BY":{"zoneName":"Belarus"},"BZ":{"zoneName":"Belize"},"CA-AB":{"countryName":"Canada","zoneName":"Alberta"},"CA-BC":{"countryName":"Canada","zoneName":"British Columbia"},"CA-MB":{"countryName":"Canada","zoneName":"Manitoba"},"CA-NB":{"countryName":"Canada","zoneName":"New Brunswick"},"CA-NL-LB":{"countryName":"Canada","zoneName":"Labrador"},"CA-NL-NF":{"countryName":"Canada","zoneName":"Newfoundland"},"CA-NS":{"countryName":"Canada","zoneName":"Nova Scotia"},"CA-NT":{"countryName":"Canada","zoneName":"Northwest Territories"},"CA-NU":{"countryName":"Canada","zoneName":"Nunavut"},"CA-ON":{"countryName":"Canada","zoneName":"Ontario"},"CA-PE":{"countryName":"Canada","zoneName":"Prince Edward Island"},"CA-QC":{"countryName":"Canada","zoneName":"Québec"},"CA-SK":{"countryName":"Canada","zoneName":"Saskatchewan"},"CA-YT":{"countryName":"Canada","zoneName":"Yukon"},"CD":{"zoneName":"Democratic Republic of the Congo"},"CF":{"zoneName":"Central African Republic"},"CG":{"zoneName":"Congo"},"CH":{"zoneName":"Switzerland"},"CI":{"zoneName":"Ivory Coast"},"CL-CHP":{"countryName":"Chile","zoneName":"Easter Island"},"CL-SEA":{"countryName":"Chile","zoneName":"Sistema Eléctrico de Aysén"},"CL-SEM":{"countryName":"Chile","zoneName":"Sistema Eléctrico de Magallanes"},"CL-SEN":{"countryName":"Chile","zoneName":"Sistema Eléctrico Nacional"},"CM":{"zoneName":"Cameroon"},"CN":{"zoneName":"China"},"CO":{"zoneName":"Colombia"},"CR":{"zoneName":"Costa Rica"},"CU":{"zoneName":"Cuba"},"CV":{"zoneName":"Cabo Verde"},"CY":{"zoneName":"Cyprus"},"CZ":{"zoneName":"Czechia"},"DE":{"zoneName":"Germany"},"DJ":{"zoneName":"Djibouti"},"DK":{"zoneName":"Denmark"},"DK-BHM":{"countryName":"Denmark","zoneName":"Bornholm"},"DK-DK1":{"countryName":"Denmark","zoneName":"West Denmark"},"DK-DK2":{"countryName":"Denmark","zoneName":"East Denmark"},"DM":{"zoneName":"Dominica"},"DO":{"zoneName":"Dominican Republic"},"DZ":{"zoneName":"Algeria"},"EC":{"zoneName":"Ecuador"},"EE":{"zoneName":"Estonia"},"EG":{"zoneName":"Egypt"},"EH":{"zoneName":"Western Sahara"},"ER":{"zoneName":"Eritrea"},"ES":{"zoneName":"Spain"},"ES-CE":{"countryName":"Spain","zoneName":"Ceuta"},"ES-CN-FVLZ":{"countryName":"Spain","zoneName":"Fuerteventura/Lanzarote"},"ES-CN-GC":{"countryName":"Spain","zoneName":"Gran Canaria"},"ES-CN-HI":{"countryName":"Spain","zoneName":"El Hierro"},"ES-CN-IG":{"countryName":"Spain","zoneName":"Isla de la Gomera"},"ES-CN-LP":{"countryName":"Spain","zoneName":"La Palma"},"ES-CN-TE":{"countryName":"Spain","zoneName":"Tenerife"},"ES-IB-FO":{"countryName":"Spain","zoneName":"Formentera"},"ES-IB-IZ":{"countryName":"Spain","zoneName":"Ibiza"},"ES-IB-MA":{"countryName":"Spain","zoneName":"Mallorca"},"ES-IB-ME":{"countryName":"Spain","zoneName":"Menorca"},"ES-ML":{"countryName":"Spain","zoneName":"Melilla"},"ET":{"zoneName":"Ethiopia"},"FI":{"zoneName":"Finland"},"FJ":{"zoneName":"Fiji"},"FK":{"zoneName":"Falkland Islands"},"FM":{"zoneName":"Micronesia"},"FO":{"zoneName":"Faroe Islands"},"FO-MI":{"countryName":"Faroe Islands","zoneName":"Main Islands"},"FO-SI":{"countryName":"Faroe Islands","zoneName":"South Island"},"FR":{"zoneName":"France"},"FR-COR":{"countryName":"France","zoneName":"Corsica"},"GA":{"zoneName":"Gabon"},"GB":{"zoneName":"Great Britain"},"GB-NIR":{"zoneName":"Northern Ireland"},"GB-ORK":{"countryName":"Great Britain","zoneName":"Orkney Islands"},"GB-ZET":{"zoneName":"Unknown"},"GE":{"zoneName":"Georgia"},"GF":{"zoneName":"French Guiana"},"GH":{"zoneName":"Ghana"},"GL":{"zoneName":"Greenland"},"GM":{"zoneName":"Gambia"},"GN":{"zoneName":"Guinea"},"GP":{"zoneName":"Guadeloupe"},"GQ":{"zoneName":"Equatorial Guinea"},"GR":{"zoneName":"Greece"},"GR-IS":{"countryName":"Greece","zoneName":"Aegean Islands"},"GS":{"zoneName":"South Georgia and the South Sandwich Islands"},"GT":{"zoneName":"Guatemala"},"GU":{"zoneName":"Guam"},"GW":{"zoneName":"Guinea-Bissau"},"GY":{"zoneName":"Guyana"},"HK":{"zoneName":"Hong Kong"},"HM":{"zoneName":"Heard Island and McDonald Islands"},"HN":{"zoneName":"Honduras"},"HR":{"zoneName":"Croatia"},"HT":{"zoneName":"Haiti"},"HU":{"zoneName":"Hungary"},"ID":{"zoneName":"Indonesia"},"IE":{"zoneName":"Ireland"},"IL":{"zoneName":"Israel"},"IM":{"zoneName":"Isle of Man"},"IN":{"zoneName":"India"},"IN-AN":{"countryName":"India","zoneName":"Andaman and Nicobar Islands"},"IN-DL":{"countryName":"India","zoneName":"Delhi"},"IN-EA":{"countryName":"India","zoneName":"Eastern India"},"IN-HP":{"countryName":"India","zoneName":"Himachal Pradesh"},"IN-KA":{"countryName":"India","zoneName":"Karnataka"},"IN-MH":{"countryName":"India","zoneName":"Maharashtra"},"IN-NE":{"countryName":"India","zoneName":"North Eastern India"},"IN-NO":{"countryName":"India","zoneName":"Northern India"},"IN-PB":{"countryName":"India","zoneName":"Punjab"},"IN-SO":{"countryName":"India","zoneName":"Southern India"},"IN-UP":{"countryName":"India","zoneName":"Uttar Pradesh"},"IN-UT":{"countryName":"India","zoneName":"Uttarakhand"},"IN-WE":{"countryName":"India","zoneName":"Western India"},"IQ":{"zoneName":"Iraq"},"IQ-KUR":{"countryName":"Iraq","zoneName":"Kurdistan"},"IR":{"zoneName":"Iran"},"IS":{"zoneName":"Iceland"},"IT":{"zoneName":"Italy"},"IT-CNO":{"countryName":"Italy","zoneName":"Central North Italy"},"IT-CSO":{"countryName":"Italy","zoneName":"Central South Italy"},"IT-NO":{"countryName":"Italy","zoneName":"North Italy"},"IT-SAR":{"countryName":"Italy","zoneName":"Sardinia"},"IT-SIC":{"countryName":"Italy","zoneName":"Sicily"},"IT-SO":{"countryName":"Italy","zoneName":"South Italy"},"JM":{"zoneName":"Jamaica"},"JO":{"zoneName":"Jordan"},"JP":{"zoneName":"Japan"},"JP-CB":{"countryName":"Japan","zoneName":"Chūbu"},"JP-CG":{"countryName":"Japan","zoneName":"Chūgoku"},"JP-HKD":{"countryName":"Japan","zoneName":"Hokkaidō"},"JP-HR":{"countryName":"Japan","zoneName":"Hokuriku"},"JP-KN":{"countryName":"Japan","zoneName":"Kansai"},"JP-KY":{"countryName":"Japan","zoneName":"Kyūshū"},"JP-ON":{"countryName":"Japan","zoneName":"Okinawa"},"JP-SK":{"countryName":"Japan","zoneName":"Shikoku"},"JP-TH":{"countryName":"Japan","zoneName":"Tōhoku"},"JP-TK":{"countryName":"Japan","zoneName":"Tōkyō"},"KE":{"zoneName":"Kenya"},"KG":{"zoneName":"Kyrgyzstan"},"KH":{"zoneName":"Cambodia"},"KM":{"zoneName":"Comoros"},"KP":{"zoneName":"North Korea"},"KR":{"zoneName":"South Korea"},"KW":{"zoneName":"Kuwait"},"KZ":{"zoneName":"Kazakhstan"},"LA":{"zoneName":"Laos"},"LB":{"zoneName":"Lebanon"},"LC":{"zoneName":"Saint Lucia"},"LI":{"zoneName":"Liechtenstein"},"LK":{"zoneName":"Sri Lanka"},"LR":{"zoneName":"Liberia"},"LS":{"zoneName":"Lesotho"},"LT":{"zoneName":"Lithuania"},"LU":{"zoneName":"Luxembourg"},"LV":{"zoneName":"Latvia"},"LY":{"zoneName":"Libya"},"MA":{"zoneName":"Morocco"},"MD":{"zoneName":"Moldova"},"ME":{"zoneName":"Montenegro"},"MG":{"zoneName":"Madagascar"},"MK":{"zoneName":"North Macedonia"},"ML":{"zoneName":"Mali"},"MM":{"zoneName":"Myanmar"},"MN":{"zoneName":"Mongolia"},"MQ":{"zoneName":"Martinique"},"MR":{"zoneName":"Mauritania"},"MT":{"zoneName":"Malta"},"MU":{"zoneName":"Mauritius"},"MW":{"zoneName":"Malawi"},"MX":{"zoneName":"Mexico"},"MX-BC":{"countryName":"Mexico","zoneName":"Baja California"},"MX-BCS":{"zoneName":"Unknown"},"MX-CE":{"countryName":"Mexico","zoneName":"Central"},"MX-NE":{"countryName":"Mexico","zoneName":"North East"},"MX-NO":{"countryName":"Mexico","zoneName":"North"},"MX-NW":{"countryName":"Mexico","zoneName":"North West"},"MX-OC":{"countryName":"Mexico","zoneName":"Occidental"},"MX-OR":{"countryName":"Mexico","zoneName":"Oriental"},"MX-PN":{"countryName":"Mexico","zoneName":"Peninsula"},"MY-EM":{"countryName":"Malaysia","zoneName":"Borneo"},"MY-WM":{"countryName":"Malaysia","zoneName":"Peninsula"},"MZ":{"zoneName":"Mozambique"},"NA":{"zoneName":"Namibia"},"NC":{"zoneName":"New Caledonia"},"NE":{"zoneName":"Niger"},"NG":{"zoneName":"Nigeria"},"NI":{"zoneName":"Nicaragua"},"NKR":{"zoneName":"Nagorno-Karabakh"},"NL":{"zoneName":"Netherlands"},"NO":{"zoneName":"Norway"},"NO-NO1":{"countryName":"Norway","zoneName":"Southeast Norway"},"NO-NO2":{"countryName":"Norway","zoneName":"Southwest Norway"},"NO-NO3":{"countryName":"Norway","zoneName":"Middle Norway"},"NO-NO4":{"countryName":"Norway","zoneName":"North Norway"},"NO-NO5":{"countryName":"Norway","zoneName":"West Norway"},"NP":{"zoneName":"Nepal"},"NZ":{"zoneName":"New Zealand"},"NZ-NZA":{"countryName":"New Zealand","zoneName":"Auckland Islands"},"NZ-NZC":{"countryName":"New Zealand","zoneName":"Chatham Islands"},"NZ-NZST":{"countryName":"New Zealand","zoneName":"Stewart Island"},"OM":{"zoneName":"Oman"},"PA":{"zoneName":"Panama"},"PE":{"zoneName":"Peru"},"PF":{"zoneName":"French Polynesia"},"PG":{"zoneName":"Papua New Guinea"},"PH":{"zoneName":"Philippines"},"PK":{"zoneName":"Pakistan"},"PL":{"zoneName":"Poland"},"PM":{"zoneName":"Saint Pierre and Miquelon"},"PR":{"zoneName":"Puerto Rico"},"PS":{"zoneName":"State of Palestine"},"PT":{"zoneName":"Portugal"},"PT-AC":{"countryName":"Portugal","zoneName":"Azores"},"PT-MA":{"countryName":"Portugal","zoneName":"Madeira"},"PW":{"zoneName":"Palau"},"PY":{"zoneName":"Paraguay"},"QA":{"zoneName":"Qatar"},"RE":{"zoneName":"Réunion"},"RO":{"zoneName":"Romania"},"RS":{"zoneName":"Serbia"},"RU":{"zoneName":"Russia"},"RU-1":{"countryName":"Russia","zoneName":"Europe-Ural"},"RU-2":{"countryName":"Russia","zoneName":"Siberia"},"RU-AS":{"countryName":"Russia","zoneName":"East"},"RU-EU":{"countryName":"Russia","zoneName":"Arctic"},"RU-FE":{"countryName":"Russia","zoneName":"Far East"},"RU-KGD":{"countryName":"Russia","zoneName":"Kaliningrad"},"RW":{"zoneName":"Rwanda"},"SA":{"zoneName":"Saudi Arabia"},"SB":{"zoneName":"Solomon Islands"},"SD":{"zoneName":"Sudan"},"SE":{"zoneName":"Sweden"},"SE-SE1":{"countryName":"Sweden","zoneName":"North Sweden"},"SE-SE2":{"countryName":"Sweden","zoneName":"North Central Sweden"},"SE-SE3":{"countryName":"Sweden","zoneName":"South Central Sweden"},"SE-SE4":{"countryName":"Sweden","zoneName":"South Sweden"},"SG":{"zoneName":"Singapore"},"SI":{"zoneName":"Slovenia"},"SJ":{"zoneName":"Svalbard and Jan Mayen"},"SK":{"zoneName":"Slovakia"},"SL":{"zoneName":"Sierra Leone"},"SN":{"zoneName":"Senegal"},"SO":{"zoneName":"Somalia"},"SR":{"zoneName":"Suriname"},"SS":{"zoneName":"South Sudan"},"ST":{"zoneName":"Sao Tome and Principe"},"SV":{"zoneName":"El Salvador"},"SY":{"zoneName":"Syria"},"SZ":{"zoneName":"Swaziland"},"TD":{"zoneName":"Chad"},"TF":{"zoneName":"French Southern Territories"},"TG":{"zoneName":"Togo"},"TH":{"zoneName":"Thailand"},"TJ":{"zoneName":"Tajikistan"},"TL":{"zoneName":"Timor-Leste"},"TM":{"zoneName":"Turkmenistan"},"TN":{"zoneName":"Tunisia"},"TO":{"zoneName":"Tonga"},"TR":{"zoneName":"Turkey"},"TT":{"zoneName":"Trinidad and Tobago"},"TW":{"zoneName":"Taiwan"},"TZ":{"zoneName":"Tanzania"},"UA":{"zoneName":"Ukraine"},"UA-CR":{"countryName":"Ukraine","zoneName":"Crimea"},"UG":{"zoneName":"Uganda"},"US":{"zoneName":"Contiguous United States"},"US-AK":{"countryName":"USA","zoneName":"Alaska"},"US-CAL-BANC":{"countryName":"USA","zoneName":"Balancing Authority Of Northern California"},"US-CAL-CISO":{"countryName":"USA","zoneName":"California Independent System Operator"},"US-CAL-IID":{"countryName":"USA","zoneName":"Imperial Irrigation District"},"US-CAL-LDWP":{"countryName":"USA","zoneName":"Los Angeles Department Of Water And Power"},"US-CAL-TIDC":{"countryName":"USA","zoneName":"Turlock Irrigation District"},"US-CAR-CPLE":{"countryName":"USA","zoneName":"Duke Energy Progress East"},"US-CAR-CPLW":{"countryName":"USA","zoneName":"Duke Energy Progress West"},"US-CAR-DUK":{"countryName":"USA","zoneName":"Duke Energy Carolinas"},"US-CAR-SC":{"countryName":"USA","zoneName":"South Carolina Public Service Authority"},"US-CAR-SCEG":{"countryName":"USA","zoneName":"South Carolina Electric & Gas Company"},"US-CAR-YAD":{"countryName":"USA","zoneName":"Alcoa Power Generating, Inc. Yadkin Division"},"US-CENT-SPA":{"countryName":"USA","zoneName":"Southwestern Power Administration"},"US-CENT-SWPP":{"countryName":"USA","zoneName":"Southwest Power Pool"},"US-FLA-FMPP":{"countryName":"USA","zoneName":"Florida Municipal Power Pool"},"US-FLA-FPC":{"countryName":"USA","zoneName":"Duke Energy Florida Inc"},"US-FLA-FPL":{"countryName":"USA","zoneName":"Florida Power & Light Company"},"US-FLA-GVL":{"countryName":"USA","zoneName":"Gainesville Regional Utilities"},"US-FLA-HST":{"countryName":"USA","zoneName":"City Of Homestead"},"US-FLA-JEA":{"countryName":"USA","zoneName":"Jacksonville Electric Authority"},"US-FLA-NSB":{"zoneName":"Unknown"},"US-FLA-SEC":{"countryName":"USA","zoneName":"Seminole Electric Cooperative"},"US-FLA-TAL":{"countryName":"USA","zoneName":"City Of Tallahassee"},"US-FLA-TEC":{"countryName":"USA","zoneName":"Tampa Electric Company"},"US-HI-HA":{"countryName":"USA","zoneName":"Hawaii"},"US-HI-KA":{"countryName":"USA","zoneName":"Kauai"},"US-HI-KH":{"countryName":"USA","zoneName":"Kahoolawe"},"US-HI-LA":{"countryName":"USA","zoneName":"Lanai"},"US-HI-MA":{"countryName":"USA","zoneName":"Maui"},"US-HI-MO":{"countryName":"USA","zoneName":"Molokai"},"US-HI-NI":{"countryName":"USA","zoneName":"Niihau"},"US-HI-OA":{"countryName":"USA","zoneName":"Oahu"},"US-MIDA-PJM":{"countryName":"USA","zoneName":"PJM Interconnection, Llc"},"US-MIDW-AECI":{"countryName":"USA","zoneName":"Associated Electric Cooperative, Inc."},"US-MIDW-LGEE":{"countryName":"USA","zoneName":"Louisville Gas And Electric Company And Kentucky Utilities"},"US-MIDW-MISO":{"countryName":"USA","zoneName":"Midcontinent Independent Transmission System Operator, Inc."},"US-NE-ISNE":{"countryName":"USA","zoneName":"Iso New England Inc."},"US-NW-AVA":{"countryName":"USA","zoneName":"Avista Corporation"},"US-NW-AVRN":{"countryName":"USA","zoneName":"Avangrid Renewables Cooperative"},"US-NW-BPAT":{"countryName":"USA","zoneName":"Bonneville Power Administration"},"US-NW-CHPD":{"countryName":"USA","zoneName":"PUD No. 1 Of Chelan County"},"US-NW-DOPD":{"countryName":"USA","zoneName":"PUD No. 1 Of Douglas County"},"US-NW-GCPD":{"countryName":"USA","zoneName":"PUD No. 2 Of Grant County, Washington"},"US-NW-GRID":{"countryName":"USA","zoneName":"Gridforce Energy Management, Llc"},"US-NW-GWA":{"countryName":"USA","zoneName":"Naturener Power Watch, Llc (Gwa)"},"US-NW-IPCO":{"countryName":"USA","zoneName":"Idaho Power Company"},"US-NW-NEVP":{"countryName":"USA","zoneName":"Nevada Power Company"},"US-NW-NWMT":{"countryName":"USA","zoneName":"Northwestern Energy"},"US-NW-PACE":{"countryName":"USA","zoneName":"Pacificorp East"},"US-NW-PACW":{"countryName":"USA","zoneName":"Pacificorp West"},"US-NW-PGE":{"countryName":"USA","zoneName":"Portland General Electric Company"},"US-NW-PSCO":{"countryName":"USA","zoneName":"Public Service Company Of Colorado"},"US-NW-PSEI":{"countryName":"USA","zoneName":"Puget Sound Energy"},"US-NW-SCL":{"countryName":"USA","zoneName":"Seattle City Light"},"US-NW-TPWR":{"countryName":"USA","zoneName":"City Of Tacoma, Department Of Public Utilities, Light Division"},"US-NW-WACM":{"countryName":"USA","zoneName":"Western Area Power Administration - Rocky Mountain Region"},"US-NW-WAUW":{"countryName":"USA","zoneName":"Western Area Power Administration UGP West"},"US-NW-WWA":{"countryName":"USA","zoneName":"Naturener Wind Watch, Llc"},"US-NY-NYIS":{"countryName":"USA","zoneName":"New York Independent System Operator"},"US-SE-AEC":{"zoneName":"Unknown"},"US-SE-SEPA":{"countryName":"USA","zoneName":"Southeastern Power Administration"},"US-SE-SOCO":{"countryName":"USA","zoneName":"Southern Company Services, Inc. - Trans"},"US-SW-AZPS":{"countryName":"USA","zoneName":"Arizona Public Service Company"},"US-SW-DEAA":{"countryName":"USA","zoneName":"Arlington Valley, LLC"},"US-SW-EPE":{"countryName":"USA","zoneName":"El Paso Electric Company"},"US-SW-GRIF":{"countryName":"USA","zoneName":"Griffith Energy, LLC"},"US-SW-GRMA":{"zoneName":"Unknown"},"US-SW-HGMA":{"countryName":"USA","zoneName":"New Harquahala Generating Company, LLC"},"US-SW-PNM":{"countryName":"USA","zoneName":"Public Service Company Of New Mexico"},"US-SW-SRP":{"countryName":"USA","zoneName":"Salt River Project"},"US-SW-TEPC":{"countryName":"USA","zoneName":"Tucson Electric Power Company"},"US-SW-WALC":{"countryName":"USA","zoneName":"Western Area Power Administration - Desert Southwest Region"},"US-TEN-TVA":{"countryName":"USA","zoneName":"Tennessee Valley Authority"},"US-TEX-ERCO":{"countryName":"USA","zoneName":"Electric Reliability Council Of Texas, Inc."},"UY":{"zoneName":"Uruguay"},"UZ":{"zoneName":"Uzbekistan"},"VC":{"zoneName":"Saint Vincent and the Grenadines"},"VE":{"zoneName":"Venezuela"},"VI":{"countryName":"USA","zoneName":"Virgin Islands"},"VN":{"zoneName":"Vietnam"},"VN-C":{"countryName":"Vietnam","zoneName":"Central Vietnam"},"VN-N":{"countryName":"Vietnam","zoneName":"Northern Vietnam"},"VN-S":{"countryName":"Vietnam","zoneName":"Southern Vietnam"},"VU":{"zoneName":"Vanuatu"},"WS":{"zoneName":"Samoa"},"XK":{"zoneName":"Kosovo"},"XX":{"zoneName":"Northern Cyprus"},"YE":{"zoneName":"Yemen"},"YT":{"zoneName":"Mayotte"},"ZA":{"zoneName":"South Africa"},"ZM":{"zoneName":"Zambia"},"ZW":{"zoneName":"Zimbabwe"}} -------------------------------------------------------------------------------- /src/j.ml: -------------------------------------------------------------------------------- 1 | let find = Ezjsonm.find_opt 2 | let to_list = Ezjsonm.get_list 3 | let to_string = Ezjsonm.get_string 4 | let to_int = Ezjsonm.get_int 5 | let to_float = Ezjsonm.get_float 6 | let null_to_option = function `Null -> None | v -> Some v 7 | let path keys v = Ezjsonm.find v keys 8 | let only = function `A [ v ] -> v | _ -> raise Not_found 9 | --------------------------------------------------------------------------------