finance_query/models/corporate/transcript/
mod.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Transcript {
11 pub transcript_content: TranscriptContent,
13 pub transcript_metadata: TranscriptMetadata,
15}
16
17impl Transcript {
18 pub fn text(&self) -> &str {
20 self.transcript_content
21 .transcript
22 .as_ref()
23 .map(|t| t.text.as_str())
24 .unwrap_or("")
25 }
26
27 pub fn quarter(&self) -> &str {
29 &self.transcript_metadata.fiscal_period
30 }
31
32 pub fn year(&self) -> i32 {
34 self.transcript_metadata.fiscal_year
35 }
36
37 pub fn speaker_name(&self, speaker_id: i32) -> Option<&str> {
39 self.transcript_content
40 .speaker_mapping
41 .iter()
42 .find(|s| s.speaker == speaker_id)
43 .map(|s| s.speaker_data.name.as_str())
44 }
45
46 #[cfg(feature = "sentiment")]
51 pub(crate) fn score_sentiment(&mut self) {
52 if let Some(t) = self.transcript_content.transcript.as_mut() {
53 for p in t.paragraphs.iter_mut() {
54 p.sentiment = Some(crate::models::sentiment::analyze(&p.text));
55 }
56 }
57 }
58
59 #[cfg(feature = "sentiment")]
64 pub fn overall_sentiment(&self) -> crate::models::sentiment::Sentiment {
65 let texts: Vec<&str> = self
66 .transcript_content
67 .transcript
68 .as_ref()
69 .map(|t| t.paragraphs.iter().map(|p| p.text.as_str()).collect())
70 .unwrap_or_default();
71 crate::models::sentiment::aggregate_weighted(&texts)
72 .unwrap_or_else(crate::models::sentiment::Sentiment::neutral)
73 }
74
75 pub fn paragraphs_with_speakers(&self) -> Vec<(&Paragraph, Option<&str>)> {
77 self.transcript_content
78 .transcript
79 .as_ref()
80 .map(|t| {
81 t.paragraphs
82 .iter()
83 .map(|p| (p, self.speaker_name(p.speaker)))
84 .collect()
85 })
86 .unwrap_or_default()
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct TranscriptContent {
93 pub company_id: i64,
95 pub event_id: i64,
97 #[serde(default)]
99 pub version: Option<String>,
100 #[serde(default)]
102 pub speaker_mapping: Vec<SpeakerMapping>,
103 #[serde(default)]
105 pub transcript: Option<TranscriptData>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct SpeakerMapping {
111 pub speaker: i32,
113 pub speaker_data: SpeakerData,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct SpeakerData {
120 #[serde(default)]
122 pub company: Option<String>,
123 #[serde(default)]
125 pub name: String,
126 #[serde(default)]
128 pub role: Option<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct TranscriptData {
134 #[serde(default)]
136 pub number_of_speakers: i32,
137 #[serde(default)]
139 pub text: String,
140 #[serde(default)]
142 pub paragraphs: Vec<Paragraph>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct Paragraph {
148 #[serde(default)]
150 pub speaker: i32,
151 #[serde(default)]
153 pub start: f64,
154 #[serde(default)]
156 pub end: f64,
157 #[serde(default)]
159 pub text: String,
160 #[serde(default)]
162 pub sentences: Vec<Sentence>,
163 #[cfg(feature = "sentiment")]
166 #[serde(skip_serializing_if = "Option::is_none", default)]
167 pub sentiment: Option<crate::models::sentiment::Sentiment>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct Sentence {
173 #[serde(default)]
175 pub start: f64,
176 #[serde(default)]
178 pub end: f64,
179 #[serde(default)]
181 pub text: String,
182 #[serde(default)]
184 pub words: Vec<Word>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct Word {
190 #[serde(default)]
192 pub word: String,
193 #[serde(default)]
195 pub punctuated_word: String,
196 #[serde(default)]
198 pub start: f64,
199 #[serde(default)]
201 pub end: f64,
202 #[serde(default)]
204 pub confidence: f64,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct TranscriptMetadata {
211 #[serde(default)]
213 pub date: i64,
214 #[serde(default)]
216 pub event_id: i64,
217 #[serde(default)]
219 pub event_type: String,
220 #[serde(default)]
222 pub fiscal_period: String,
223 #[serde(default)]
225 pub fiscal_year: i32,
226 #[serde(default)]
228 pub is_latest: bool,
229 #[serde(default)]
231 pub s3_url: String,
232 #[serde(default)]
234 pub title: String,
235 #[serde(default)]
237 pub transcript_id: i64,
238 #[serde(default, rename = "type")]
240 pub transcript_type: String,
241 #[serde(default)]
243 pub updated: i64,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct TranscriptWithMeta {
252 pub event_id: String,
254 pub quarter: Option<String>,
256 pub year: Option<i32>,
258 pub title: String,
260 pub url: String,
262 pub transcript: Transcript,
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_deserialize_transcript() {
272 let json = r#"{
273 "transcriptContent": {
274 "company_id": 4742,
275 "event_id": 369370,
276 "version": "1.0.0",
277 "speaker_mapping": [
278 {
279 "speaker": 0,
280 "speaker_data": {
281 "company": "Apple",
282 "name": "Tim Cook",
283 "role": "CEO"
284 }
285 }
286 ],
287 "transcript": {
288 "number_of_speakers": 15,
289 "text": "Hello everyone...",
290 "paragraphs": []
291 }
292 },
293 "transcriptMetadata": {
294 "date": 1761858000,
295 "eventId": 369370,
296 "eventType": "Earnings Call",
297 "fiscalPeriod": "Q4",
298 "fiscalYear": 2025,
299 "isLatest": true,
300 "title": "Q4 2025"
301 }
302 }"#;
303
304 let transcript: Transcript = serde_json::from_str(json).unwrap();
305 assert_eq!(transcript.transcript_content.company_id, 4742);
306 assert_eq!(transcript.quarter(), "Q4");
307 assert_eq!(transcript.year(), 2025);
308 assert_eq!(transcript.speaker_name(0), Some("Tim Cook"));
309 }
310}