webpage_quality_analyzer 1.0.2

High-performance webpage quality analyzer with 115 comprehensive metrics - Rust library with WASM, C++, and Python bindings
Documentation
/// Universal Metric Registry Implementation
/// Provides centralized access to all metric definitions and profile configurations
use crate::metrics::definitions::{
    MetricCategory, MetricDefinition, ProfileMetricConfig, METRIC_REGISTRY,
};
use std::collections::HashMap;

/// Registry validation errors
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
    #[error("Metric not found: {0}")]
    MetricNotFound(String),

    #[error("Profile configuration not found for metric '{metric}' in profile '{profile}'")]
    ProfileConfigNotFound { metric: String, profile: String },

    #[error("Invalid metric category: {0}")]
    InvalidCategory(String),

    #[error("Registry validation failed: {0}")]
    ValidationFailed(String),
}

/// Registry statistics for monitoring and debugging
#[derive(Debug, Clone)]
pub struct RegistryStats {
    pub total_metrics: usize,
    pub metrics_by_category: HashMap<MetricCategory, usize>,
    pub configurable_metrics: usize,
    pub html_only_metrics: usize,
    pub network_required_metrics: usize,
    pub profiles_with_overrides: HashMap<String, usize>,
}

/// Enhanced registry operations
pub struct MetricRegistryOps;

impl MetricRegistryOps {
    /// Get metric definition with error handling
    pub fn get_metric_definition(
        field_name: &str,
    ) -> Result<&'static MetricDefinition, RegistryError> {
        METRIC_REGISTRY
            .get_metric_definition(field_name)
            .ok_or_else(|| RegistryError::MetricNotFound(field_name.to_string()))
    }

    /// Get profile configuration with fallback to defaults
    pub fn get_profile_config(
        field_name: &str,
        profile: &str,
    ) -> Result<ProfileMetricConfig, RegistryError> {
        let metric_def = Self::get_metric_definition(field_name)?;

        // Try to get profile-specific config
        if let Some(config) = METRIC_REGISTRY.get_profile_config(field_name, profile) {
            Ok(config.clone())
        } else {
            // Fallback to default config
            Ok(Self::create_default_config(metric_def))
        }
    }

    /// Get all metrics for a specific profile with their configurations
    pub fn get_profile_metrics(
        profile: &str,
    ) -> HashMap<String, (MetricDefinition, ProfileMetricConfig)> {
        let mut profile_metrics = HashMap::new();

        for metric_def in METRIC_REGISTRY.all_metrics() {
            let config = match Self::get_profile_config(metric_def.field_name, profile) {
                Ok(config) => config,
                Err(_) => Self::create_default_config(metric_def),
            };

            if config.enabled {
                profile_metrics.insert(
                    metric_def.field_name.to_string(),
                    (metric_def.clone(), config),
                );
            }
        }

        profile_metrics
    }

    /// Get metrics by category with profile configurations
    pub fn get_category_metrics(
        category: MetricCategory,
        profile: &str,
    ) -> Vec<(MetricDefinition, ProfileMetricConfig)> {
        METRIC_REGISTRY
            .list_metrics_by_category(category)
            .into_iter()
            .filter_map(|metric_def| {
                Self::get_profile_config(metric_def.field_name, profile)
                    .ok()
                    .filter(|config| config.enabled)
                    .map(|config| (metric_def.clone(), config))
            })
            .collect()
    }

    /// Validate profile compatibility with registry
    pub fn validate_profile(
        _profile_name: &str,
        metric_overrides: &HashMap<String, ProfileMetricConfig>,
    ) -> Result<(), RegistryError> {
        let mut errors = Vec::new();

        // Check that all metric overrides reference valid metrics
        for metric_name in metric_overrides.keys() {
            if METRIC_REGISTRY.get_metric_definition(metric_name).is_none() {
                errors.push(format!("Unknown metric: {}", metric_name));
            }
        }

        // Validate weight sums don't exceed reasonable bounds per category
        let mut category_weights: HashMap<MetricCategory, f32> = HashMap::new();

        for (metric_name, config) in metric_overrides {
            if let Some(metric_def) = METRIC_REGISTRY.get_metric_definition(metric_name) {
                let current_weight = category_weights
                    .entry(metric_def.category.clone())
                    .or_insert(0.0);
                *current_weight += config.weight;
            }
        }

        // Check for unreasonable weight distributions
        for (category, total_weight) in category_weights {
            if total_weight > 5.0 {
                // Arbitrary but reasonable limit
                errors.push(format!(
                    "Category {:?} has excessive total weight: {:.2}",
                    category, total_weight
                ));
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(RegistryError::ValidationFailed(errors.join("; ")))
        }
    }

    /// Generate registry statistics
    pub fn get_registry_stats() -> RegistryStats {
        let all_metrics = METRIC_REGISTRY.all_metrics();
        let total_metrics = all_metrics.len();

        let mut metrics_by_category = HashMap::new();
        let mut configurable_metrics = 0;
        let mut html_only_metrics = 0;
        let mut network_required_metrics = 0;
        let mut profiles_with_overrides = HashMap::new();

        for metric in &all_metrics {
            // Count by category
            *metrics_by_category
                .entry(metric.category.clone())
                .or_insert(0) += 1;

            // Count configurable metrics
            if metric.default_enabled {
                configurable_metrics += 1;
            }

            // Count availability
            if metric.html_only_available {
                html_only_metrics += 1;
            }
            if metric.requires_network {
                network_required_metrics += 1;
            }

            // Count profile overrides
            for profile_name in metric.profile_configurations.keys() {
                *profiles_with_overrides
                    .entry(profile_name.clone())
                    .or_insert(0) += 1;
            }
        }

        RegistryStats {
            total_metrics,
            metrics_by_category,
            configurable_metrics,
            html_only_metrics,
            network_required_metrics,
            profiles_with_overrides,
        }
    }

    /// List available profiles based on metric configurations
    pub fn list_available_profiles() -> Vec<String> {
        let mut profiles = std::collections::HashSet::new();

        for metric in METRIC_REGISTRY.all_metrics() {
            for profile_name in metric.profile_configurations.keys() {
                profiles.insert(profile_name.clone());
            }
        }

        let mut profile_list: Vec<String> = profiles.into_iter().collect();
        profile_list.sort();
        profile_list
    }

    /// Find metrics that are missing from a profile
    pub fn find_missing_profile_configs(profile: &str) -> Vec<String> {
        let mut missing = Vec::new();

        for metric in METRIC_REGISTRY.all_metrics() {
            if metric.default_enabled && !metric.profile_configurations.contains_key(profile) {
                missing.push(metric.field_name.to_string());
            }
        }

        missing
    }

    /// Create default configuration for a metric
    fn create_default_config(metric_def: &MetricDefinition) -> ProfileMetricConfig {
        use crate::metrics::definitions::{ProfileMetricConfig, ThresholdSet};

        ProfileMetricConfig {
            weight: metric_def.default_weight,
            enabled: metric_def.default_enabled,
            thresholds: ThresholdSet::default(),
            penalty_multiplier: 1.0,
            bonus_conditions: Vec::new(),
            scoring_function_override: None,
        }
    }

    /// Get recommended metrics for a content type
    pub fn get_recommended_metrics(content_type: &str) -> Vec<String> {
        match content_type.to_lowercase().as_str() {
            "article" | "blog" | "content" => vec![
                "word_count".to_string(),
                "readability_fk".to_string(),
                "headings_count".to_string(),
                "main_text_ratio".to_string(),
                "sentence_count".to_string(),
                "title_len".to_string(),
            ],
            "news" => vec![
                "word_count".to_string(),
                "title_len".to_string(),
                "readability_fk".to_string(),
                "meta_desc_len".to_string(),
                "publish_date".to_string(),
            ],
            "product" | "ecommerce" => vec![
                "images_count".to_string(),
                "title_len".to_string(),
                "meta_desc_len".to_string(),
                "image_alt_coverage".to_string(),
                "structured_data_score".to_string(),
            ],
            "portfolio" => vec![
                "images_count".to_string(),
                "image_alt_coverage".to_string(),
                "mobile_friendly_score".to_string(),
                "design_system_compliance".to_string(),
            ],
            _ => vec![
                "word_count".to_string(),
                "title_len".to_string(),
                "headings_count".to_string(),
                "images_count".to_string(),
            ],
        }
    }
}

/// Convenience functions for common registry operations
pub mod registry_utils {
    use super::*;

    /// Quick check if a metric exists
    pub fn metric_exists(field_name: &str) -> bool {
        METRIC_REGISTRY.get_metric_definition(field_name).is_some()
    }

    /// Get metric category
    pub fn get_metric_category(field_name: &str) -> Option<MetricCategory> {
        METRIC_REGISTRY
            .get_metric_definition(field_name)
            .map(|def| def.category.clone())
    }

    /// Check if metric requires network access
    pub fn requires_network(field_name: &str) -> bool {
        METRIC_REGISTRY
            .get_metric_definition(field_name)
            .map(|def| def.requires_network)
            .unwrap_or(true) // Default to requiring network for unknown metrics
    }

    /// Get default weight for a metric
    pub fn get_default_weight(field_name: &str) -> f32 {
        METRIC_REGISTRY
            .get_metric_definition(field_name)
            .map(|def| def.default_weight)
            .unwrap_or(1.0)
    }

    /// Print registry summary
    pub fn print_registry_summary() {
        let stats = MetricRegistryOps::get_registry_stats();
        println!("=== Metric Registry Summary ===");
        println!("Total metrics: {}", stats.total_metrics);
        println!("Configurable metrics: {}", stats.configurable_metrics);
        println!("HTML-only metrics: {}", stats.html_only_metrics);
        println!(
            "Network-required metrics: {}",
            stats.network_required_metrics
        );

        println!("\nMetrics by category:");
        for (category, count) in &stats.metrics_by_category {
            println!("  {:?}: {}", category, count);
        }

        println!("\nProfiles with overrides:");
        for (profile, count) in &stats.profiles_with_overrides {
            println!("  {}: {} metrics", profile, count);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_metric_registry_access() {
        // Test basic registry access
        assert!(MetricRegistryOps::get_metric_definition("word_count").is_ok());
        assert!(MetricRegistryOps::get_metric_definition("nonexistent_metric").is_err());
    }

    #[test]
    fn test_profile_config_retrieval() {
        // Test profile configuration retrieval
        let config = MetricRegistryOps::get_profile_config("word_count", "content_article");
        assert!(config.is_ok());

        let config = config.unwrap();
        assert!(config.enabled);
        assert!(config.weight > 0.0);
    }

    #[test]
    fn test_category_metrics() {
        let content_metrics =
            MetricRegistryOps::get_category_metrics(MetricCategory::Content, "content_article");
        assert!(!content_metrics.is_empty());

        // Verify all returned metrics are content category
        for (metric_def, _) in &content_metrics {
            assert_eq!(metric_def.category, MetricCategory::Content);
        }
    }

    #[test]
    fn test_registry_stats() {
        let stats = MetricRegistryOps::get_registry_stats();
        assert!(stats.total_metrics > 0);
        assert!(stats
            .metrics_by_category
            .contains_key(&MetricCategory::Content));
        assert!(stats.configurable_metrics > 0);
    }

    #[test]
    fn test_available_profiles() {
        let profiles = MetricRegistryOps::list_available_profiles();
        println!("Available profiles: {:?}", profiles);
        assert!(profiles.contains(&"content_article".to_string()));
        assert!(profiles.contains(&"news".to_string()));
        assert!(profiles.contains(&"product".to_string()));
    }
}