1use serde::{Deserialize, Serialize};
2
3use crate::common::{DimensionCategory, ScoreSource};
4use crate::version::SchemaVersion;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ScoreModel {
15 pub schema_version: SchemaVersion,
16 #[serde(default)]
17 pub dimensions: Vec<ScoreDimension>,
18 #[serde(default)]
19 pub thresholds: ScoreThresholds,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct ScoreDimension {
25 pub id: String,
27 pub label: String,
29 pub weight: u32,
31 #[serde(default)]
33 pub category: DimensionCategory,
34 pub source: ScoreSource,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ScoreThresholds {
41 pub pass: f64,
43 pub warn: f64,
45}
46
47impl Default for ScoreThresholds {
48 fn default() -> Self {
49 Self {
50 pass: 0.8,
51 warn: 0.6,
52 }
53 }
54}
55
56impl ScoreModel {
57 pub fn default_model() -> Self {
59 Self {
60 schema_version: SchemaVersion::CURRENT,
61 dimensions: vec![
62 ScoreDimension {
63 id: "correctness".to_string(),
64 label: "Correctness".to_string(),
65 weight: 30,
66 category: DimensionCategory::Required,
67 source: ScoreSource::Gate,
68 },
69 ScoreDimension {
70 id: "conformance-coverage".to_string(),
71 label: "Conformance Coverage".to_string(),
72 weight: 20,
73 category: DimensionCategory::Scored,
74 source: ScoreSource::TestSuite,
75 },
76 ScoreDimension {
77 id: "behavior-pass-rate".to_string(),
78 label: "Behavior Pass Rate".to_string(),
79 weight: 10,
80 category: DimensionCategory::Scored,
81 source: ScoreSource::TestSuite,
82 },
83 ScoreDimension {
84 id: "lint-quality".to_string(),
85 label: "Lint Quality".to_string(),
86 weight: 10,
87 category: DimensionCategory::Scored,
88 source: ScoreSource::Gate,
89 },
90 ScoreDimension {
91 id: "doc-completeness".to_string(),
92 label: "Documentation Completeness".to_string(),
93 weight: 10,
94 category: DimensionCategory::Advisory,
95 source: ScoreSource::Manual,
96 },
97 ScoreDimension {
98 id: "panic-safety".to_string(),
99 label: "Panic Safety".to_string(),
100 weight: 5,
101 category: DimensionCategory::Scored,
102 source: ScoreSource::Gate,
103 },
104 ScoreDimension {
105 id: "contract-coverage".to_string(),
106 label: "Contract Coverage".to_string(),
107 weight: 10,
108 category: DimensionCategory::Scored,
109 source: ScoreSource::Coverage,
110 },
111 ScoreDimension {
112 id: "api-drift".to_string(),
113 label: "API Drift".to_string(),
114 weight: 5,
115 category: DimensionCategory::Advisory,
116 source: ScoreSource::Gate,
117 },
118 ],
119 thresholds: ScoreThresholds::default(),
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_score_model_toml_roundtrip() {
130 let model = ScoreModel::default_model();
131 let toml_str = toml::to_string_pretty(&model).unwrap();
132 let parsed: ScoreModel = toml::from_str(&toml_str).unwrap();
133 assert_eq!(parsed.dimensions.len(), 8);
134 assert_eq!(parsed.thresholds.pass, 0.8);
135 }
136
137 #[test]
138 fn test_weights_sum() {
139 let model = ScoreModel::default_model();
140 let total: u32 = model.dimensions.iter().map(|d| d.weight).sum();
141 assert_eq!(total, 100);
142 }
143}