use serde::{Deserialize, Serialize};
use crate::{
models::spt::{SPTExp, SPT},
validation::ValidationError,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NLayerData {
pub thickness: f64,
pub n: f64,
pub h_over_n: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SptSoilClassificationResult {
pub layers: Vec<NLayerData>,
pub sum_h_over_n: f64,
pub n_30: f64,
pub soil_class: String,
}
pub fn validate_input(spt: &SPT) -> Result<(), ValidationError> {
spt.validate(&["n", "depth"])?;
Ok(())
}
fn prepare_spt_exp(spt: &mut SPT) -> SPTExp {
let mut spt_exp = spt.get_idealized_exp("idealized".to_string());
spt_exp.apply_energy_correction(spt.energy_correction_factor.unwrap());
spt_exp
}
pub fn compute_n_30(spt_exp: &SPTExp) -> Vec<NLayerData> {
let mut result = Vec::new();
let mut remaining_depth = 30.0;
let blows = &spt_exp.blows;
for (i, blow) in blows.iter().enumerate() {
if remaining_depth <= 0.0 {
break;
}
let previous_depth = if i == 0 {
0.0
} else {
blows[i - 1].depth.unwrap()
};
let thickness = (blow.depth.unwrap() - previous_depth).min(remaining_depth);
if thickness <= 0.0 {
continue; }
let n = blow.n60.unwrap().to_i32() as f64;
if n <= 0.0 {
continue; }
let h_over_n = thickness / n;
result.push(NLayerData {
thickness,
n,
h_over_n,
});
remaining_depth -= thickness;
}
result
}
pub fn calc_lsc_by_spt(spt: &mut SPT) -> Result<SptSoilClassificationResult, ValidationError> {
validate_input(spt)?;
let spt_exp = prepare_spt_exp(spt);
let n_layers = compute_n_30(&spt_exp);
let sum_h_over_n: f64 = n_layers.iter().map(|l| l.h_over_n).sum();
let depth = spt_exp.blows.last().unwrap().depth.unwrap().min(30.);
let n_30 = if sum_h_over_n > 0.0 {
depth / sum_h_over_n
} else {
0.0
};
let soil_class = match n_30 {
c if c > 50.0 => "ZC",
c if c >= 15.0 => "ZD",
_ => "ZE",
}
.to_string();
Ok(SptSoilClassificationResult {
layers: n_layers,
sum_h_over_n,
n_30,
soil_class,
})
}