use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriteriaResult {
pub regulation_name: String,
pub regulation_reference: String,
pub vessel_name: String,
pub loading_condition: String,
pub displacement: f64,
pub cog: [f64; 3],
pub criteria: Vec<CriterionResult>,
pub overall_pass: bool,
pub pass_count: usize,
pub fail_count: usize,
pub notes: String,
pub plots: Vec<PlotData>,
}
impl Default for CriteriaResult {
fn default() -> Self {
Self {
regulation_name: String::new(),
regulation_reference: String::new(),
vessel_name: String::new(),
loading_condition: String::new(),
displacement: 0.0,
cog: [0.0, 0.0, 0.0],
criteria: Vec::new(),
overall_pass: false,
pass_count: 0,
fail_count: 0,
notes: String::new(),
plots: Vec::new(),
}
}
}
impl std::fmt::Display for CriteriaResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"======================================================================"
)?;
writeln!(f, " {}", self.regulation_name)?;
writeln!(f, " Reference: {}", self.regulation_reference)?;
writeln!(
f,
"======================================================================"
)?;
writeln!(f, "Vessel: {}", self.vessel_name)?;
writeln!(f, "Loading Condition: {}", self.loading_condition)?;
writeln!(f, "Displacement: {:.0} kg", self.displacement)?;
writeln!(
f,
"COG: ({:.2}, {:.2}, {:.2}) m",
self.cog[0], self.cog[1], self.cog[2]
)?;
writeln!(f)?;
writeln!(f, "CRITERIA RESULTS")?;
writeln!(
f,
"----------------------------------------------------------------------"
)?;
for crit in &self.criteria {
let icon = match crit.status {
CriteriaStatus::Pass => "✓",
CriteriaStatus::Fail => "✗",
CriteriaStatus::NotApplicable => "-",
CriteriaStatus::Warning => "!",
};
writeln!(f, "[{}] {}", icon, crit.name)?;
writeln!(f, " Required: {:.4} {}", crit.required_value, crit.unit)?;
writeln!(f, " Actual: {:.4} {}", crit.actual_value, crit.unit)?;
if crit.required_value != 0.0 {
let margin_percent = (crit.margin / crit.required_value.abs()) * 100.0;
writeln!(
f,
" Margin: {:+.4} {} ({:+.1}%)",
crit.margin, crit.unit, margin_percent
)?;
}
if let Some(ref notes) = crit.notes {
if !notes.is_empty() {
writeln!(f, " Note: {}", notes)?;
}
}
writeln!(f)?;
}
writeln!(
f,
"----------------------------------------------------------------------"
)?;
let status = if self.overall_pass { "PASS" } else { "FAIL" };
writeln!(
f,
"OVERALL: {} ({}/{} criteria passed)",
status,
self.pass_count,
self.criteria.len()
)?;
writeln!(
f,
"======================================================================"
)?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriterionResult {
pub name: String,
pub description: String,
pub required_value: f64,
pub actual_value: f64,
pub unit: String,
pub status: CriteriaStatus,
pub margin: f64,
pub notes: Option<String>,
pub plot_id: Option<String>,
}
impl Default for CriterionResult {
fn default() -> Self {
Self {
name: String::new(),
description: String::new(),
required_value: 0.0,
actual_value: 0.0,
unit: String::new(),
status: CriteriaStatus::NotApplicable,
margin: 0.0,
notes: None,
plot_id: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CriteriaStatus {
Pass,
Fail,
NotApplicable,
Warning,
}
impl std::fmt::Display for CriteriaStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CriteriaStatus::Pass => write!(f, "PASS"),
CriteriaStatus::Fail => write!(f, "FAIL"),
CriteriaStatus::NotApplicable => write!(f, "N/A"),
CriteriaStatus::Warning => write!(f, "WARN"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlotData {
pub id: String,
pub title: String,
pub x_label: String,
pub y_label: String,
pub elements: Vec<PlotElement>,
}
impl Default for PlotData {
fn default() -> Self {
Self {
id: "main".to_string(),
title: String::new(),
x_label: "Heel (°)".to_string(),
y_label: "GZ (m)".to_string(),
elements: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum PlotElement {
Curve {
name: String,
x: Vec<f64>,
y: Vec<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
style: Option<String>,
},
HorizontalLine {
name: String,
y: f64,
#[serde(skip_serializing_if = "Option::is_none")]
x_min: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
x_max: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
style: Option<String>,
},
VerticalLine {
name: String,
x: f64,
#[serde(skip_serializing_if = "Option::is_none")]
y_min: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
y_max: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
style: Option<String>,
},
FilledArea {
name: String,
x: Vec<f64>,
y_lower: Vec<f64>,
y_upper: Vec<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
alpha: Option<f64>,
},
Point {
name: String,
x: f64,
y: f64,
#[serde(skip_serializing_if = "Option::is_none")]
marker: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
},
Annotation {
text: String,
x: f64,
y: f64,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
},
}