converge_analytics/packs/segmentation/
types.rs1use converge_pack::gate::GateResult as Result;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct SegmentationInput {
6 pub records: Vec<Vec<f64>>,
7 pub k: usize,
8 #[serde(default = "default_max_iterations")]
9 pub max_iterations: usize,
10 pub seed: Option<u64>,
11}
12
13fn default_max_iterations() -> usize {
14 100
15}
16
17impl SegmentationInput {
18 pub fn validate(&self) -> Result<()> {
19 if self.records.is_empty() {
20 return Err(converge_pack::GateError::invalid_input(
21 "At least one record required",
22 ));
23 }
24 if self.k == 0 {
25 return Err(converge_pack::GateError::invalid_input("k must be >= 1"));
26 }
27 if self.k > self.records.len() {
28 return Err(converge_pack::GateError::invalid_input(
29 "k cannot exceed number of records",
30 ));
31 }
32 if self.max_iterations == 0 {
33 return Err(converge_pack::GateError::invalid_input(
34 "max_iterations must be >= 1",
35 ));
36 }
37 let dim = self.records[0].len();
38 if dim == 0 {
39 return Err(converge_pack::GateError::invalid_input(
40 "Records must have at least one feature",
41 ));
42 }
43 for (i, record) in self.records.iter().enumerate() {
44 if record.len() != dim {
45 return Err(converge_pack::GateError::invalid_input(format!(
46 "Record {} has {} features, expected {}",
47 i,
48 record.len(),
49 dim
50 )));
51 }
52 }
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SegmentationOutput {
59 pub assignments: Vec<usize>,
60 pub centroids: Vec<Vec<f64>>,
61 pub iterations_used: usize,
62 pub inertia: f64,
63}
64
65impl SegmentationOutput {
66 pub fn summary(&self) -> String {
67 format!(
68 "Segmented {} records into {} clusters in {} iterations (inertia: {:.2})",
69 self.assignments.len(),
70 self.centroids.len(),
71 self.iterations_used,
72 self.inertia,
73 )
74 }
75}