vendor: update cargo-cxai-energy-0.1.0

This commit is contained in:
cx-git-agent
2026-04-26 16:48:11 +00:00
committed by GitHub
parent 3090065003
commit eb7dee624d
9 changed files with 2321 additions and 3 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d878deb8441897ecdd416011b49d2d2f6112e867"
},
"path_in_vcs": "crates/cxai-energy"
}
Generated
+1942
View File
File diff suppressed because it is too large Load Diff
+74
View File
@@ -0,0 +1,74 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "cxai-energy"
version = "0.1.0"
authors = ["CxAI-LLM <agent@cxai-studio.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "ERCOT energy market client — DAM/RT prices, demand, wind/solar, SCADA, forecasting"
homepage = "https://cxllm.io"
readme = false
license = "MIT"
repository = "https://git.cxllm-studio.com/CxAI-LLM/CxAI.Rust"
resolver = "2"
[lib]
name = "cxai_energy"
path = "src/lib.rs"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.cxai-sdk]
version = "0.1.0"
registry-index = "sparse+https://git.cxllm-studio.com/api/packages/CxAI-LLM/cargo/"
[dependencies.reqwest]
version = "0.12"
features = [
"json",
"rustls-tls",
"stream",
]
default-features = false
[dependencies.serde]
version = "1"
features = ["derive"]
[dependencies.serde_json]
version = "1"
[dependencies.thiserror]
version = "2"
[dependencies.tokio]
version = "1"
features = ["full"]
[dependencies.tracing]
version = "0.1"
[dev-dependencies.tokio]
version = "1"
features = [
"full",
"test-util",
"macros",
]
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "cxai-energy"
description = "ERCOT energy market client — DAM/RT prices, demand, wind/solar, SCADA, forecasting"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
authors.workspace = true
[dependencies]
cxai-sdk = { workspace = true }
tokio = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["test-util", "macros"] }
-3
View File
@@ -1,3 +0,0 @@
# cargo-cxai-energy-0.1.0
Cargo crate: cxai-energy-0.1.0
+63
View File
@@ -0,0 +1,63 @@
use cxai_sdk::models::energy::*;
use tracing::debug;
/// High-level ERCOT energy market client.
pub struct ErcotClient {
platform: cxai_sdk::CxPlatform,
}
impl ErcotClient {
pub fn new(platform: cxai_sdk::CxPlatform) -> Self {
Self { platform }
}
/// Get day-ahead market prices for a settlement point.
pub async fn dam_prices(&self, settlement_point: &str) -> cxai_sdk::CxResult<Vec<DamPrice>> {
debug!(settlement_point, "Fetching DAM prices");
self.platform.energy().dam_prices(settlement_point).await
}
/// Get real-time prices for a settlement point.
pub async fn rt_prices(&self, settlement_point: &str) -> cxai_sdk::CxResult<Vec<RtPrice>> {
debug!(settlement_point, "Fetching RT prices");
self.platform.energy().rt_prices(settlement_point).await
}
/// Get current system demand.
pub async fn system_demand(&self) -> cxai_sdk::CxResult<SystemDemand> {
self.platform.energy().system_demand().await
}
/// Get current wind generation.
pub async fn wind(&self) -> cxai_sdk::CxResult<WindGeneration> {
self.platform.energy().wind_generation().await
}
/// Get current solar generation.
pub async fn solar(&self) -> cxai_sdk::CxResult<SolarGeneration> {
self.platform.energy().solar_generation().await
}
/// Get full market summary.
pub async fn market_summary(&self) -> cxai_sdk::CxResult<MarketSummary> {
self.platform.energy().market_summary().await
}
/// Get ML prediction for energy data.
pub async fn predict(
&self,
prediction_type: &str,
model: &str,
target_date: &str,
target_hour: u8,
) -> cxai_sdk::CxResult<ErcotMlPrediction> {
debug!(
prediction_type,
model, target_date, target_hour, "ERCOT predict"
);
self.platform
.energy()
.predict(prediction_type, model, target_date, target_hour)
.await
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod ercot;
pub mod market;
pub mod scada;
pub use ercot::ErcotClient;
pub use market::MarketAnalyzer;
pub use scada::ScadaClient;
+130
View File
@@ -0,0 +1,130 @@
use cxai_sdk::models::energy::*;
use serde::{Deserialize, Serialize};
/// Market analysis utilities for ERCOT data.
pub struct MarketAnalyzer;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpreadAnalysis {
pub avg_dam: f64,
pub avg_rt: f64,
pub spread: f64,
pub max_spread: f64,
pub arbitrage_opportunities: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RenewableMix {
pub wind_mw: f64,
pub solar_mw: f64,
pub total_renewable_mw: f64,
pub demand_mw: f64,
pub renewable_percentage: f64,
}
impl MarketAnalyzer {
/// Calculate DAM/RT price spread.
pub fn spread(dam_prices: &[DamPrice], rt_prices: &[RtPrice]) -> SpreadAnalysis {
let avg_dam = if dam_prices.is_empty() {
0.0
} else {
dam_prices.iter().map(|p| p.price).sum::<f64>() / dam_prices.len() as f64
};
let avg_rt = if rt_prices.is_empty() {
0.0
} else {
rt_prices.iter().map(|p| p.price).sum::<f64>() / rt_prices.len() as f64
};
let spreads: Vec<f64> = dam_prices
.iter()
.zip(rt_prices.iter())
.map(|(d, r)| (d.price - r.price).abs())
.collect();
let max_spread = spreads.iter().cloned().fold(0.0_f64, f64::max);
let arbitrage_opportunities = spreads.iter().filter(|&&s| s > 10.0).count();
SpreadAnalysis {
avg_dam,
avg_rt,
spread: avg_dam - avg_rt,
max_spread,
arbitrage_opportunities,
}
}
/// Calculate renewable energy mix.
pub fn renewable_mix(
wind: &WindGeneration,
solar: &SolarGeneration,
demand: &SystemDemand,
) -> RenewableMix {
let total = wind.generation_mw + solar.generation_mw;
let pct = if demand.demand_mw > 0.0 {
(total / demand.demand_mw) * 100.0
} else {
0.0
};
RenewableMix {
wind_mw: wind.generation_mw,
solar_mw: solar.generation_mw,
total_renewable_mw: total,
demand_mw: demand.demand_mw,
renewable_percentage: pct,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{NaiveDate, Utc};
#[test]
fn spread_calculation() {
let dam = vec![DamPrice {
settlement_point: "HB_HOUSTON".into(),
delivery_date: NaiveDate::from_ymd_opt(2026, 4, 20).unwrap(),
hour_ending: 14,
price: 45.0,
timestamp: None,
}];
let rt = vec![RtPrice {
settlement_point: "HB_HOUSTON".into(),
price: 52.0,
interval: "15min".into(),
timestamp: Utc::now(),
}];
let analysis = MarketAnalyzer::spread(&dam, &rt);
assert_eq!(analysis.avg_dam, 45.0);
assert_eq!(analysis.avg_rt, 52.0);
assert!((analysis.spread - (-7.0)).abs() < f64::EPSILON);
}
#[test]
fn renewable_mix_calculation() {
let wind = WindGeneration {
generation_mw: 15000.0,
capacity_mw: 40000.0,
timestamp: Utc::now(),
};
let solar = SolarGeneration {
generation_mw: 8000.0,
capacity_mw: 20000.0,
timestamp: Utc::now(),
};
let demand = SystemDemand {
demand_mw: 60000.0,
timestamp: Utc::now(),
forecast_mw: None,
};
let mix = MarketAnalyzer::renewable_mix(&wind, &solar, &demand);
assert_eq!(mix.total_renewable_mw, 23000.0);
assert!((mix.renewable_percentage - 38.333333333333336).abs() < 0.001);
}
}
+77
View File
@@ -0,0 +1,77 @@
use cxai_sdk::models::energy::ScadaSnapshot;
use tracing::debug;
/// SCADA telemetry client for battery storage systems.
pub struct ScadaClient {
platform: cxai_sdk::CxPlatform,
}
impl ScadaClient {
pub fn new(platform: cxai_sdk::CxPlatform) -> Self {
Self { platform }
}
/// Get the latest SCADA snapshot for a device.
pub async fn snapshot(&self, device_id: &str) -> cxai_sdk::CxResult<ScadaSnapshot> {
debug!(device_id, "Fetching SCADA snapshot");
self.platform.energy().scada_snapshot(device_id).await
}
/// Check if a battery is within safe operating parameters.
pub fn is_safe(snapshot: &ScadaSnapshot) -> bool {
snapshot.soc_percent >= 5.0
&& snapshot.soc_percent <= 95.0
&& snapshot.temperature_c < 55.0
&& snapshot.soh_percent > 70.0
}
/// Calculate charge/discharge efficiency from a snapshot.
pub fn efficiency(snapshot: &ScadaSnapshot) -> f64 {
if snapshot.voltage_v == 0.0 || snapshot.current_a == 0.0 {
return 0.0;
}
let power_calc = snapshot.voltage_v * snapshot.current_a / 1000.0;
if power_calc == 0.0 {
return 0.0;
}
(snapshot.power_kw / power_calc).abs().min(1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
fn test_snapshot() -> ScadaSnapshot {
ScadaSnapshot {
device_id: "BESS-001".into(),
soc_percent: 65.0,
soh_percent: 92.0,
power_kw: 450.0,
voltage_v: 800.0,
current_a: 580.0,
temperature_c: 32.0,
timestamp: Utc::now(),
}
}
#[test]
fn safe_battery() {
assert!(ScadaClient::is_safe(&test_snapshot()));
}
#[test]
fn unsafe_temperature() {
let mut s = test_snapshot();
s.temperature_c = 60.0;
assert!(!ScadaClient::is_safe(&s));
}
#[test]
fn efficiency_calculation() {
let s = test_snapshot();
let eff = ScadaClient::efficiency(&s);
assert!(eff > 0.0 && eff <= 1.0);
}
}