whyyoulying 0.1.0

Proactive detection of Labor Category Fraud and Ghost Billing
Documentation
//! Fraud detection modules.

pub mod ghost;
pub mod labor;
pub mod rate_inflation;
pub mod overtime;
pub mod duplicate_billing;

#[cfg(test)]
mod tests {
    use super::labor::LaborDetector;
    use super::ghost::GhostDetector;
    use crate::data::Dataset;
    use crate::types::{Contract, Employee, LaborCharge, BillingRecord};

    fn contract(id: &str, agency: Option<&str>, cage: Option<&str>) -> Contract {
        let mut c = Contract::default();
        c.id = id.into();
        c.agency = agency.map(String::from);
        c.cage_code = cage.map(String::from);
        c
    }

    #[test]
    fn labor_detector_empty_ds_no_alerts() {
        let ds = Dataset::default();
        let det = LaborDetector::new(15.0);
        assert!(det.run(&ds).is_empty());
    }

    #[test]
    fn labor_detector_qual_below() {
        let mut ds = Dataset::default();
        let c = contract("C1", Some("DoD"), None);
        ds.contracts.insert(c.id.clone(), c);
        ds.employees.insert(
            "E1".into(),
            Employee {
            id: "E1".into(),
            quals: vec!["BA".into()],
            labor_cat_min: Some("Junior".into()),
            verified: false,
            ..Default::default()
        },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "Principal".into(),
            hours: 40.0,
            rate: Some(150.0),
            ..Default::default()
        });
        let det = LaborDetector::new(15.0);
        let alerts = det.run(&ds);
        assert!(!alerts.is_empty());
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("LaborQualBelow")));
    }

    #[test]
    fn labor_detector_variance_unapproved_cat() {
        let mut ds = Dataset::default();
        ds.contracts.insert(
            "C1".into(),
            Contract {
                id: "C1".into(),
                labor_cats: [("Senior".to_string(), "BA".to_string())].into_iter().collect(),
                ..Default::default()
            },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "UnapprovedCat".into(),
            hours: 10.0,
            rate: None,
            ..Default::default()
        });
        let det = LaborDetector::new(15.0);
        let alerts = det.run(&ds);
        assert!(!alerts.is_empty());
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("LaborVariance")));
    }

    #[test]
    fn ghost_detector_empty_ds_no_alerts() {
        let ds = Dataset::default();
        let det = GhostDetector::new();
        assert!(det.run(&ds).is_empty());
    }

    #[test]
    fn ghost_detector_no_employee() {
        let mut ds = Dataset::default();
        let c = contract("C1", None, None);
        ds.contracts.insert(c.id.clone(), c);
        ds.billing_records.push(BillingRecord {
            contract_id: "C1".into(),
            employee_id: "E99".into(),
            billed_hours: 10.0,
            billed_cat: "Junior".into(),
            period: None,
            ..Default::default()
        });
        let det = GhostDetector::new();
        let alerts = det.run(&ds);
        assert!(!alerts.is_empty());
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostNoEmployee")));
    }

    #[test]
    fn ghost_detector_billed_not_performed() {
        let mut ds = Dataset::default();
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                verified: true,
                ..Default::default()
            },
        );
        ds.billing_records.push(BillingRecord {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            billed_hours: 40.0,
            billed_cat: "Senior".into(),
            period: None,
            ..Default::default()
        });
        let det = GhostDetector::new();
        let alerts = det.run(&ds);
        assert!(!alerts.is_empty());
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostBilledNotPerformed")));
    }

    #[test]
    fn labor_detector_qual_ok_no_alert() {
        let mut ds = Dataset::default();
        ds.contracts.insert(
            "C1".into(),
            Contract {
                id: "C1".into(),
                labor_cats: [("Junior".to_string(), "Assoc".to_string())].into_iter().collect(),
                ..Default::default()
            },
        );
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                labor_cat_min: Some("Senior".into()),
                ..Default::default()
            },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "Junior".into(),
            hours: 40.0,
            rate: None,
            ..Default::default()
        });
        let det = LaborDetector::new(15.0);
        let alerts = det.run(&ds);
        assert!(alerts.is_empty());
    }

    #[test]
    fn labor_detector_both_unknown_category_no_qual_alert() {
        let mut ds = Dataset::default();
        ds.contracts.insert(
            "C1".into(),
            Contract {
                id: "C1".into(),
                labor_cats: [("CustomCat".to_string(), "X".to_string())].into_iter().collect(),
                ..Default::default()
            },
        );
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                labor_cat_min: Some("OtherCustom".into()),
                ..Default::default()
            },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "CustomCat".into(),
            hours: 40.0,
            rate: None,
            ..Default::default()
        });
        let det = LaborDetector::new(15.0);
        let alerts = det.run(&ds);
        assert!(!alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("LaborQualBelow")));
    }

    #[test]
    fn ghost_detector_not_verified() {
        let mut ds = Dataset::default();
        let c = contract("C1", None, None);
        ds.contracts.insert(c.id.clone(), c);
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                verified: false,
                ..Default::default()
            },
        );
        ds.billing_records.push(BillingRecord {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            billed_hours: 10.0,
            billed_cat: "Junior".into(),
            period: None,
            ..Default::default()
        });
        let det = GhostDetector::new();
        let alerts = det.run(&ds);
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostNotVerified")));
    }

    #[test]
    fn ghost_detector_partial_performed() {
        let mut ds = Dataset::default();
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                verified: true,
                ..Default::default()
            },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "Senior".into(),
            hours: 20.0,
            rate: None,
            ..Default::default()
        });
        ds.billing_records.push(BillingRecord {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            billed_hours: 40.0,
            billed_cat: "Senior".into(),
            period: None,
            ..Default::default()
        });
        let det = GhostDetector::new();
        let alerts = det.run(&ds);
        assert!(alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostBilledNotPerformed")));
    }

    #[test]
    fn ghost_detector_verified_no_alert() {
        let mut ds = Dataset::default();
        ds.employees.insert(
            "E1".into(),
            Employee {
                id: "E1".into(),
                verified: true,
                ..Default::default()
            },
        );
        ds.labor_charges.push(LaborCharge {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            labor_cat: "Senior".into(),
            hours: 40.0,
            rate: None,
            ..Default::default()
        });
        ds.billing_records.push(BillingRecord {
            contract_id: "C1".into(),
            employee_id: "E1".into(),
            billed_hours: 40.0,
            billed_cat: "Senior".into(),
            period: None,
            ..Default::default()
        });
        let det = GhostDetector::new();
        let alerts = det.run(&ds);
        assert!(!alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostNotVerified")));
        assert!(!alerts.iter().any(|a| format!("{:?}", a.rule_id).contains("GhostBilledNotPerformed")));
    }
}