use std::path::PathBuf;
use std::str::FromStr;
use cached::proc_macro::cached;
use polars::prelude::*;
use strum::IntoEnumIterator;
use avin_core::{Iid, TimeFrame};
use avin_utils::{AvinError, Cmd};
use crate::{Size, Sz};
pub trait Analyse {
fn analyse(iid: &Iid, tf: TimeFrame) -> Result<(), AvinError>;
fn analyse_all() -> Result<(), AvinError>;
fn save(iid: &Iid, name: &str, df: &mut DataFrame) {
let path = create_path(iid, name);
Cmd::write_pqt(df, &path).unwrap();
log::info!("Analyse save {}", path.display());
}
fn load(iid: &Iid, name: &str) -> Result<DataFrame, AvinError> {
let path = create_path(iid, name);
if !Cmd::is_exist(&path) {
let msg = format!("analyse not found: {}", path.display());
let err = AvinError::NotFound(msg);
return Err(err);
}
cached_load_file(path)
}
fn delete(iid: &Iid, name: &str) -> Result<(), AvinError> {
let mut path = iid.path();
path.push("ANALYSE");
path.push(name);
if !Cmd::is_exist(&path) {
log::info!("Skip delete {}", path.display());
return Ok(());
} else if path.is_dir() {
Cmd::delete_dir(&path).unwrap();
log::info!("Analyse delete {}", path.display());
} else if path.is_file() {
Cmd::delete(&path).unwrap();
log::info!("Analyse delete {}", path.display());
}
Ok(())
}
fn eval_cdf(values: &Series) -> DataFrame {
let mut values = values.sort(SortOptions::default()).unwrap();
values.set_sorted_flag(polars::series::IsSorted::Ascending);
values.rename("value".into());
let mut df = values
.value_counts(false, false, "count".into(), false)
.unwrap();
let pf = values
.value_counts(false, false, "pf".into(), true)
.unwrap()
.column("pf")
.unwrap()
.as_materialized_series()
.clone();
let cdf = cum_sum(&pf, false).unwrap().rename("cdf".into()).clone();
let mut cdf_p = cdf.clone() * 100;
cdf_p.rename("cdf_p".into());
df.with_column(pf).unwrap();
df.with_column(cdf).unwrap();
df.with_column(cdf_p).unwrap();
df
}
fn eval_size(cdf: &DataFrame) -> DataFrame {
let mut sizes = Vec::new();
let mut begin = Vec::new();
let mut end = Vec::new();
let mut b = cdf
.column("value")
.unwrap()
.as_materialized_series()
.first()
.value()
.clone();
let mut e;
for size in Size::iter() {
let filtered = cdf
.clone()
.lazy()
.filter(col("cdf_p").lt_eq(size.range().max()))
.collect()
.unwrap();
if filtered.is_empty() {
e = b.clone();
} else {
e = filtered
.column("value")
.unwrap()
.as_materialized_series()
.last()
.value()
.clone()
}
sizes.push(size.name());
begin.push(b.clone());
end.push(e.clone());
b = e.clone();
}
df!(
"size" => sizes,
"begin" => begin,
"end" => end,
)
.unwrap()
}
fn eval_sz(cdf: &DataFrame) -> DataFrame {
let mut sizes = Vec::new();
let mut begin = Vec::new();
let mut end = Vec::new();
let mut b = cdf
.column("value")
.unwrap()
.as_materialized_series()
.first()
.value()
.clone();
let mut e;
for size in Sz::iter() {
let filtered = cdf
.clone()
.lazy()
.filter(col("cdf_p").lt_eq(size.range().max()))
.collect()
.unwrap();
if filtered.is_empty() {
e = b.clone();
} else {
e = filtered
.column("value")
.unwrap()
.as_materialized_series()
.last()
.value()
.clone()
}
sizes.push(size.name());
begin.push(b.clone());
end.push(e.clone());
b = e.clone();
}
df!(
"sz" => sizes,
"begin" => begin,
"end" => end,
)
.unwrap()
}
fn cdf<T>(value: T, cdf_df: DataFrame) -> f64
where
T: NumericNative + Into<Expr>,
{
cdf_df
.lazy()
.filter(col("value").lt_eq(value))
.last()
.collect()
.unwrap()
.column("cdf")
.unwrap()
.f64()
.unwrap()
.last()
.unwrap()
}
fn cdf_p<T>(value: T, cdf_df: DataFrame) -> f64
where
T: NumericNative + Into<Expr>,
{
cdf_df
.lazy()
.filter(col("value").lt_eq(value))
.last()
.collect()
.unwrap()
.column("cdf_p")
.unwrap()
.f64()
.unwrap()
.last()
.unwrap()
}
fn size<T>(value: T, sizes: &DataFrame) -> Size
where
T: NumericNative + Into<Expr>,
{
let filtered = sizes
.clone()
.lazy()
.filter(col("begin").lt_eq(value))
.filter(col("end").gt(value))
.collect()
.unwrap();
if filtered.height() == 1 {
let name = filtered
.column("size")
.unwrap()
.str()
.unwrap()
.last()
.unwrap();
let size = Size::from_str(name).unwrap();
return size;
}
let greatest = sizes
.column("end")
.unwrap()
.as_materialized_series()
.max()
.unwrap()
.lt(&Some(value));
if greatest {
Size::GreatestBig
} else {
Size::GreatestSmall
}
}
fn sz<T>(value: T, sizes: &DataFrame) -> Sz
where
T: NumericNative + Into<Expr>,
{
let filtered = sizes
.clone()
.lazy()
.filter(col("begin").lt_eq(value))
.filter(col("end").gt(value))
.collect()
.unwrap();
if filtered.height() == 1 {
let name = filtered
.column("sz")
.unwrap()
.str()
.unwrap()
.last()
.unwrap();
let sz = Sz::from_str(name).unwrap();
return sz;
}
let greatest = sizes
.column("end")
.unwrap()
.as_materialized_series()
.max()
.unwrap()
.lt(&Some(value));
if greatest { Sz::XL } else { Sz::XS }
}
}
fn create_path(iid: &Iid, analyse_name: &str) -> PathBuf {
let mut path = iid.path();
path.push("ANALYSE");
let analyse_name = format!("{analyse_name}.parquet");
for part in analyse_name.split(' ') {
path.push(part);
}
path
}
#[inline]
#[cached]
fn cached_load_file(path: PathBuf) -> Result<DataFrame, AvinError> {
Cmd::read_pqt(&path)
}