Skip to main content

native_whisperx/config/
report.rs

1//! User-facing workflow report types returned after output writing.
2
3use std::path::PathBuf;
4
5use audio_analysis_transcription::TranscriptionPipelineResponse;
6use serde::{Deserialize, Serialize};
7
8use super::{
9    AutomaticWorkflowSelection, AutomaticWorkflowSelectionResource, ConfigSelection,
10    ModelResourceSource, OutputFormat,
11};
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct NativeWhisperxReport {
16    pub response: TranscriptionPipelineResponse,
17    #[serde(default)]
18    pub output_files: Vec<OutputFile>,
19    #[serde(
20        default,
21        skip_serializing_if = "NativeWorkflowSelectionReport::is_empty"
22    )]
23    pub workflow_selection: NativeWorkflowSelectionReport,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct OutputFile {
29    pub format: OutputFormat,
30    pub path: PathBuf,
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct NativeWorkflowSelectionReport {
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub selected_vad: Option<SelectedVadReport>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub selected_diarization_model: Option<SelectedDiarizationModelReport>,
40}
41
42impl NativeWorkflowSelectionReport {
43    pub fn from_selection(selection: &AutomaticWorkflowSelection) -> Self {
44        let selected_vad = selection
45            .decisions
46            .iter()
47            .find(|decision| decision.target == AutomaticWorkflowSelectionResource::Vad)
48            .map(|decision| SelectedVadReport {
49                method: selection.config.vad.method.as_whisperx_arg().to_string(),
50                selection: decision.selection,
51                resource_source: decision.source,
52            });
53        let selected_diarization_model = selection
54            .config
55            .diarization
56            .enabled
57            .then(|| {
58                selection
59                    .decisions
60                    .iter()
61                    .find(|decision| {
62                        decision.target == AutomaticWorkflowSelectionResource::Diarization
63                    })
64                    .map(|decision| SelectedDiarizationModelReport {
65                        model_id: selection.config.diarization.model_id.clone(),
66                        selection: decision.selection,
67                        resource_source: decision.source,
68                    })
69            })
70            .flatten();
71
72        Self {
73            selected_vad,
74            selected_diarization_model,
75        }
76    }
77
78    pub fn is_empty(&self) -> bool {
79        self.selected_vad.is_none() && self.selected_diarization_model.is_none()
80    }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct SelectedVadReport {
86    pub method: String,
87    pub selection: ConfigSelection,
88    pub resource_source: ModelResourceSource,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct SelectedDiarizationModelReport {
94    pub model_id: String,
95    pub selection: ConfigSelection,
96    pub resource_source: ModelResourceSource,
97}
98
99#[cfg(test)]
100mod tests {
101    use std::path::PathBuf;
102
103    use super::*;
104    use crate::config::{
105        AlignmentConfig, AsrConfig, DiarizationConfig, InputSource, NativeWhisperxConfig,
106        OutputConfig, TranslationConfig, VadConfig, VadMethod,
107    };
108
109    #[test]
110    fn automatic_selection_report_summarizes_automatic_choices_without_paths_or_tokens() {
111        let secret = "hf_secret_token";
112        let selection = AutomaticWorkflowSelection {
113            config: NativeWhisperxConfig {
114                input: InputSource::Path {
115                    path: PathBuf::from("sample.wav"),
116                },
117                asr: AsrConfig::default(),
118                translation: TranslationConfig::default(),
119                vad: VadConfig {
120                    method: VadMethod::Pyannote,
121                    ..VadConfig::default()
122                },
123                alignment: AlignmentConfig::default(),
124                diarization: DiarizationConfig {
125                    enabled: true,
126                    model_id: "pyannote/speaker-diarization-community-1".to_string(),
127                    hf_token: Some(secret.to_string()),
128                    ..DiarizationConfig::default()
129                },
130                output: OutputConfig::default(),
131            },
132            decisions: vec![
133                automatic_decision(
134                    AutomaticWorkflowSelectionResource::Vad,
135                    Some("pyannote/segmentation-3.0"),
136                ),
137                automatic_decision(
138                    AutomaticWorkflowSelectionResource::Diarization,
139                    Some("pyannote/speaker-diarization-community-1"),
140                ),
141            ],
142        };
143
144        let report = NativeWorkflowSelectionReport::from_selection(&selection);
145
146        assert_eq!(
147            report.selected_vad,
148            Some(SelectedVadReport {
149                method: "pyannote".to_string(),
150                selection: ConfigSelection::Automatic,
151                resource_source: ModelResourceSource::ModelDir,
152            })
153        );
154        assert_eq!(
155            report.selected_diarization_model,
156            Some(SelectedDiarizationModelReport {
157                model_id: "pyannote/speaker-diarization-community-1".to_string(),
158                selection: ConfigSelection::Automatic,
159                resource_source: ModelResourceSource::ModelDir,
160            })
161        );
162        let json = serde_json::to_string(&report).expect("selection report json");
163        assert!(!json.contains("/models"));
164        assert!(!json.contains(secret));
165    }
166
167    #[test]
168    fn automatic_selection_report_summarizes_explicit_choices() {
169        let selection = AutomaticWorkflowSelection {
170            config: NativeWhisperxConfig {
171                input: InputSource::Path {
172                    path: PathBuf::from("sample.wav"),
173                },
174                asr: AsrConfig::default(),
175                translation: TranslationConfig::default(),
176                vad: VadConfig {
177                    selection: ConfigSelection::Explicit,
178                    method: VadMethod::Energy,
179                    ..VadConfig::default()
180                },
181                alignment: AlignmentConfig::default(),
182                diarization: DiarizationConfig {
183                    enabled: true,
184                    model_selection: ConfigSelection::Explicit,
185                    model_id: "native-spectral-speaker-baseline".to_string(),
186                    ..DiarizationConfig::default()
187                },
188                output: OutputConfig::default(),
189            },
190            decisions: vec![
191                explicit_decision(AutomaticWorkflowSelectionResource::Vad, Some("energy")),
192                explicit_decision(
193                    AutomaticWorkflowSelectionResource::Diarization,
194                    Some("native-spectral-speaker-baseline"),
195                ),
196            ],
197        };
198
199        let report = NativeWorkflowSelectionReport::from_selection(&selection);
200
201        assert_eq!(
202            report.selected_vad,
203            Some(SelectedVadReport {
204                method: "energy".to_string(),
205                selection: ConfigSelection::Explicit,
206                resource_source: ModelResourceSource::ExplicitConfig,
207            })
208        );
209        assert_eq!(
210            report.selected_diarization_model,
211            Some(SelectedDiarizationModelReport {
212                model_id: "native-spectral-speaker-baseline".to_string(),
213                selection: ConfigSelection::Explicit,
214                resource_source: ModelResourceSource::ExplicitConfig,
215            })
216        );
217    }
218
219    fn automatic_decision(
220        target: AutomaticWorkflowSelectionResource,
221        model_id: Option<&str>,
222    ) -> super::super::AutomaticWorkflowSelectionDecision {
223        super::super::AutomaticWorkflowSelectionDecision {
224            target,
225            selection: ConfigSelection::Automatic,
226            model_id: model_id.map(str::to_string),
227            source: ModelResourceSource::ModelDir,
228            path: Some(PathBuf::from("/models/secret")),
229        }
230    }
231
232    fn explicit_decision(
233        target: AutomaticWorkflowSelectionResource,
234        model_id: Option<&str>,
235    ) -> super::super::AutomaticWorkflowSelectionDecision {
236        super::super::AutomaticWorkflowSelectionDecision {
237            target,
238            selection: ConfigSelection::Explicit,
239            model_id: model_id.map(str::to_string),
240            source: ModelResourceSource::ExplicitConfig,
241            path: Some(PathBuf::from("/models/secret")),
242        }
243    }
244}