mathhook_core/educational/enhanced_steps/
formatting.rs

1//! Format conversion and presentation methods for enhanced steps
2
3use crate::core::Expression;
4use crate::formatter::latex::LaTeXFormatter;
5use crate::formatter::{FormattingError, MathLanguage};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Format context for enhanced steps
10#[derive(Debug, Clone)]
11pub struct FormatContext {
12    pub target_format: MathLanguage,
13    pub include_intermediate_steps: bool,
14    pub verbosity_level: u8,
15}
16
17impl Default for FormatContext {
18    fn default() -> Self {
19        Self {
20            target_format: MathLanguage::default(),
21            include_intermediate_steps: true,
22            verbosity_level: 3,
23        }
24    }
25}
26
27impl FormatContext {
28    /// Format an expression according to the target format
29    pub fn format_expression(&self, expr: &Expression) -> Result<String, FormattingError> {
30        match self.target_format {
31            MathLanguage::LaTeX => expr.to_latex(None),
32            MathLanguage::Wolfram => Ok(expr.to_string()),
33            MathLanguage::Simple | MathLanguage::Human => Ok(expr.to_string()),
34            MathLanguage::Json => {
35                serde_json::to_string(expr).map_err(|e| FormattingError::SerializationError {
36                    message: e.to_string(),
37                })
38            }
39            MathLanguage::Markdown => Ok(expr.to_string()),
40        }
41    }
42
43    /// Format an expression with fallback on error
44    pub fn format_expression_safe(&self, expr: &Expression) -> String {
45        self.format_expression(expr)
46            .unwrap_or_else(|e| format!("{{error: {}}}", e))
47    }
48}
49
50/// Presentation hints for visual rendering
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct PresentationHints {
53    pub color_theme: String,
54    pub importance: u8,
55    pub animation: String,
56    pub layout: String,
57    pub interactive_elements: Vec<String>,
58}
59
60impl Default for PresentationHints {
61    fn default() -> Self {
62        Self {
63            color_theme: "blue".to_owned(),
64            importance: 3,
65            animation: "fade-in".to_owned(),
66            layout: "standard".to_owned(),
67            interactive_elements: Vec::new(),
68        }
69    }
70}
71
72/// Enhanced step explanation with multiple output formats
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct EnhancedStepExplanation {
75    pub steps: Vec<super::EnhancedStep>,
76    pub metadata: ExplanationMetadata,
77    pub summary: ExplanationSummary,
78}
79
80/// Explanation metadata
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct ExplanationMetadata {
83    pub step_count: usize,
84    pub difficulty_level: u8,
85    pub topic: String,
86    pub method: String,
87    pub estimated_time: u8,
88    pub prerequisites: Vec<String>,
89}
90
91/// Explanation summary
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ExplanationSummary {
94    pub problem: String,
95    pub approach: String,
96    pub answer: String,
97    pub key_insights: Vec<String>,
98    pub next_steps: Vec<String>,
99}
100
101impl EnhancedStepExplanation {
102    /// Create new explanation from steps
103    pub fn new(steps: Vec<super::EnhancedStep>) -> Self {
104        let metadata = ExplanationMetadata {
105            step_count: steps.len(),
106            difficulty_level: Self::calculate_difficulty(&steps),
107            topic: Self::determine_topic(&steps),
108            method: Self::determine_method(&steps),
109            estimated_time: Self::estimate_time(&steps),
110            prerequisites: Self::determine_prerequisites(&steps),
111        };
112
113        let summary = ExplanationSummary {
114            problem: Self::extract_problem(&steps),
115            approach: Self::extract_approach(&steps),
116            answer: Self::extract_answer(&steps),
117            key_insights: Self::extract_insights(&steps),
118            next_steps: Self::suggest_next_steps(&steps),
119        };
120
121        Self {
122            steps,
123            metadata,
124            summary,
125        }
126    }
127
128    /// Export as JSON for API consumption
129    pub fn to_json(&self) -> Result<String, serde_json::Error> {
130        serde_json::to_string_pretty(self)
131    }
132
133    /// Export human-readable text only
134    pub fn to_human_text(&self) -> String {
135        let mut text = String::new();
136        text.push_str(&format!("Problem: {}\n\n", self.summary.problem));
137
138        for (i, step) in self.steps.iter().enumerate() {
139            text.push_str(&format!(
140                "Step {}: {}\n{}\n\n",
141                i + 1,
142                step.title,
143                step.human_message
144            ));
145        }
146
147        text.push_str(&format!("Answer: {}\n", self.summary.answer));
148        text
149    }
150
151    /// Export structured data only (for external apps)
152    pub fn to_api_data(&self) -> HashMap<String, serde_json::Value> {
153        let mut data = HashMap::new();
154
155        data.insert(
156            "metadata".to_owned(),
157            serde_json::to_value(&self.metadata).unwrap(),
158        );
159        data.insert(
160            "summary".to_owned(),
161            serde_json::to_value(&self.summary).unwrap(),
162        );
163
164        let step_data: Vec<serde_json::Value> = self
165            .steps
166            .iter()
167            .map(|step| serde_json::to_value(&step.api_data).unwrap())
168            .collect();
169        data.insert("steps".to_owned(), serde_json::Value::Array(step_data));
170
171        data
172    }
173
174    fn calculate_difficulty(steps: &[super::EnhancedStep]) -> u8 {
175        match steps.len() {
176            1..=3 => 2,
177            4..=6 => 4,
178            7..=10 => 6,
179            _ => 8,
180        }
181    }
182
183    fn determine_topic(steps: &[super::EnhancedStep]) -> String {
184        if let Some(first_step) = steps.first() {
185            first_step.api_data.category.clone()
186        } else {
187            "unknown".to_owned()
188        }
189    }
190
191    fn determine_method(steps: &[super::EnhancedStep]) -> String {
192        if steps
193            .iter()
194            .any(|s| s.api_data.operation.contains("quadratic_formula"))
195        {
196            "Quadratic Formula".to_owned()
197        } else if steps
198            .iter()
199            .any(|s| s.api_data.operation.contains("isolation"))
200        {
201            "Variable Isolation".to_owned()
202        } else {
203            "General Algebraic Method".to_owned()
204        }
205    }
206
207    fn estimate_time(steps: &[super::EnhancedStep]) -> u8 {
208        (steps.len() as u8).saturating_mul(2).min(30)
209    }
210
211    fn determine_prerequisites(steps: &[super::EnhancedStep]) -> Vec<String> {
212        let mut prereqs = vec!["Basic Algebra".to_owned()];
213
214        if steps
215            .iter()
216            .any(|s| s.api_data.category == "quadratic_equation")
217        {
218            prereqs.push("Quadratic Equations".to_owned());
219        }
220
221        prereqs
222    }
223
224    fn extract_problem(steps: &[super::EnhancedStep]) -> String {
225        if let Some(first_step) = steps.first() {
226            first_step.math_context.equation.clone()
227        } else {
228            "Unknown problem".to_owned()
229        }
230    }
231
232    fn extract_approach(steps: &[super::EnhancedStep]) -> String {
233        Self::determine_method(steps)
234    }
235
236    fn extract_answer(steps: &[super::EnhancedStep]) -> String {
237        if let Some(last_step) = steps.last() {
238            last_step
239                .api_data
240                .outputs
241                .get("solution")
242                .or_else(|| last_step.api_data.outputs.get("result"))
243                .unwrap_or(&"Solution in progress".to_owned())
244                .clone()
245        } else {
246            "No solution yet".to_owned()
247        }
248    }
249
250    fn extract_insights(steps: &[super::EnhancedStep]) -> Vec<String> {
251        steps
252            .iter()
253            .filter(|step| step.message_key.message_type == "insight")
254            .map(|step| step.human_message.clone())
255            .collect()
256    }
257
258    fn suggest_next_steps(_steps: &[super::EnhancedStep]) -> Vec<String> {
259        vec![
260            "Try solving similar equations".to_owned(),
261            "Practice with different coefficients".to_owned(),
262            "Explore quadratic equations".to_owned(),
263        ]
264    }
265}
266
267/// Conversion to legacy step system
268impl From<super::EnhancedStep> for crate::educational::step_by_step::Step {
269    fn from(enhanced_step: super::EnhancedStep) -> Self {
270        Self::new(enhanced_step.title, enhanced_step.human_message)
271    }
272}
273
274impl From<EnhancedStepExplanation> for crate::educational::step_by_step::StepByStepExplanation {
275    fn from(enhanced_explanation: EnhancedStepExplanation) -> Self {
276        let legacy_steps: Vec<crate::educational::step_by_step::Step> = enhanced_explanation
277            .steps
278            .into_iter()
279            .map(|enhanced_step| enhanced_step.into())
280            .collect();
281
282        Self::new(legacy_steps)
283    }
284}