nu_analytics/core/report/
mod.rs1pub mod formats;
7pub mod term_scheduler;
8pub mod visualization;
9
10use crate::core::metrics::CurriculumMetrics;
11use crate::core::metrics_export::CurriculumSummary;
12use crate::core::models::{Degree, Plan, School, DAG};
13use std::error::Error;
14use std::path::Path;
15
16pub use formats::{HtmlReporter, MarkdownReporter, PdfReporter, ReportFormat};
17pub use term_scheduler::{SchedulerConfig, TermPlan, TermScheduler};
18pub use visualization::MermaidGenerator;
19
20#[derive(Debug, Clone)]
25pub struct ReportContext<'a> {
26 pub school: &'a School,
28 pub plan: &'a Plan,
30 pub degree: Option<&'a Degree>,
32 pub metrics: &'a CurriculumMetrics,
34 pub summary: &'a CurriculumSummary,
36 pub dag: &'a DAG,
38 pub term_plan: &'a TermPlan,
40}
41
42impl<'a> ReportContext<'a> {
43 #[must_use]
45 pub const fn new(
46 school: &'a School,
47 plan: &'a Plan,
48 degree: Option<&'a Degree>,
49 metrics: &'a CurriculumMetrics,
50 summary: &'a CurriculumSummary,
51 dag: &'a DAG,
52 term_plan: &'a TermPlan,
53 ) -> Self {
54 Self {
55 school,
56 plan,
57 degree,
58 metrics,
59 summary,
60 dag,
61 term_plan,
62 }
63 }
64
65 #[must_use]
67 pub fn institution_name(&self) -> &str {
68 self.plan
69 .institution
70 .as_deref()
71 .unwrap_or(&self.school.name)
72 }
73
74 #[must_use]
76 pub fn degree_name(&self) -> String {
77 self.degree
78 .map_or_else(|| self.plan.degree_id.clone(), Degree::id)
79 }
80
81 #[must_use]
83 pub fn system_type(&self) -> &str {
84 self.degree.map_or("semester", |d| d.system_type.as_str())
85 }
86
87 #[must_use]
89 pub fn cip_code(&self) -> &str {
90 self.degree.map_or("", |d| d.cip_code.as_str())
91 }
92
93 #[must_use]
95 pub fn total_credits(&self) -> f32 {
96 self.plan
97 .courses
98 .iter()
99 .filter_map(|key| self.school.get_course(key))
100 .map(|c| c.credit_hours)
101 .sum()
102 }
103
104 #[must_use]
106 pub const fn course_count(&self) -> usize {
107 self.plan.courses.len()
108 }
109
110 #[allow(clippy::cast_precision_loss)]
112 #[must_use]
113 pub fn years(&self) -> f32 {
114 let terms_used = self.term_plan.terms_used();
115 let terms_per_year = if self.term_plan.is_quarter_system {
116 3.0 } else {
118 2.0 };
120 (terms_used as f32 / terms_per_year).ceil()
121 }
122}
123
124pub trait ReportGenerator {
126 fn generate(&self, ctx: &ReportContext, output_path: &Path) -> Result<(), Box<dyn Error>>;
131
132 fn render(&self, ctx: &ReportContext) -> Result<String, Box<dyn Error>>;
137}