use crate::config::enhanced_models::*;
use crate::config::profile_builder::{ContentType, ProfileBuilder};
use std::collections::HashMap;
pub struct ProfileTemplates;
#[derive(Debug, thiserror::Error)]
pub enum TemplateError {
#[error("Unknown template: {0}")]
UnknownTemplate(String),
#[error("Template creation failed: {0}")]
CreationFailed(String),
}
impl ProfileTemplates {
pub fn get_template(template_name: &str) -> Result<EnhancedScoringProfile, TemplateError> {
match template_name.to_lowercase().as_str() {
"content_article" | "article" | "blog" => Ok(Self::content_article_template()),
"news" | "news_article" => Ok(Self::news_article_template()),
"product" | "product_page" | "ecommerce" => Ok(Self::product_page_template()),
"landing_page" | "landing" => Ok(Self::landing_page_template()),
"portfolio" | "showcase" => Ok(Self::portfolio_template()),
"documentation" | "docs" => Ok(Self::documentation_template()),
"podcast" | "podcast_episode" => Ok(Self::podcast_episode_template()),
"video" | "video_content" => Ok(Self::video_content_template()),
"service" | "service_page" => Ok(Self::service_page_template()),
"contact" | "contact_page" => Ok(Self::contact_page_template()),
_ => Err(TemplateError::UnknownTemplate(template_name.to_string())),
}
}
pub fn list_templates() -> Vec<&'static str> {
vec![
"content_article",
"news",
"product",
"landing_page",
"portfolio",
"documentation",
"podcast",
"video",
"service",
"contact",
]
}
pub fn get_template_info(template_name: &str) -> Result<ProfileMetadata, TemplateError> {
let profile = Self::get_template(template_name)?;
Ok(profile.metadata)
}
fn content_article_template() -> EnhancedScoringProfile {
ProfileBuilder::new("Content Article")
.for_content_type(ContentType::LongFormArticle)
.with_description("Optimized for long-form articles, blog posts, and editorial content")
.with_tags(vec![
"content-focused".to_string(),
"text-heavy".to_string(),
"readability-important".to_string(),
])
.build()
.expect("Failed to build content article template")
}
fn news_article_template() -> EnhancedScoringProfile {
ProfileBuilder::new("News Article")
.for_content_type(ContentType::NewsArticle)
.with_description("Optimized for news articles, press releases, and timely content")
.with_tags(vec![
"news".to_string(),
"timely".to_string(),
"concise".to_string(),
"seo-important".to_string(),
])
.build()
.expect("Failed to build news article template")
}
fn product_page_template() -> EnhancedScoringProfile {
ProfileBuilder::new("Product Page")
.for_content_type(ContentType::ProductPage)
.with_description("Optimized for e-commerce product pages and service descriptions")
.with_tags(vec![
"product".to_string(),
"ecommerce".to_string(),
"conversion-focused".to_string(),
"media-heavy".to_string(),
])
.build()
.expect("Failed to build product page template")
}
fn portfolio_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Portfolio".to_string(),
description: "Optimized for portfolio pages and personal showcases".to_string(),
target_content_types: vec![
"portfolio".to_string(),
"showcase".to_string(),
"personal".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"portfolio".to_string(),
"visual".to_string(),
"creative".to_string(),
"personal".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("media".to_string(), 0.50);
weights.insert("structure".to_string(), 0.20);
weights.insert("content".to_string(), 0.15);
weights.insert("technical".to_string(), 0.10);
weights.insert("accessibility".to_string(), 0.05);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 25,
optimal_range: (50, 300),
maximum_useful: Some(800),
penalty_curve: PenaltyCurve::Linear,
}),
media_requirements: Some(MediaExpectation {
minimum_images: 5,
image_to_text_ratio: Some((1.0, 5.0)),
alt_text_coverage: 0.80,
require_audio: Some(false),
require_video: Some(false),
audio_duration_minimum: None,
video_duration_minimum: None,
transcript_preferred: None,
}),
..Default::default()
},
metric_overrides: Self::create_portfolio_overrides(),
quality_bands: QualityBandConfig {
excellent: 85.0,
good: 70.0,
fair: 50.0,
poor: 30.0,
},
penalties: Self::create_portfolio_penalties(),
bonuses: Self::create_portfolio_bonuses(),
}
}
fn landing_page_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Landing Page".to_string(),
description: "Optimized for conversion-focused landing pages".to_string(),
target_content_types: vec![
"landing".to_string(),
"conversion".to_string(),
"marketing".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"landing".to_string(),
"conversion".to_string(),
"marketing".to_string(),
"cta-focused".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("seo".to_string(), 0.30);
weights.insert("content".to_string(), 0.25);
weights.insert("technical".to_string(), 0.20);
weights.insert("media".to_string(), 0.15);
weights.insert("structure".to_string(), 0.10);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 200,
optimal_range: (400, 1200),
maximum_useful: Some(2000),
penalty_curve: PenaltyCurve::Linear,
}),
technical_requirements: Some(TechnicalExpectation {
max_page_size_kb: Some(1024),
min_mobile_score: Some(90.0),
required_meta_tags: vec!["description".to_string(), "viewport".to_string()],
max_load_time_ms: Some(2000),
min_lighthouse_performance: Some(80.0),
ssl_required: true,
}),
..Default::default()
},
metric_overrides: HashMap::new(), quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn documentation_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Documentation".to_string(),
description: "Optimized for technical documentation and guides".to_string(),
target_content_types: vec![
"documentation".to_string(),
"guide".to_string(),
"manual".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"documentation".to_string(),
"technical".to_string(),
"structured".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.60);
weights.insert("structure".to_string(), 0.25);
weights.insert("accessibility".to_string(), 0.10);
weights.insert("technical".to_string(), 0.05);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 500,
optimal_range: (1000, 4000),
maximum_useful: Some(8000),
penalty_curve: PenaltyCurve::Linear,
}),
heading_structure: Some(HeadingExpectation {
require_h1: true,
minimum_headings: 5,
maximum_heading_depth: 5,
logical_hierarchy: true,
heading_length_limits: Some((5, 80)),
}),
..Default::default()
},
metric_overrides: HashMap::new(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn podcast_episode_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Podcast Episode".to_string(),
description: "Optimized for podcast episode pages with audio content".to_string(),
target_content_types: vec![
"podcast".to_string(),
"audio_content".to_string(),
"episode".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"podcast".to_string(),
"audio".to_string(),
"episodic".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("media".to_string(), 0.40);
weights.insert("content".to_string(), 0.30);
weights.insert("seo".to_string(), 0.20);
weights.insert("technical".to_string(), 0.10);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 100,
optimal_range: (200, 800),
maximum_useful: Some(1500),
penalty_curve: PenaltyCurve::Linear,
}),
media_requirements: Some(MediaExpectation {
minimum_images: 1,
image_to_text_ratio: Some((0.1, 1.0)),
alt_text_coverage: 0.90,
require_audio: Some(true),
require_video: Some(false),
audio_duration_minimum: Some(300), video_duration_minimum: None,
transcript_preferred: Some(true),
}),
..Default::default()
},
metric_overrides: HashMap::new(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn video_content_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Video Content".to_string(),
description: "Optimized for video-centric content pages".to_string(),
target_content_types: vec![
"video".to_string(),
"multimedia".to_string(),
"streaming".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"video".to_string(),
"multimedia".to_string(),
"visual".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("media".to_string(), 0.50);
weights.insert("technical".to_string(), 0.25);
weights.insert("content".to_string(), 0.15);
weights.insert("seo".to_string(), 0.10);
weights
},
content_expectations: ContentExpectations {
media_requirements: Some(MediaExpectation {
minimum_images: 1,
image_to_text_ratio: Some((0.5, 3.0)),
alt_text_coverage: 0.85,
require_audio: Some(false),
require_video: Some(true),
audio_duration_minimum: None,
video_duration_minimum: Some(60), transcript_preferred: Some(true),
}),
technical_requirements: Some(TechnicalExpectation {
max_page_size_kb: Some(5120), min_mobile_score: Some(75.0),
required_meta_tags: vec!["description".to_string(), "viewport".to_string()],
max_load_time_ms: Some(4000),
min_lighthouse_performance: Some(60.0),
ssl_required: true,
}),
..Default::default()
},
metric_overrides: HashMap::new(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn service_page_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Service Page".to_string(),
description: "Optimized for service description and business pages".to_string(),
target_content_types: vec![
"service".to_string(),
"business".to_string(),
"offering".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"service".to_string(),
"business".to_string(),
"conversion".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("content".to_string(), 0.35);
weights.insert("seo".to_string(), 0.30);
weights.insert("structure".to_string(), 0.20);
weights.insert("technical".to_string(), 0.15);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 200,
optimal_range: (500, 1500),
maximum_useful: Some(3000),
penalty_curve: PenaltyCurve::Linear,
}),
..Default::default()
},
metric_overrides: HashMap::new(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn contact_page_template() -> EnhancedScoringProfile {
EnhancedScoringProfile {
metadata: ProfileMetadata {
name: "Contact Page".to_string(),
description: "Optimized for contact and information pages".to_string(),
target_content_types: vec![
"contact".to_string(),
"about".to_string(),
"info".to_string(),
],
version: "2.0.0".to_string(),
author: "system".to_string(),
created_at: chrono::Utc::now().to_rfc3339(),
tags: vec![
"contact".to_string(),
"utility".to_string(),
"information".to_string(),
],
},
category_weights: {
let mut weights = HashMap::new();
weights.insert("structure".to_string(), 0.40);
weights.insert("content".to_string(), 0.25);
weights.insert("accessibility".to_string(), 0.20);
weights.insert("technical".to_string(), 0.15);
weights
},
content_expectations: ContentExpectations {
word_count: Some(WordCountExpectation {
minimum: 50,
optimal_range: (100, 500),
maximum_useful: Some(1000),
penalty_curve: PenaltyCurve::Linear,
}),
..Default::default()
},
metric_overrides: HashMap::new(),
quality_bands: QualityBandConfig::default(),
penalties: PenaltyConfig::default(),
bonuses: BonusConfig::default(),
}
}
fn create_content_article_overrides() -> HashMap<String, MetricOverride> {
let mut overrides = HashMap::new();
overrides.insert(
"word_count".to_string(),
MetricOverride {
weight: 0.25,
enabled: true,
thresholds: Some(crate::metrics::definitions::ThresholdSet {
excellent: 1500.0,
good: 800.0,
fair: 400.0,
poor: 100.0,
}),
penalty_multiplier: 2.5,
bonus_conditions: vec![crate::metrics::definitions::BonusCondition {
condition_type: crate::metrics::definitions::BonusConditionType::ValueBetween {
min: 1500.0,
max: 3000.0,
},
bonus_points: 5.0,
description: "Optimal content length for articles".to_string(),
}],
scoring_function_override: None,
},
);
overrides.insert(
"readability_fk".to_string(),
MetricOverride {
weight: 0.20,
enabled: true,
thresholds: Some(crate::metrics::definitions::ThresholdSet {
excellent: 10.0,
good: 14.0,
fair: 18.0,
poor: 22.0,
}),
penalty_multiplier: 1.8,
bonus_conditions: vec![crate::metrics::definitions::BonusCondition {
condition_type: crate::metrics::definitions::BonusConditionType::ValueBetween {
min: 6.0,
max: 12.0,
},
bonus_points: 5.0,
description: "Optimal readability range".to_string(),
}],
scoring_function_override: None,
},
);
overrides
}
fn create_news_overrides() -> HashMap<String, MetricOverride> {
let mut overrides = HashMap::new();
overrides.insert(
"word_count".to_string(),
MetricOverride {
weight: 0.15,
enabled: true,
thresholds: Some(crate::metrics::definitions::ThresholdSet {
excellent: 800.0,
good: 400.0,
fair: 200.0,
poor: 50.0,
}),
penalty_multiplier: 1.2,
bonus_conditions: Vec::new(),
scoring_function_override: None,
},
);
overrides
}
fn create_product_overrides() -> HashMap<String, MetricOverride> {
let mut overrides = HashMap::new();
overrides.insert(
"word_count".to_string(),
MetricOverride {
weight: 0.08,
enabled: true,
thresholds: Some(crate::metrics::definitions::ThresholdSet {
excellent: 300.0,
good: 150.0,
fair: 75.0,
poor: 25.0,
}),
penalty_multiplier: 0.8,
bonus_conditions: Vec::new(),
scoring_function_override: None,
},
);
overrides
}
fn create_portfolio_overrides() -> HashMap<String, MetricOverride> {
let mut overrides = HashMap::new();
overrides.insert(
"word_count".to_string(),
MetricOverride {
weight: 0.05,
enabled: true,
thresholds: Some(crate::metrics::definitions::ThresholdSet {
excellent: 200.0,
good: 100.0,
fair: 50.0,
poor: 10.0,
}),
penalty_multiplier: 0.5,
bonus_conditions: Vec::new(),
scoring_function_override: None,
},
);
overrides
}
fn create_content_article_penalties() -> PenaltyConfig {
PenaltyConfig::default()
}
fn create_content_article_bonuses() -> BonusConfig {
BonusConfig::default()
}
fn create_news_penalties() -> PenaltyConfig {
PenaltyConfig::default()
}
fn create_news_bonuses() -> BonusConfig {
BonusConfig::default()
}
fn create_product_penalties() -> PenaltyConfig {
PenaltyConfig::default()
}
fn create_product_bonuses() -> BonusConfig {
BonusConfig::default()
}
fn create_portfolio_penalties() -> PenaltyConfig {
PenaltyConfig::default()
}
fn create_portfolio_bonuses() -> BonusConfig {
BonusConfig::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::profile_builder::ProfileBuilder;
#[test]
fn test_template_listing() {
let templates = ProfileTemplates::list_templates();
assert!(!templates.is_empty());
assert!(templates.contains(&"content_article"));
assert!(templates.contains(&"news"));
assert!(templates.contains(&"product"));
}
#[test]
fn test_get_content_article_template() {
let template = ProfileTemplates::get_template("content_article");
assert!(template.is_ok());
let profile = template.unwrap();
assert_eq!(profile.metadata.name, "Content Article");
assert!(profile.category_weights.contains_key("content"));
assert_eq!(profile.category_weights["content"], 0.80);
}
#[test]
fn test_get_unknown_template() {
let template = ProfileTemplates::get_template("unknown_template");
assert!(template.is_err());
assert!(matches!(
template.unwrap_err(),
TemplateError::UnknownTemplate(_)
));
}
#[test]
fn test_profile_builder() {
let profile = ProfileBuilder::new("Test Profile")
.with_description("A test profile")
.with_category_weights({
let mut weights = std::collections::HashMap::new();
weights.insert("content".to_string(), 0.6);
weights.insert("seo".to_string(), 0.2);
weights.insert("technical".to_string(), 0.1);
weights.insert("structure".to_string(), 0.1);
weights
})
.build();
assert!(profile.is_ok());
let profile = profile.unwrap();
assert_eq!(profile.metadata.name, "Test Profile");
assert_eq!(profile.metadata.description, "A test profile");
}
}