Skip to content
This repository has been archived by the owner on Mar 3, 2025. It is now read-only.

Commit

Permalink
add experimental lint, normalize and explain features
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop22 committed Nov 21, 2024
1 parent 26233fa commit ca54cbd
Show file tree
Hide file tree
Showing 11 changed files with 807 additions and 29 deletions.
71 changes: 67 additions & 4 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@ pub enum Command {
///
/// This command will show you a breakdown of all the calculated costs.
Analyze(Analyze),
/// Lint a provided tariff.
Lint(Lint),
}

impl Command {
fn run(self) -> Result<()> {
match self {
Self::Validate(args) => args.run(),
Self::Analyze(args) => args.run(),
Self::Lint(args) => args.run(),
}
}
}
Expand Down Expand Up @@ -217,7 +220,7 @@ impl Validate {

table.row(&[
"Total Energy".into(),
report.total_energy.with_scale().to_string(),
report.total_energy.with_default_scale().to_string(),
cdr.total_energy.to_string(),
]);

Expand All @@ -226,7 +229,7 @@ impl Validate {
table.row(&[
"Total Cost (Excl.)".into(),
to_string_or_default(report.total_cost.map(|p| p.excl_vat)),
cdr.total_cost.with_scale().excl_vat.to_string(),
cdr.total_cost.with_default_scale().excl_vat.to_string(),
]);

table.row(&[
Expand All @@ -242,13 +245,21 @@ impl Validate {

table.row(&[
"Total Time Cost (Excl.)".into(),
to_string_or_default(report.total_time_cost.map(|p| p.with_scale().excl_vat)),
to_string_or_default(
report
.total_time_cost
.map(|p| p.with_default_scale().excl_vat),
),
to_string_or_default(cdr.total_time_cost.map(|p| p.excl_vat)),
]);

table.row(&[
"Total Time Cost (Incl.)".into(),
to_string_or_default(report.total_time_cost.and_then(|p| p.with_scale().incl_vat)),
to_string_or_default(
report
.total_time_cost
.and_then(|p| p.with_default_scale().incl_vat),
),
to_string_or_default(cdr.total_time_cost.and_then(|p| p.incl_vat)),
]);

Expand Down Expand Up @@ -522,3 +533,55 @@ enum Item {
fn to_string_or_default<T: Display>(v: Option<T>) -> String {
v.map(|v| v.to_string()).unwrap_or_default()
}

#[derive(Parser)]
pub struct Lint {
/// A path to the tariff structure in json format.
#[arg(short = 't', long)]
tariff: Option<PathBuf>,
/// The OCPI version that should be used for the input structures.
///
/// If the input consists of version 2.1.1 structures they will be converted to 2.2.1
/// structures. The actual calculation and output will always be according to OCPI 2.2.1.
///
/// use `detect` to let to tool try to find the matching version.
#[arg(short = 'o', long, value_enum, default_value_t = OcpiVersion::default())]
ocpi_version: OcpiVersion,
}

impl Lint {
fn run(self) -> Result<()> {
let tariff = if let Some(tariff) = &self.tariff {
let file = File::open(tariff).map_err(|e| Error::file(tariff.clone(), e))?;
from_reader_with_version::<_, _, v211::tariff::OcpiTariff>(file, self.ocpi_version)
.map_err(|e| Error::deserialize(tariff.display(), "Tariff", e))?
} else {
let mut stdin = stdin().lock();
from_reader_with_version::<_, _, v211::tariff::OcpiTariff>(
&mut stdin,
self.ocpi_version,
)
.map_err(|e| Error::deserialize("<stdin>", "Tariff", e))?
};

let warnings = ocpi_tariffs::lint::lint(&tariff);

println!(
"\n{} tariff `{}`:",
style("Linting").green().bold(),
style(
self.tariff
.map(|t| t.file_name().unwrap().to_string_lossy().to_string())
.unwrap_or("<stdin>".into())
)
.blue(),
);

for warn in warnings {
println!(" - {}", warn);
}

println!();
Ok(())
}
}
113 changes: 113 additions & 0 deletions ocpi-tariffs/src/explain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate::{
ocpi::v221::tariff::{OcpiTariff, OcpiTariffRestriction, TariffDimensionType},
types::money::Money,
};

#[derive(Debug)]
pub struct Explain {
pub elements: Vec<ExplainElement>,
}

#[derive(Debug)]
pub struct ExplainElement {
pub restrictions: Vec<String>,
pub components: ExplainComponents,
}

#[derive(Debug, Default)]
pub struct ExplainComponents {
pub energy: Option<Money>,
pub flat: Option<Money>,
pub time: Option<Money>,
pub parking_time: Option<Money>,
}

pub fn explain(tariff: &OcpiTariff) -> Explain {
let mut elements = Vec::new();

for element in &tariff.elements {
let mut components = ExplainComponents::default();

for component in &element.price_components {
match component.component_type {
TariffDimensionType::Flat => components.flat = Some(component.price.with_scale(2)),
TariffDimensionType::Time => components.time = Some(component.price.with_scale(2)),
TariffDimensionType::Energy => {
components.energy = Some(component.price.with_scale(2))
}
TariffDimensionType::ParkingTime => {
components.parking_time = Some(component.price.with_scale(2))
}
}
}

let restrictions = element
.restrictions
.as_ref()
.map(|restr| explain_restrictions(restr))
.unwrap_or_default();

elements.push(ExplainElement {
restrictions,
components,
});
}

Explain { elements }
}

/// Explain the given restriction.
pub fn explain_restrictions(restr: &OcpiTariffRestriction) -> Vec<String> {
let mut explains = Vec::new();

if let Some((min_kwh, max_kwh)) = restr.min_kwh.zip(restr.max_kwh) {
explains.push(format!(
"total energy is between {} and {} kWh",
min_kwh.normalize(),
max_kwh.normalize()
));
} else if let Some(min_kwh) = restr.min_kwh {
explains.push(format!("total energy exceeds {} kWh", min_kwh.normalize()));
} else if let Some(max_kwh) = restr.max_kwh {
explains.push(format!(
"total energy is less than {} kWh",
max_kwh.normalize()
));
}

if let Some((start_time, end_time)) = restr.start_time.zip(restr.end_time) {
explains.push(format!("between {} and {}", start_time, end_time));
} else if let Some(start_time) = restr.start_time {
explains.push(format!("after {}", start_time));
} else if let Some(end_time) = restr.end_time {
explains.push(format!("before {}", end_time));
}

if let Some((min_duration, max_duration)) = restr.min_duration.zip(restr.max_duration) {
explains.push(format!(
"session duration is between {} and {} hours",
min_duration.hours(),
max_duration.hours()
));
} else if let Some(min_duration) = restr.min_duration {
explains.push(format!(
"session duration exceeds {} hours",
min_duration.hours(),
));
} else if let Some(max_duration) = restr.max_duration {
explains.push(format!(
"session duration is less than {} hours",
max_duration.hours(),
));
}

if let Some((start_date, end_date)) = restr.start_date.zip(restr.end_date) {
explains.push(format!("between {} and {}", start_date, end_date));
} else if let Some(start_date) = restr.start_date {
explains.push(format!("after {}", start_date));
} else if let Some(end_date) = restr.end_date {
explains.push(format!("before {}", end_date));
}

explains
}
8 changes: 8 additions & 0 deletions ocpi-tariffs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ mod restriction;
mod session;
mod tariff;

/// Module for generating human readable tariffs.
pub mod explain;

/// Module for normalizing tariffs.
pub mod normalize;

pub mod lint;

/// OCPI specific numeric types used for calculations, serializing and deserializing.
pub mod types;

Expand Down
Loading

0 comments on commit ca54cbd

Please sign in to comment.