use super::builder::ReflectionData;
use super::templates;
use super::types::{PlaybookRequest, PlaybookSynthesisSource, RecommendedPlaybook};
use crate::error::Result;
use crate::pattern::Pattern;
use crate::semantic::EpisodeSummary;
use tracing::{info, instrument};
use uuid::Uuid;
#[derive(Clone)]
pub struct PlaybookGenerator {
min_pattern_confidence: f32,
max_patterns: usize,
}
impl Default for PlaybookGenerator {
fn default() -> Self {
Self::new()
}
}
impl PlaybookGenerator {
#[must_use]
pub fn new() -> Self {
Self {
min_pattern_confidence: 0.5,
max_patterns: 10,
}
}
#[must_use]
pub fn with_min_pattern_confidence(mut self, confidence: f32) -> Self {
self.min_pattern_confidence = confidence.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn with_max_patterns(mut self, max: usize) -> Self {
self.max_patterns = max;
self
}
#[instrument(skip(self, patterns, summaries, reflections), fields(
task = %request.task_description,
domain = %request.domain,
pattern_count = patterns.len(),
summary_count = summaries.len()
))]
pub fn generate(
&self,
request: &PlaybookRequest,
patterns: &[Pattern],
summaries: &[EpisodeSummary],
reflections: &[ReflectionData],
) -> Result<RecommendedPlaybook> {
let playbook_id = Uuid::new_v4();
let mut source = PlaybookSynthesisSource::new();
let task_match_score = templates::calculate_task_match(request, patterns);
let ordered_steps = templates::synthesize_steps(
patterns,
&mut source,
request.max_steps,
self.max_patterns,
);
let (when_to_apply, when_not_to_apply) =
templates::synthesize_applicability(patterns, &request.context, self.max_patterns);
let pitfalls = templates::synthesize_pitfalls(reflections, &mut source);
let expected_outcome =
templates::synthesize_expected_outcome(patterns, summaries, &mut source);
let confidence = templates::calculate_confidence(patterns, summaries, &source);
let why_relevant = templates::generate_why_relevant(patterns, summaries, &source);
let supporting_pattern_ids = patterns
.iter()
.take(self.max_patterns)
.map(|p| p.id())
.collect();
let supporting_episode_ids = source.episode_ids.clone();
info!(playbook_id = %playbook_id, task_match_score, confidence, step_count = ordered_steps.len(), source_count = source.total_sources(), "Generated playbook");
Ok(RecommendedPlaybook {
playbook_id,
task_match_score,
why_relevant,
when_to_apply,
when_not_to_apply,
ordered_steps,
pitfalls,
expected_outcome,
confidence,
supporting_pattern_ids,
supporting_episode_ids,
created_at: chrono::Utc::now(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::PatternEffectiveness;
use crate::types::{ComplexityLevel, TaskContext};
fn create_test_pattern() -> Pattern {
Pattern::ToolSequence {
id: Uuid::new_v4(),
tools: vec!["read_file".to_string(), "edit_file".to_string()],
context: TaskContext {
domain: "web-api".to_string(),
language: Some("rust".to_string()),
framework: Some("axum".to_string()),
complexity: ComplexityLevel::Moderate,
tags: vec!["api".to_string()],
},
success_rate: 0.85,
avg_latency: chrono::Duration::milliseconds(100),
occurrence_count: 10,
effectiveness: PatternEffectiveness::new(),
}
}
fn create_test_summary() -> EpisodeSummary {
EpisodeSummary::new(Uuid::new_v4())
}
#[test]
fn test_generator_creation() {
let generator = PlaybookGenerator::new()
.with_min_pattern_confidence(0.7)
.with_max_patterns(5);
assert_eq!(generator.min_pattern_confidence, 0.7);
assert_eq!(generator.max_patterns, 5);
}
#[test]
fn test_generate_playbook() {
let generator = PlaybookGenerator::new();
let request = PlaybookRequest::new("Test task", "web-api");
let patterns = vec![create_test_pattern()];
let summaries = vec![create_test_summary()];
let reflections = vec![];
let playbook = generator
.generate(&request, &patterns, &summaries, &reflections)
.unwrap();
assert!(playbook.task_match_score > 0.0);
assert!(!playbook.ordered_steps.is_empty());
assert!(!playbook.why_relevant.is_empty());
}
#[test]
fn test_generate_playbook_empty_inputs() {
let generator = PlaybookGenerator::new();
let request = PlaybookRequest::new("Test task", "web-api");
let playbook = generator.generate(&request, &[], &[], &[]).unwrap();
assert_eq!(playbook.task_match_score, 0.0);
assert!(playbook.ordered_steps.is_empty());
assert_eq!(playbook.confidence, 0.0);
}
}