vaultparser 0.3.8

A fast, pure-Rust library and visual tool for extracting tabular transaction ledgers from PDF bank statements
Documentation
use crate::config::ExtractionConfig;

/// Supported out-of-the-box bank statement templates.
///
/// Each preset contains standard column coordinates and label mappings
/// for common Indian banks.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BankPreset {
    /// HDFC Bank India statement template.
    Hdfc,
    /// State Bank of India (SBI) statement template.
    Sbi,
    /// Canara Bank statement template.
    Canara,
    /// Union Bank of India statement template.
    Union,
    /// UCO Bank statement template.
    Uco,
    /// Indian Bank statement template.
    Indian,
    /// H P State Co-operative Bank statement template.
    Hpscb,
    /// ICICI Bank statement template.
    Icici,
    /// Punjab National Bank (PNB) statement template.
    Pnb,
    /// Kotak Mahindra Bank statement template.
    Kotak,
    /// Axis Bank statement template.
    Axis,
    /// Bank of Baroda statement template.
    Bob,
    /// YES Bank statement template.
    Yes,
    /// IDFC FIRST Bank statement template.
    Idfc,
    /// IndusInd Bank statement template.
    Indusind,
    /// Himachal Pradesh Gramin Bank statement template.
    Hpgb,
}

impl std::fmt::Display for BankPreset {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl BankPreset {
    /// Returns the human-readable display name of the bank preset.
    #[must_use]
    pub fn name(&self) -> &'static str {
        match self {
            Self::Hdfc => "HDFC Bank India",
            Self::Sbi => "State Bank of India (SBI)",
            Self::Canara => "Canara Bank",
            Self::Union => "Union Bank of India",
            Self::Uco => "UCO Bank",
            Self::Indian => "Indian Bank",
            Self::Hpscb => "H P State Co-operative Bank",
            Self::Icici => "ICICI Bank",
            Self::Pnb => "Punjab National Bank (PNB)",
            Self::Kotak => "Kotak Mahindra Bank",
            Self::Axis => "Axis Bank",
            Self::Bob => "Bank of Baroda",
            Self::Yes => "YES Bank",
            Self::Idfc => "IDFC FIRST Bank",
            Self::Indusind => "IndusInd Bank",
            Self::Hpgb => "Himachal Pradesh Gramin Bank",
        }
    }

    /// Returns the pre-configured [`ExtractionConfig`] for the specific bank.
    ///
    /// # Examples
    /// ```
    /// use vaultparser::BankPreset;
    ///
    /// let config = BankPreset::Hdfc.config();
    /// assert!(config.filter_only_date);
    /// assert_eq!(config.col_mappings.len(), config.col_guides.len() + 1);
    /// ```
    #[must_use]
    pub fn config(&self) -> ExtractionConfig {
        let mut config = ExtractionConfig::default();
        match self {
            Self::Hdfc => {
                config.col_guides = vec![0.11, 0.42, 0.52, 0.62, 0.75, 0.88];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "skip".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
            }
            Self::Sbi => {
                config.col_guides = vec![0.10, 0.20, 0.52, 0.64, 0.76, 0.88];
                config.col_mappings = vec![
                    "date".to_string(),
                    "skip".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 15.0;
            }
            Self::Canara => {
                config.col_guides = vec![0.12, 0.48, 0.60, 0.74, 0.87];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
            }
            Self::Union => {
                config.col_guides = vec![0.11, 0.42, 0.52, 0.62, 0.75, 0.88];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "skip".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
            }
            Self::Uco => {
                config.col_guides = vec![0.12, 0.24, 0.60, 0.74, 0.87];
                config.col_mappings = vec![
                    "date".to_string(),
                    "reference".to_string(),
                    "description".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
            }
            Self::Indian => {
                config.col_guides = vec![0.08, 0.18, 0.45, 0.58, 0.71, 0.80];
                config.col_mappings = vec![
                    "date".to_string(),
                    "value_date".to_string(),
                    "description".to_string(),
                    "chq_no".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 15.0;
            }
            Self::Hpscb => {
                config.col_guides = vec![0.08, 0.16, 0.38, 0.41, 0.65, 0.74, 0.82];
                config.col_mappings = vec![
                    "s_no".to_string(),
                    "date".to_string(),
                    "value_date".to_string(),
                    "chq_no".to_string(),
                    "description".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 15.0;
            }
            Self::Icici => {
                config.col_guides = vec![0.08, 0.44, 0.55, 0.62, 0.88];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "chq_no".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Pnb => {
                config.col_guides = vec![0.14, 0.23, 0.33, 0.40, 0.47, 0.53];
                config.col_mappings = vec![
                    "date".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                    "skip".to_string(),
                    "chq_no".to_string(),
                    "description".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Kotak => {
                config.col_guides = vec![0.15, 0.44, 0.65, 0.79];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "debit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Axis => {
                config.col_guides = vec![0.12, 0.42, 0.55, 0.68, 0.82];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "chq_no".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Bob => {
                config.col_guides = vec![0.10, 0.20, 0.50, 0.65, 0.78, 0.88];
                config.col_mappings = vec![
                    "date".to_string(),
                    "value_date".to_string(),
                    "description".to_string(),
                    "chq_no".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 15.0;
            }
            Self::Yes => {
                config.col_guides = vec![0.12, 0.45, 0.58, 0.72, 0.85];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Idfc => {
                config.col_guides = vec![0.11, 0.40, 0.54, 0.68, 0.82];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "reference".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Indusind => {
                config.col_guides = vec![0.12, 0.44, 0.56, 0.70, 0.84];
                config.col_mappings = vec![
                    "date".to_string(),
                    "description".to_string(),
                    "chq_no".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 12.0;
            }
            Self::Hpgb => {
                config.col_guides = vec![0.06, 0.21, 0.45, 0.58, 0.74];
                config.col_mappings = vec![
                    "date".to_string(),
                    "reference".to_string(),
                    "description".to_string(),
                    "debit".to_string(),
                    "credit".to_string(),
                    "balance".to_string(),
                ];
                config.filter_only_date = true;
                config.y_tolerance = 10.0;
            }
        }
        config
    }

    /// Returns the lowercase machine key for this bank preset.
    #[must_use]
    pub fn key(&self) -> &'static str {
        match self {
            Self::Hdfc => "hdfc",
            Self::Sbi => "sbi",
            Self::Canara => "canara",
            Self::Union => "union",
            Self::Uco => "uco",
            Self::Indian => "indian",
            Self::Hpscb => "hpscb",
            Self::Icici => "icici",
            Self::Pnb => "pnb",
            Self::Kotak => "kotak",
            Self::Axis => "axis",
            Self::Bob => "bob",
            Self::Yes => "yes",
            Self::Idfc => "idfc",
            Self::Indusind => "indusind",
            Self::Hpgb => "hpgb",
        }
    }

    /// Returns a list of all supported bank presets.
    #[must_use]
    pub fn all() -> &'static [BankPreset] {
        &[
            Self::Hdfc,
            Self::Sbi,
            Self::Canara,
            Self::Union,
            Self::Uco,
            Self::Indian,
            Self::Hpscb,
            Self::Icici,
            Self::Pnb,
            Self::Kotak,
            Self::Axis,
            Self::Bob,
            Self::Yes,
            Self::Idfc,
            Self::Indusind,
            Self::Hpgb,
        ]
    }

    /// Attempts to parse a case-insensitive string into a [`BankPreset`].
    /// Returns `None` if the name is unrecognized.
    ///
    /// # Examples
    /// ```
    /// use vaultparser::BankPreset;
    ///
    /// assert_eq!(BankPreset::from_str("hdfc"), Some(BankPreset::Hdfc));
    /// assert_eq!(BankPreset::from_str("HDFC"), Some(BankPreset::Hdfc));
    /// assert_eq!(BankPreset::from_str("nope"), None);
    /// ```
    #[allow(clippy::should_implement_trait)]
    #[must_use]
    pub fn from_str(s: &str) -> Option<Self> {
        s.parse::<Self>().ok()
    }
}

impl std::str::FromStr for BankPreset {
    type Err = crate::error::ExtractorError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "hdfc" => Ok(Self::Hdfc),
            "sbi" => Ok(Self::Sbi),
            "canara" => Ok(Self::Canara),
            "union" => Ok(Self::Union),
            "uco" => Ok(Self::Uco),
            "indian" => Ok(Self::Indian),
            "hpscb" => Ok(Self::Hpscb),
            "icici" => Ok(Self::Icici),
            "pnb" => Ok(Self::Pnb),
            "kotak" => Ok(Self::Kotak),
            "axis" => Ok(Self::Axis),
            "bob" | "baroda" => Ok(Self::Bob),
            "yes" => Ok(Self::Yes),
            "idfc" => Ok(Self::Idfc),
            "indusind" => Ok(Self::Indusind),
            "hpgb" | "himachal pradesh gramin bank" | "himachal pradesh gramin" | "hpgramin" => {
                Ok(Self::Hpgb)
            }
            _ => Err(crate::error::ExtractorError::InvalidConfig(format!(
                "Unknown bank preset: '{}'",
                s
            ))),
        }
    }
}