use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use voirs_recognizer::traits::{PhonemeAlignment, Transcript};
use voirs_sdk::{AudioBuffer, LanguageCode, VoirsError};
use crate::{EvaluationError, EvaluationResult};
#[derive(Debug, Clone)]
pub struct EcosystemConfig {
pub global_config: Arc<RwLock<HashMap<String, ConfigValue>>>,
pub language_configs: Arc<RwLock<HashMap<LanguageCode, LanguageConfig>>>,
pub quality_thresholds: QualityThresholds,
pub integration_settings: IntegrationSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum ConfigValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<ConfigValue>),
Object(HashMap<String, ConfigValue>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageConfig {
pub language: LanguageCode,
pub phoneme_mapping: HashMap<String, String>,
pub quality_weights: HashMap<String, f32>,
pub difficulty_adjustments: HashMap<String, f32>,
pub cultural_adaptations: CulturalAdaptations,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CulturalAdaptations {
pub feedback_style: FeedbackStyle,
pub accent_tolerance: f32,
pub regional_variants: HashMap<String, Vec<String>>,
pub context_preferences: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FeedbackStyle {
Direct,
Supportive,
Technical,
Concise,
CulturallySensitive,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityThresholds {
pub minimum_quality: f32,
pub good_quality: f32,
pub excellent_quality: f32,
pub pronunciation_thresholds: PronunciationThresholds,
pub confidence_thresholds: ConfidenceThresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PronunciationThresholds {
pub minimum_pronunciation: f32,
pub native_like: f32,
pub phoneme_accuracy: f32,
pub fluency: f32,
pub stress_accuracy: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceThresholds {
pub low_confidence: f32,
pub medium_confidence: f32,
pub high_confidence: f32,
pub auto_decision_threshold: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntegrationSettings {
pub auto_sdk_integration: bool,
pub shared_caching: bool,
pub cross_crate_errors: bool,
pub shared_metrics: bool,
pub data_format_version: String,
pub max_processing_timeout: std::time::Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EcosystemDataBridge {
pub audio_metadata: HashMap<String, AudioMetadata>,
pub processing_state: ProcessingState,
pub performance_metrics: PerformanceMetrics,
pub error_context: ErrorContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioMetadata {
pub source_id: String,
pub pipeline_stage: String,
pub quality_metrics: HashMap<String, f32>,
pub timestamps: HashMap<String, std::time::SystemTime>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessingState {
pub current_stage: String,
pub completed_stages: Vec<String>,
pub stage_results: HashMap<String, serde_json::Value>,
pub options: HashMap<String, ConfigValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub stage_times: HashMap<String, std::time::Duration>,
pub memory_usage: HashMap<String, u64>,
pub throughput: HashMap<String, f64>,
pub error_rates: HashMap<String, f32>,
pub cache_hit_rates: HashMap<String, f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorContext {
pub source_crate: String,
pub propagation_path: Vec<String>,
pub context_data: HashMap<String, serde_json::Value>,
pub timestamp: std::time::SystemTime,
pub recovery_suggestions: Vec<String>,
}
#[async_trait]
pub trait EcosystemEvaluator {
async fn initialize_with_ecosystem(&mut self, config: &EcosystemConfig)
-> EvaluationResult<()>;
async fn process_with_ecosystem(
&self,
audio: &AudioBuffer,
bridge: &mut EcosystemDataBridge,
) -> EvaluationResult<serde_json::Value>;
async fn get_ecosystem_results(&self) -> EvaluationResult<EcosystemResults>;
fn handle_ecosystem_error(&self, error: VoirsError) -> EvaluationError;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EcosystemResults {
pub evaluation_results: HashMap<String, serde_json::Value>,
pub quality_scores: HashMap<String, f32>,
pub metadata: HashMap<String, serde_json::Value>,
pub processing_stats: PerformanceMetrics,
pub recommendations: Vec<EcosystemRecommendation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EcosystemRecommendation {
pub target_component: String,
pub recommendation_type: RecommendationType,
pub description: String,
pub priority: RecommendationPriority,
pub parameters: HashMap<String, ConfigValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecommendationType {
QualityImprovement,
PerformanceOptimization,
ConfigurationAdjustment,
ModelParameterTuning,
DataPreprocessingEnhancement,
ErrorHandlingImprovement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecommendationPriority {
Critical,
High,
Medium,
Low,
Informational,
}
impl EcosystemConfig {
pub fn default() -> Self {
Self {
global_config: Arc::new(RwLock::new(HashMap::new())),
language_configs: Arc::new(RwLock::new(HashMap::new())),
quality_thresholds: QualityThresholds::default(),
integration_settings: IntegrationSettings::default(),
}
}
pub fn new(
quality_thresholds: QualityThresholds,
integration_settings: IntegrationSettings,
) -> Self {
Self {
global_config: Arc::new(RwLock::new(HashMap::new())),
language_configs: Arc::new(RwLock::new(HashMap::new())),
quality_thresholds,
integration_settings,
}
}
pub async fn set_global_config(&self, key: String, value: ConfigValue) {
let mut config = self.global_config.write().await;
config.insert(key, value);
}
pub async fn get_global_config(&self, key: &str) -> Option<ConfigValue> {
let config = self.global_config.read().await;
config.get(key).cloned()
}
pub async fn set_language_config(&self, language: LanguageCode, config: LanguageConfig) {
let mut configs = self.language_configs.write().await;
configs.insert(language, config);
}
pub async fn get_language_config(&self, language: &LanguageCode) -> Option<LanguageConfig> {
let configs = self.language_configs.read().await;
configs.get(language).cloned()
}
pub fn validate_compatibility(&self) -> Result<(), EvaluationError> {
let version = &self.integration_settings.data_format_version;
if version.is_empty() {
return Err(EvaluationError::ConfigurationError {
message: "Data format version not specified".to_string(),
});
}
let thresholds = &self.quality_thresholds;
if thresholds.minimum_quality < 0.0 || thresholds.minimum_quality > 1.0 {
return Err(EvaluationError::ConfigurationError {
message: "Invalid minimum quality threshold".to_string(),
});
}
if thresholds.good_quality <= thresholds.minimum_quality {
return Err(EvaluationError::ConfigurationError {
message: "Good quality threshold must be higher than minimum".to_string(),
});
}
if thresholds.excellent_quality <= thresholds.good_quality {
return Err(EvaluationError::ConfigurationError {
message: "Excellent quality threshold must be higher than good".to_string(),
});
}
Ok(())
}
}
impl Default for QualityThresholds {
fn default() -> Self {
Self {
minimum_quality: 0.6,
good_quality: 0.8,
excellent_quality: 0.9,
pronunciation_thresholds: PronunciationThresholds::default(),
confidence_thresholds: ConfidenceThresholds::default(),
}
}
}
impl Default for PronunciationThresholds {
fn default() -> Self {
Self {
minimum_pronunciation: 0.5,
native_like: 0.95,
phoneme_accuracy: 0.8,
fluency: 0.7,
stress_accuracy: 0.75,
}
}
}
impl Default for ConfidenceThresholds {
fn default() -> Self {
Self {
low_confidence: 0.3,
medium_confidence: 0.6,
high_confidence: 0.8,
auto_decision_threshold: 0.75,
}
}
}
impl Default for IntegrationSettings {
fn default() -> Self {
Self {
auto_sdk_integration: true,
shared_caching: true,
cross_crate_errors: true,
shared_metrics: true,
data_format_version: "1.0.0".to_string(),
max_processing_timeout: std::time::Duration::from_secs(300), }
}
}
impl Default for EcosystemDataBridge {
fn default() -> Self {
Self {
audio_metadata: HashMap::new(),
processing_state: ProcessingState::default(),
performance_metrics: PerformanceMetrics::default(),
error_context: ErrorContext::default(),
}
}
}
impl Default for ProcessingState {
fn default() -> Self {
Self {
current_stage: "initialized".to_string(),
completed_stages: Vec::new(),
stage_results: HashMap::new(),
options: HashMap::new(),
}
}
}
impl Default for PerformanceMetrics {
fn default() -> Self {
Self {
stage_times: HashMap::new(),
memory_usage: HashMap::new(),
throughput: HashMap::new(),
error_rates: HashMap::new(),
cache_hit_rates: HashMap::new(),
}
}
}
impl Default for ErrorContext {
fn default() -> Self {
Self {
source_crate: "voirs-evaluation".to_string(),
propagation_path: Vec::new(),
context_data: HashMap::new(),
timestamp: std::time::SystemTime::now(),
recovery_suggestions: Vec::new(),
}
}
}
pub mod utils {
use super::*;
pub fn convert_error_with_context(
error: VoirsError,
context: &ErrorContext,
) -> EvaluationError {
match error {
VoirsError::ModelError {
model_type,
message,
source,
} => EvaluationError::ModelError {
message: format!(
"[{}] Model error ({}): {}",
context.source_crate, model_type, message
),
source,
},
VoirsError::AudioError {
message,
buffer_info,
} => EvaluationError::AudioProcessingError {
message: format!("[{}] Audio error: {}", context.source_crate, message),
source: buffer_info.map(|info| {
Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Buffer info: {:?}", info),
)) as Box<dyn std::error::Error + Send + Sync>
}),
},
_ => EvaluationError::ModelError {
message: format!("[{}] Ecosystem error: {}", context.source_crate, error),
source: None,
},
}
}
pub fn create_recommendation(
target: &str,
rec_type: RecommendationType,
description: &str,
priority: RecommendationPriority,
) -> EcosystemRecommendation {
EcosystemRecommendation {
target_component: target.to_string(),
recommendation_type: rec_type,
description: description.to_string(),
priority,
parameters: HashMap::new(),
}
}
pub fn merge_performance_metrics(metrics: Vec<PerformanceMetrics>) -> PerformanceMetrics {
let mut merged = PerformanceMetrics::default();
for metric in &metrics {
for (stage, time) in &metric.stage_times {
*merged
.stage_times
.entry(stage.clone())
.or_insert(std::time::Duration::ZERO) += *time;
}
for (stage, memory) in &metric.memory_usage {
let current = merged.memory_usage.entry(stage.clone()).or_insert(0);
*current = (*current).max(*memory);
}
for (stage, throughput) in &metric.throughput {
*merged.throughput.entry(stage.clone()).or_insert(0.0) += throughput;
}
for (stage, rate) in &metric.error_rates {
*merged.error_rates.entry(stage.clone()).or_insert(0.0) += rate;
}
for (stage, rate) in &metric.cache_hit_rates {
*merged.cache_hit_rates.entry(stage.clone()).or_insert(0.0) += rate;
}
}
let count = metrics.len() as f64;
if count > 1.0 {
for throughput in merged.throughput.values_mut() {
*throughput /= count;
}
for error_rate in merged.error_rates.values_mut() {
*error_rate /= count as f32;
}
for hit_rate in merged.cache_hit_rates.values_mut() {
*hit_rate /= count as f32;
}
}
merged
}
pub fn check_version_compatibility(
current_version: &str,
required_version: &str,
) -> Result<(), EvaluationError> {
if current_version.split('.').next() != required_version.split('.').next() {
return Err(EvaluationError::ConfigurationError {
message: format!(
"Version incompatibility: current {} vs required {}",
current_version, required_version
),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_ecosystem_config_creation() {
let config = EcosystemConfig::default();
assert!(config.validate_compatibility().is_ok());
}
#[tokio::test]
async fn test_global_config_management() {
let config = EcosystemConfig::default();
config
.set_global_config(
"test_key".to_string(),
ConfigValue::String("test_value".to_string()),
)
.await;
let value = config.get_global_config("test_key").await;
assert!(value.is_some());
if let Some(ConfigValue::String(s)) = value {
assert_eq!(s, "test_value");
} else {
panic!("Expected string configuration value");
}
}
#[tokio::test]
async fn test_language_config_management() {
let config = EcosystemConfig::default();
let lang_config = LanguageConfig {
language: LanguageCode::EnUs,
phoneme_mapping: HashMap::new(),
quality_weights: HashMap::new(),
difficulty_adjustments: HashMap::new(),
cultural_adaptations: CulturalAdaptations {
feedback_style: FeedbackStyle::Direct,
accent_tolerance: 0.8,
regional_variants: HashMap::new(),
context_preferences: vec!["formal".to_string()],
},
};
config
.set_language_config(LanguageCode::EnUs, lang_config.clone())
.await;
let retrieved = config.get_language_config(&LanguageCode::EnUs).await;
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().language, LanguageCode::EnUs);
}
#[test]
fn test_quality_thresholds_validation() {
let mut thresholds = QualityThresholds::default();
let settings = IntegrationSettings::default();
let config = EcosystemConfig::new(thresholds.clone(), settings);
assert!(config.validate_compatibility().is_ok());
thresholds.minimum_quality = 1.5; let config = EcosystemConfig::new(thresholds, IntegrationSettings::default());
assert!(config.validate_compatibility().is_err());
}
#[test]
fn test_performance_metrics_merging() {
let mut metrics1 = PerformanceMetrics::default();
metrics1
.stage_times
.insert("stage1".to_string(), std::time::Duration::from_millis(100));
metrics1.throughput.insert("stage1".to_string(), 10.0);
let mut metrics2 = PerformanceMetrics::default();
metrics2
.stage_times
.insert("stage1".to_string(), std::time::Duration::from_millis(150));
metrics2.throughput.insert("stage1".to_string(), 20.0);
let merged = utils::merge_performance_metrics(vec![metrics1, metrics2]);
assert_eq!(
merged.stage_times.get("stage1"),
Some(&std::time::Duration::from_millis(250))
);
assert_eq!(merged.throughput.get("stage1"), Some(&15.0)); }
#[test]
fn test_version_compatibility() {
assert!(utils::check_version_compatibility("1.0.0", "1.1.0").is_ok());
assert!(utils::check_version_compatibility("1.0.0", "2.0.0").is_err());
assert!(utils::check_version_compatibility("2.1.5", "2.3.1").is_ok());
}
#[test]
fn test_ecosystem_recommendation_creation() {
let rec = utils::create_recommendation(
"voirs-acoustic",
RecommendationType::QualityImprovement,
"Increase sample rate for better quality",
RecommendationPriority::High,
);
assert_eq!(rec.target_component, "voirs-acoustic");
assert_eq!(rec.description, "Increase sample rate for better quality");
assert!(matches!(rec.priority, RecommendationPriority::High));
}
#[test]
fn test_error_context_conversion() {
let context = ErrorContext {
source_crate: "voirs-test".to_string(),
..Default::default()
};
let voirs_error = VoirsError::AudioError {
message: "Test audio error".to_string(),
buffer_info: None,
};
let eval_error = utils::convert_error_with_context(voirs_error, &context);
assert!(matches!(
eval_error,
EvaluationError::AudioProcessingError { .. }
));
}
}