1use std::collections::HashMap;
4
5use chrono::NaiveDate;
6use serde::{Deserialize, Serialize};
7
8use crate::models::graph_properties::{GraphPropertyValue, ToNodeProperties};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum FilingType {
14 Form10K,
17 Form10Q,
19 Form8K,
21 Form20F,
23
24 Jahresabschluss,
27 EBilanz,
29
30 LiasseFiscale,
33
34 UkAnnualReturn,
37 Ct600,
39
40 YukaShokenHokokusho,
43
44 AnnualStatements,
47 QuarterlyReport,
49 TaxReturn,
51 Custom(String),
53}
54
55impl std::fmt::Display for FilingType {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Self::Form10K => write!(f, "10-K"),
59 Self::Form10Q => write!(f, "10-Q"),
60 Self::Form8K => write!(f, "8-K"),
61 Self::Form20F => write!(f, "20-F"),
62 Self::Jahresabschluss => write!(f, "Jahresabschluss"),
63 Self::EBilanz => write!(f, "E-Bilanz"),
64 Self::LiasseFiscale => write!(f, "Liasse fiscale"),
65 Self::UkAnnualReturn => write!(f, "Annual Return"),
66 Self::Ct600 => write!(f, "CT600"),
67 Self::YukaShokenHokokusho => write!(f, "有価証券報告書"),
68 Self::AnnualStatements => write!(f, "Annual Financial Statements"),
69 Self::QuarterlyReport => write!(f, "Quarterly Report"),
70 Self::TaxReturn => write!(f, "Tax Return"),
71 Self::Custom(s) => write!(f, "{s}"),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum FilingFrequency {
80 Annual,
82 SemiAnnual,
84 Quarterly,
86 Monthly,
88 EventDriven,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum FilingStatus {
96 NotDue,
98 Pending,
100 Filed,
102 FiledLate,
104 Overdue,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct FilingRequirement {
111 pub filing_type: FilingType,
113 pub frequency: FilingFrequency,
115 pub regulator: String,
117 pub jurisdiction: String,
119 pub deadline_days: u32,
121 pub electronic_filing: bool,
123 pub xbrl_required: bool,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct RegulatoryFiling {
130 pub filing_type: FilingType,
132 pub company_code: String,
134 pub jurisdiction: String,
136 pub period_end: NaiveDate,
138 pub deadline: NaiveDate,
140 pub filing_date: Option<NaiveDate>,
142 pub status: FilingStatus,
144 pub regulator: String,
146 pub filing_reference: Option<String>,
148}
149
150impl RegulatoryFiling {
151 pub fn new(
153 filing_type: FilingType,
154 company_code: impl Into<String>,
155 jurisdiction: impl Into<String>,
156 period_end: NaiveDate,
157 deadline: NaiveDate,
158 regulator: impl Into<String>,
159 ) -> Self {
160 Self {
161 filing_type,
162 company_code: company_code.into(),
163 jurisdiction: jurisdiction.into(),
164 period_end,
165 deadline,
166 filing_date: None,
167 status: FilingStatus::Pending,
168 regulator: regulator.into(),
169 filing_reference: None,
170 }
171 }
172
173 pub fn filed_on(mut self, date: NaiveDate) -> Self {
175 self.filing_date = Some(date);
176 self.status = if date <= self.deadline {
177 FilingStatus::Filed
178 } else {
179 FilingStatus::FiledLate
180 };
181 self
182 }
183
184 pub fn days_to_deadline(&self, from: NaiveDate) -> i64 {
186 (self.deadline - from).num_days()
187 }
188}
189
190impl ToNodeProperties for RegulatoryFiling {
191 fn node_type_name(&self) -> &'static str {
192 "regulatory_filing"
193 }
194 fn node_type_code(&self) -> u16 {
195 512
196 }
197 fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
198 let mut p = HashMap::new();
199 p.insert(
200 "filingType".into(),
201 GraphPropertyValue::String(self.filing_type.to_string()),
202 );
203 p.insert(
204 "companyCode".into(),
205 GraphPropertyValue::String(self.company_code.clone()),
206 );
207 p.insert(
208 "jurisdiction".into(),
209 GraphPropertyValue::String(self.jurisdiction.clone()),
210 );
211 p.insert(
212 "periodEnd".into(),
213 GraphPropertyValue::Date(self.period_end),
214 );
215 p.insert("deadline".into(), GraphPropertyValue::Date(self.deadline));
216 p.insert(
217 "status".into(),
218 GraphPropertyValue::String(format!("{:?}", self.status)),
219 );
220 p.insert(
221 "regulator".into(),
222 GraphPropertyValue::String(self.regulator.clone()),
223 );
224 if let Some(fd) = self.filing_date {
225 p.insert("filingDate".into(), GraphPropertyValue::Date(fd));
226 }
227 if let Some(ref fref) = self.filing_reference {
228 p.insert(
229 "filingReference".into(),
230 GraphPropertyValue::String(fref.clone()),
231 );
232 }
233 p
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 fn date(y: i32, m: u32, d: u32) -> NaiveDate {
242 NaiveDate::from_ymd_opt(y, m, d).expect("valid date")
243 }
244
245 #[test]
246 fn test_filing_creation() {
247 let filing = RegulatoryFiling::new(
248 FilingType::Form10K,
249 "C001",
250 "US",
251 date(2024, 12, 31),
252 date(2025, 3, 1),
253 "SEC",
254 );
255 assert_eq!(filing.company_code, "C001");
256 assert_eq!(filing.jurisdiction, "US");
257 assert_eq!(filing.status, FilingStatus::Pending);
258 assert!(filing.filing_date.is_none());
259 }
260
261 #[test]
262 fn test_filing_on_time() {
263 let filing = RegulatoryFiling::new(
264 FilingType::Form10K,
265 "C001",
266 "US",
267 date(2024, 12, 31),
268 date(2025, 3, 1),
269 "SEC",
270 )
271 .filed_on(date(2025, 2, 15));
272 assert_eq!(filing.status, FilingStatus::Filed);
273 assert_eq!(filing.filing_date, Some(date(2025, 2, 15)));
274 }
275
276 #[test]
277 fn test_filing_late() {
278 let filing = RegulatoryFiling::new(
279 FilingType::Form10K,
280 "C001",
281 "US",
282 date(2024, 12, 31),
283 date(2025, 3, 1),
284 "SEC",
285 )
286 .filed_on(date(2025, 3, 15));
287 assert_eq!(filing.status, FilingStatus::FiledLate);
288 }
289
290 #[test]
291 fn test_days_to_deadline() {
292 let filing = RegulatoryFiling::new(
293 FilingType::Form10Q,
294 "C001",
295 "US",
296 date(2024, 9, 30),
297 date(2024, 11, 9),
298 "SEC",
299 );
300 assert_eq!(filing.days_to_deadline(date(2024, 10, 9)), 31);
301 assert_eq!(filing.days_to_deadline(date(2024, 11, 9)), 0);
302 assert_eq!(filing.days_to_deadline(date(2024, 11, 19)), -10);
303 }
304
305 #[test]
306 fn test_filing_type_display() {
307 assert_eq!(format!("{}", FilingType::Form10K), "10-K");
308 assert_eq!(
309 format!("{}", FilingType::Jahresabschluss),
310 "Jahresabschluss"
311 );
312 assert_eq!(
313 format!("{}", FilingType::Custom("CbCR".to_string())),
314 "CbCR"
315 );
316 }
317}