use std::collections::HashMap;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use crate::models::graph_properties::{GraphPropertyValue, ToNodeProperties};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilingType {
Form10K,
Form10Q,
Form8K,
Form20F,
Jahresabschluss,
EBilanz,
LiasseFiscale,
UkAnnualReturn,
Ct600,
YukaShokenHokokusho,
AnnualStatements,
QuarterlyReport,
TaxReturn,
Custom(String),
}
impl std::fmt::Display for FilingType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Form10K => write!(f, "10-K"),
Self::Form10Q => write!(f, "10-Q"),
Self::Form8K => write!(f, "8-K"),
Self::Form20F => write!(f, "20-F"),
Self::Jahresabschluss => write!(f, "Jahresabschluss"),
Self::EBilanz => write!(f, "E-Bilanz"),
Self::LiasseFiscale => write!(f, "Liasse fiscale"),
Self::UkAnnualReturn => write!(f, "Annual Return"),
Self::Ct600 => write!(f, "CT600"),
Self::YukaShokenHokokusho => write!(f, "有価証券報告書"),
Self::AnnualStatements => write!(f, "Annual Financial Statements"),
Self::QuarterlyReport => write!(f, "Quarterly Report"),
Self::TaxReturn => write!(f, "Tax Return"),
Self::Custom(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilingFrequency {
Annual,
SemiAnnual,
Quarterly,
Monthly,
EventDriven,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilingStatus {
NotDue,
Pending,
Filed,
FiledLate,
Overdue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilingRequirement {
pub filing_type: FilingType,
pub frequency: FilingFrequency,
pub regulator: String,
pub jurisdiction: String,
pub deadline_days: u32,
pub electronic_filing: bool,
pub xbrl_required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegulatoryFiling {
pub filing_type: FilingType,
pub company_code: String,
pub jurisdiction: String,
pub period_end: NaiveDate,
pub deadline: NaiveDate,
pub filing_date: Option<NaiveDate>,
pub status: FilingStatus,
pub regulator: String,
pub filing_reference: Option<String>,
}
impl RegulatoryFiling {
pub fn new(
filing_type: FilingType,
company_code: impl Into<String>,
jurisdiction: impl Into<String>,
period_end: NaiveDate,
deadline: NaiveDate,
regulator: impl Into<String>,
) -> Self {
Self {
filing_type,
company_code: company_code.into(),
jurisdiction: jurisdiction.into(),
period_end,
deadline,
filing_date: None,
status: FilingStatus::Pending,
regulator: regulator.into(),
filing_reference: None,
}
}
pub fn filed_on(mut self, date: NaiveDate) -> Self {
self.filing_date = Some(date);
self.status = if date <= self.deadline {
FilingStatus::Filed
} else {
FilingStatus::FiledLate
};
self
}
pub fn days_to_deadline(&self, from: NaiveDate) -> i64 {
(self.deadline - from).num_days()
}
}
impl ToNodeProperties for RegulatoryFiling {
fn node_type_name(&self) -> &'static str {
"regulatory_filing"
}
fn node_type_code(&self) -> u16 {
512
}
fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
let mut p = HashMap::new();
p.insert(
"filingType".into(),
GraphPropertyValue::String(self.filing_type.to_string()),
);
p.insert(
"companyCode".into(),
GraphPropertyValue::String(self.company_code.clone()),
);
p.insert(
"jurisdiction".into(),
GraphPropertyValue::String(self.jurisdiction.clone()),
);
p.insert(
"periodEnd".into(),
GraphPropertyValue::Date(self.period_end),
);
p.insert("deadline".into(), GraphPropertyValue::Date(self.deadline));
p.insert(
"status".into(),
GraphPropertyValue::String(format!("{:?}", self.status)),
);
p.insert(
"regulator".into(),
GraphPropertyValue::String(self.regulator.clone()),
);
if let Some(fd) = self.filing_date {
p.insert("filingDate".into(), GraphPropertyValue::Date(fd));
}
if let Some(ref fref) = self.filing_reference {
p.insert(
"filingReference".into(),
GraphPropertyValue::String(fref.clone()),
);
}
p
}
}
#[cfg(test)]
mod tests {
use super::*;
fn date(y: i32, m: u32, d: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(y, m, d).expect("valid date")
}
#[test]
fn test_filing_creation() {
let filing = RegulatoryFiling::new(
FilingType::Form10K,
"C001",
"US",
date(2024, 12, 31),
date(2025, 3, 1),
"SEC",
);
assert_eq!(filing.company_code, "C001");
assert_eq!(filing.jurisdiction, "US");
assert_eq!(filing.status, FilingStatus::Pending);
assert!(filing.filing_date.is_none());
}
#[test]
fn test_filing_on_time() {
let filing = RegulatoryFiling::new(
FilingType::Form10K,
"C001",
"US",
date(2024, 12, 31),
date(2025, 3, 1),
"SEC",
)
.filed_on(date(2025, 2, 15));
assert_eq!(filing.status, FilingStatus::Filed);
assert_eq!(filing.filing_date, Some(date(2025, 2, 15)));
}
#[test]
fn test_filing_late() {
let filing = RegulatoryFiling::new(
FilingType::Form10K,
"C001",
"US",
date(2024, 12, 31),
date(2025, 3, 1),
"SEC",
)
.filed_on(date(2025, 3, 15));
assert_eq!(filing.status, FilingStatus::FiledLate);
}
#[test]
fn test_days_to_deadline() {
let filing = RegulatoryFiling::new(
FilingType::Form10Q,
"C001",
"US",
date(2024, 9, 30),
date(2024, 11, 9),
"SEC",
);
assert_eq!(filing.days_to_deadline(date(2024, 10, 9)), 31);
assert_eq!(filing.days_to_deadline(date(2024, 11, 9)), 0);
assert_eq!(filing.days_to_deadline(date(2024, 11, 19)), -10);
}
#[test]
fn test_filing_type_display() {
assert_eq!(format!("{}", FilingType::Form10K), "10-K");
assert_eq!(
format!("{}", FilingType::Jahresabschluss),
"Jahresabschluss"
);
assert_eq!(
format!("{}", FilingType::Custom("CbCR".to_string())),
"CbCR"
);
}
}