use crate::metrics::definitions::{BonusCondition, ThresholdSet};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Validation error: {0}")]
ValidationError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContentType {
Article,
Product,
News,
Blog,
Documentation,
LandingPage,
Portfolio,
ECommerce,
LongFormArticle,
ProductPage,
}
impl ContentType {
pub fn as_str(&self) -> &'static str {
match self {
ContentType::Article => "article",
ContentType::Product => "product",
ContentType::News => "news",
ContentType::Blog => "blog",
ContentType::Documentation => "documentation",
ContentType::LandingPage => "landing_page",
ContentType::Portfolio => "portfolio",
ContentType::ECommerce => "ecommerce",
ContentType::LongFormArticle => "long_form_article",
ContentType::ProductPage => "product_page",
}
}
}
impl Default for ContentType {
fn default() -> Self {
ContentType::Article
}
}
pub type ProfileConfig = EnhancedScoringProfile;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ComparisonOperator {
LessThan,
GreaterThan,
Equals,
NotEquals,
LessThanOrEqual,
GreaterThanOrEqual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PenaltyTrigger {
MetricThreshold {
metric_name: String,
operator: ComparisonOperator,
threshold: f32,
},
ContentDeficiency {
deficiency_type: String,
severity: f32,
},
MultipleConditions {
conditions: Vec<PenaltyTrigger>,
operator: LogicalOperator,
},
MetricBelow {
metric: String,
threshold: f32,
},
MetricAbove {
metric: String,
threshold: f32,
},
MetricEquals {
metric: String,
value: f32,
},
MetricMissing {
metric: String,
},
}
pub type PenaltyTriggerCondition = PenaltyTrigger;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BonusTrigger {
MetricThreshold {
metric_name: String,
operator: ComparisonOperator,
threshold: f32,
},
ContentQuality {
quality_type: String,
requirement: String,
},
SynergyBonus {
metrics: Vec<String>,
combined_threshold: f32,
},
MetricExcellence {
metric: String,
threshold: f32,
},
MultipleMetricsGood {
metrics: Vec<String>,
threshold: f32,
},
CategoryExcellence {
category: String,
threshold: f32,
},
}
pub type BonusTriggerCondition = BonusTrigger;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LogicalOperator {
And,
Or,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PenaltyAmount {
Fixed(f32),
Percentage(f32),
Multiplier(f32),
}
pub type GlobalPenaltyType = PenaltyAmount;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BonusAmount {
Fixed(f32),
Percentage(f32),
Multiplier(f32),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PenaltySeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnhancedScoringProfile {
pub metadata: ProfileMetadata,
pub category_weights: CategoryWeights,
pub metric_overrides: HashMap<String, MetricOverride>,
pub content_expectations: ContentExpectations,
pub quality_bands: QualityBandConfig,
pub penalties: PenaltyConfig,
pub bonuses: BonusConfig,
}
impl Default for EnhancedScoringProfile {
fn default() -> Self {
Self {
metadata: ProfileMetadata::default(),
category_weights: CategoryWeights::default(),
metric_overrides: HashMap::new(),
content_expectations: ContentExpectations::default(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
}
impl EnhancedScoringProfile {
pub fn default_with_name(name: &str) -> Self {
let mut profile = Self::default();
profile.metadata.name = name.to_string();
profile.metadata.description = format!("Profile for {}", name);
profile
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfileMetadata {
pub name: String,
pub description: String,
pub target_content_types: Vec<String>,
pub version: String,
pub author: String,
pub created_at: String,
pub tags: Vec<String>,
}
impl Default for ProfileMetadata {
fn default() -> Self {
Self {
name: "Unnamed Profile".to_string(),
description: "Custom profile configuration".to_string(),
target_content_types: vec!["general".to_string()],
version: "1.0.0".to_string(),
author: "user".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: Vec::new(),
}
}
}
pub type CategoryWeights = HashMap<String, f32>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricOverride {
pub weight: f32,
pub enabled: bool,
pub thresholds: Option<ThresholdSet>,
pub penalty_multiplier: f32,
pub bonus_conditions: Vec<BonusCondition>,
pub scoring_function_override: Option<ScoringFunctionOverride>,
}
impl Default for MetricOverride {
fn default() -> Self {
Self {
weight: 1.0,
enabled: true,
thresholds: None,
penalty_multiplier: 1.0,
bonus_conditions: Vec::new(),
scoring_function_override: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScoringFunctionOverride {
Linear {
min_value: f32,
max_value: f32,
reverse_scoring: bool,
},
Logarithmic {
base: f32,
scaling_factor: f32,
},
StepFunction {
steps: Vec<(f32, f32)>, },
CustomFunction {
function_name: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContentExpectations {
pub word_count: Option<WordCountExpectation>,
pub heading_structure: Option<HeadingExpectation>,
pub media_requirements: Option<MediaExpectation>,
pub technical_requirements: Option<TechnicalExpectation>,
pub seo_requirements: Option<SeoExpectation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WordCountExpectation {
pub minimum: usize,
pub optimal_range: (usize, usize),
pub maximum_useful: Option<usize>, pub penalty_curve: PenaltyCurve,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeadingExpectation {
pub require_h1: bool,
pub minimum_headings: usize,
pub maximum_heading_depth: usize,
pub logical_hierarchy: bool,
pub heading_length_limits: Option<(usize, usize)>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MediaExpectation {
pub minimum_images: usize,
pub image_to_text_ratio: Option<(f32, f32)>, pub alt_text_coverage: f32, pub require_audio: Option<bool>,
pub require_video: Option<bool>,
pub audio_duration_minimum: Option<usize>, pub video_duration_minimum: Option<usize>, pub transcript_preferred: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechnicalExpectation {
pub max_page_size_kb: Option<usize>,
pub min_mobile_score: Option<f32>,
pub required_meta_tags: Vec<String>,
pub max_load_time_ms: Option<usize>,
pub min_lighthouse_performance: Option<f32>,
pub ssl_required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeoExpectation {
pub title_length_range: Option<(usize, usize)>,
pub meta_description_required: bool,
pub meta_description_length_range: Option<(usize, usize)>,
pub canonical_url_required: bool,
pub structured_data_required: bool,
pub open_graph_required: bool,
pub keywords_density_range: Option<(f32, f32)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PenaltyCurve {
Linear,
Exponential { base: f32 },
StepFunction { steps: Vec<(f32, f32)> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityBandConfig {
pub excellent: f32, pub good: f32, pub fair: f32, pub poor: f32, }
impl Default for QualityBandConfig {
fn default() -> Self {
Self {
excellent: 85.0,
good: 70.0,
fair: 50.0,
poor: 30.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PenaltyConfig {
pub severe_penalties: HashMap<String, GlobalPenalty>,
pub moderate_penalties: HashMap<String, GlobalPenalty>,
pub light_penalties: HashMap<String, GlobalPenalty>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalPenalty {
pub trigger_condition: PenaltyTrigger,
pub penalty_type: PenaltyType,
pub description: String,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PenaltyType {
FixedPoints { points: f32 },
Multiplier { factor: f32 },
CategoryPenalty { category: String, multiplier: f32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeficiencySeverity {
Minimal, Moderate, Severe, Critical, }
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BonusConfig {
pub excellence_bonuses: HashMap<String, GlobalBonus>,
pub achievement_bonuses: HashMap<String, GlobalBonus>,
pub synergy_bonuses: HashMap<String, GlobalBonus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalBonus {
pub trigger_condition: BonusTrigger,
pub bonus_points: f32,
pub description: String,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub struct ValidationReport {
pub is_valid: bool,
pub warnings: Vec<String>,
pub errors: Vec<String>,
pub suggestions: Vec<String>,
}
impl ValidationReport {
pub fn new() -> Self {
Self {
is_valid: true,
warnings: Vec::new(),
errors: Vec::new(),
suggestions: Vec::new(),
}
}
pub fn add_error(&mut self, error: String) {
self.errors.push(error);
self.is_valid = false;
}
pub fn add_warning(&mut self, warning: String) {
self.warnings.push(warning);
}
pub fn add_suggestion(&mut self, suggestion: String) {
self.suggestions.push(suggestion);
}
}
#[derive(Debug, Clone)]
pub struct ContentValidationResult {
pub violations: Vec<ContentViolation>,
pub penalties: Vec<ContentPenalty>,
pub compliance_score: f32,
}
#[derive(Debug, Clone)]
pub enum ContentViolation {
InsufficientWordCount {
actual: usize,
minimum: usize,
severity: DeficiencySeverity,
},
ExcessiveWordCount {
actual: usize,
maximum_useful: usize,
},
MissingHeadings {
actual: usize,
minimum: usize,
},
InvalidHeadingStructure {
issue: String,
},
InsufficientMedia {
media_type: String,
actual: usize,
minimum: usize,
},
TechnicalRequirementMissing {
requirement: String,
},
SeoRequirementMissing {
requirement: String,
},
}
#[derive(Debug, Clone)]
pub struct ContentPenalty {
pub violation_type: String,
pub penalty_points: f32,
pub description: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enhanced_profile_creation() {
let profile = EnhancedScoringProfile::default_with_name("Test Profile");
assert_eq!(profile.metadata.name, "Test Profile");
assert_eq!(profile.metadata.description, "Profile for Test Profile");
assert!(!profile.metadata.created_at.is_empty());
}
#[test]
fn test_content_expectations_default() {
let expectations = ContentExpectations::default();
assert!(expectations.word_count.is_none());
assert!(expectations.heading_structure.is_none());
assert!(expectations.media_requirements.is_none());
}
#[test]
fn test_quality_band_defaults() {
let bands = QualityBandConfig::default();
assert_eq!(bands.excellent, 85.0);
assert_eq!(bands.good, 70.0);
assert_eq!(bands.fair, 50.0);
assert_eq!(bands.poor, 30.0);
}
#[test]
fn test_validation_report() {
let mut report = ValidationReport::new();
assert!(report.is_valid);
report.add_error("Test error".to_string());
assert!(!report.is_valid);
assert_eq!(report.errors.len(), 1);
report.add_warning("Test warning".to_string());
assert_eq!(report.warnings.len(), 1);
}
#[test]
fn test_content_type_string_conversion() {
assert_eq!(ContentType::LongFormArticle.as_str(), "long_form_article");
assert_eq!(ContentType::ProductPage.as_str(), "product_page");
assert_eq!(ContentType::Article.as_str(), "article");
assert_eq!(ContentType::News.as_str(), "news");
}
}