Skip to main content

datasynth_generators/master_data/
vendor_generator.rs

1//! Enhanced vendor generator with realistic payment behavior and bank accounts.
2//!
3//! Now integrates with the realism module for sophisticated vendor naming
4//! based on spend categories, industry patterns, and well-known brands.
5//!
6//! Also supports multi-tier vendor network generation with:
7//! - Supply chain tiers (Tier 1, 2, 3)
8//! - Vendor clustering (Reliable, Standard, Transactional, Problematic)
9//! - Strategic importance and spend tier classification
10//! - Concentration analysis and dependency tracking
11
12use chrono::NaiveDate;
13use datasynth_core::models::{
14    BankAccount, DeclineReason, PaymentHistory, PaymentTerms, SpendTier, StrategicLevel,
15    Substitutability, SupplyChainTier, Vendor, VendorBehavior, VendorCluster, VendorDependency,
16    VendorLifecycleStage, VendorNetwork, VendorPool, VendorQualityScore, VendorRelationship,
17    VendorRelationshipType,
18};
19use datasynth_core::templates::{
20    AddressGenerator, AddressRegion, SpendCategory, VendorNameGenerator,
21};
22use rand::prelude::*;
23use rand_chacha::ChaCha8Rng;
24use rust_decimal::Decimal;
25
26/// Configuration for vendor generation.
27#[derive(Debug, Clone)]
28pub struct VendorGeneratorConfig {
29    /// Distribution of payment terms (terms, probability)
30    pub payment_terms_distribution: Vec<(PaymentTerms, f64)>,
31    /// Distribution of vendor behaviors (behavior, probability)
32    pub behavior_distribution: Vec<(VendorBehavior, f64)>,
33    /// Probability of vendor being intercompany
34    pub intercompany_rate: f64,
35    /// Default country for vendors
36    pub default_country: String,
37    /// Default currency
38    pub default_currency: String,
39    /// Generate bank accounts
40    pub generate_bank_accounts: bool,
41    /// Probability of vendor having multiple bank accounts
42    pub multiple_bank_account_rate: f64,
43    /// Distribution of spend categories (category, probability)
44    pub spend_category_distribution: Vec<(SpendCategory, f64)>,
45    /// Primary region for address generation
46    pub primary_region: AddressRegion,
47    /// Use enhanced realistic naming (via realism module)
48    pub use_enhanced_naming: bool,
49}
50
51impl Default for VendorGeneratorConfig {
52    fn default() -> Self {
53        Self {
54            payment_terms_distribution: vec![
55                (PaymentTerms::Net30, 0.40),
56                (PaymentTerms::Net60, 0.20),
57                (PaymentTerms::TwoTenNet30, 0.25),
58                (PaymentTerms::Net15, 0.10),
59                (PaymentTerms::Immediate, 0.05),
60            ],
61            behavior_distribution: vec![
62                (VendorBehavior::Flexible, 0.60),
63                (VendorBehavior::Strict, 0.25),
64                (VendorBehavior::VeryFlexible, 0.10),
65                (VendorBehavior::Aggressive, 0.05),
66            ],
67            intercompany_rate: 0.05,
68            default_country: "US".to_string(),
69            default_currency: "USD".to_string(),
70            generate_bank_accounts: true,
71            multiple_bank_account_rate: 0.20,
72            spend_category_distribution: vec![
73                (SpendCategory::OfficeSupplies, 0.15),
74                (SpendCategory::ITServices, 0.12),
75                (SpendCategory::ProfessionalServices, 0.12),
76                (SpendCategory::Telecommunications, 0.08),
77                (SpendCategory::Utilities, 0.08),
78                (SpendCategory::RawMaterials, 0.10),
79                (SpendCategory::Logistics, 0.10),
80                (SpendCategory::Marketing, 0.08),
81                (SpendCategory::Facilities, 0.07),
82                (SpendCategory::Staffing, 0.05),
83                (SpendCategory::Travel, 0.05),
84            ],
85            primary_region: AddressRegion::NorthAmerica,
86            use_enhanced_naming: true,
87        }
88    }
89}
90
91/// Configuration for vendor network generation.
92#[derive(Debug, Clone)]
93pub struct VendorNetworkConfig {
94    /// Enable vendor network generation
95    pub enabled: bool,
96    /// Maximum depth of supply chain tiers (1-3)
97    pub depth: u8,
98    /// Number of Tier 1 vendors to generate
99    pub tier1_count: TierCountConfig,
100    /// Number of Tier 2 vendors per Tier 1 parent
101    pub tier2_per_parent: TierCountConfig,
102    /// Number of Tier 3 vendors per Tier 2 parent
103    pub tier3_per_parent: TierCountConfig,
104    /// Cluster distribution
105    pub cluster_distribution: ClusterDistribution,
106    /// Concentration limits
107    pub concentration_limits: ConcentrationLimits,
108    /// Strategic level distribution
109    pub strategic_distribution: Vec<(StrategicLevel, f64)>,
110    /// Single-source percentage
111    pub single_source_percent: f64,
112}
113
114/// Count range for tier generation.
115#[derive(Debug, Clone)]
116pub struct TierCountConfig {
117    /// Minimum count
118    pub min: usize,
119    /// Maximum count
120    pub max: usize,
121}
122
123impl TierCountConfig {
124    /// Create a new tier count config.
125    pub fn new(min: usize, max: usize) -> Self {
126        Self { min, max }
127    }
128
129    /// Sample a count from the range.
130    pub fn sample(&self, rng: &mut impl Rng) -> usize {
131        rng.gen_range(self.min..=self.max)
132    }
133}
134
135/// Distribution of vendor clusters.
136#[derive(Debug, Clone)]
137pub struct ClusterDistribution {
138    /// Reliable strategic vendors (default: 20%)
139    pub reliable_strategic: f64,
140    /// Standard operational vendors (default: 50%)
141    pub standard_operational: f64,
142    /// Transactional vendors (default: 25%)
143    pub transactional: f64,
144    /// Problematic vendors (default: 5%)
145    pub problematic: f64,
146}
147
148impl Default for ClusterDistribution {
149    fn default() -> Self {
150        Self {
151            reliable_strategic: 0.20,
152            standard_operational: 0.50,
153            transactional: 0.25,
154            problematic: 0.05,
155        }
156    }
157}
158
159impl ClusterDistribution {
160    /// Validate that distribution sums to 1.0.
161    pub fn validate(&self) -> Result<(), String> {
162        let sum = self.reliable_strategic
163            + self.standard_operational
164            + self.transactional
165            + self.problematic;
166        if (sum - 1.0).abs() > 0.01 {
167            Err(format!("Cluster distribution must sum to 1.0, got {}", sum))
168        } else {
169            Ok(())
170        }
171    }
172
173    /// Select a cluster based on the distribution.
174    pub fn select(&self, roll: f64) -> VendorCluster {
175        let mut cumulative = 0.0;
176
177        cumulative += self.reliable_strategic;
178        if roll < cumulative {
179            return VendorCluster::ReliableStrategic;
180        }
181
182        cumulative += self.standard_operational;
183        if roll < cumulative {
184            return VendorCluster::StandardOperational;
185        }
186
187        cumulative += self.transactional;
188        if roll < cumulative {
189            return VendorCluster::Transactional;
190        }
191
192        VendorCluster::Problematic
193    }
194}
195
196/// Concentration limits for vendor spend.
197#[derive(Debug, Clone)]
198pub struct ConcentrationLimits {
199    /// Maximum concentration for a single vendor (default: 15%)
200    pub max_single_vendor: f64,
201    /// Maximum concentration for top 5 vendors (default: 45%)
202    pub max_top5: f64,
203}
204
205impl Default for ConcentrationLimits {
206    fn default() -> Self {
207        Self {
208            max_single_vendor: 0.15,
209            max_top5: 0.45,
210        }
211    }
212}
213
214impl Default for VendorNetworkConfig {
215    fn default() -> Self {
216        Self {
217            enabled: false,
218            depth: 3,
219            tier1_count: TierCountConfig::new(50, 100),
220            tier2_per_parent: TierCountConfig::new(4, 10),
221            tier3_per_parent: TierCountConfig::new(2, 5),
222            cluster_distribution: ClusterDistribution::default(),
223            concentration_limits: ConcentrationLimits::default(),
224            strategic_distribution: vec![
225                (StrategicLevel::Critical, 0.05),
226                (StrategicLevel::Important, 0.15),
227                (StrategicLevel::Standard, 0.50),
228                (StrategicLevel::Transactional, 0.30),
229            ],
230            single_source_percent: 0.05,
231        }
232    }
233}
234
235/// Legacy vendor name templates by category (kept for backward compatibility).
236/// New code should use VendorNameGenerator from the realism module.
237#[allow(dead_code)]
238const VENDOR_NAME_TEMPLATES_LEGACY: &[(&str, &[&str])] = &[
239    (
240        "Manufacturing",
241        &[
242            "Global Manufacturing Solutions",
243            "Precision Parts Inc.",
244            "Industrial Components Ltd.",
245            "Advanced Materials Corp.",
246        ],
247    ),
248    (
249        "Services",
250        &[
251            "Professional Services Group",
252            "Consulting Partners LLC",
253            "Business Solutions Inc.",
254            "Technical Services Corp.",
255        ],
256    ),
257    (
258        "Technology",
259        &[
260            "Tech Solutions Inc.",
261            "Digital Systems Corp.",
262            "Software Innovations LLC",
263            "Cloud Services Partners",
264        ],
265    ),
266];
267
268/// Bank name templates.
269const BANK_NAMES: &[&str] = &[
270    "First National Bank",
271    "Commerce Bank",
272    "United Banking Corp",
273    "Regional Trust Bank",
274    "Merchants Bank",
275    "Citizens Financial",
276    "Pacific Coast Bank",
277    "Atlantic Commerce Bank",
278    "Midwest Trust Company",
279    "Capital One Commercial",
280];
281
282/// Generator for vendor master data.
283pub struct VendorGenerator {
284    rng: ChaCha8Rng,
285    seed: u64,
286    config: VendorGeneratorConfig,
287    vendor_counter: usize,
288    /// Enhanced vendor name generator from realism module
289    vendor_name_gen: VendorNameGenerator,
290    /// Address generator for vendor addresses
291    address_gen: AddressGenerator,
292    /// Network configuration
293    network_config: VendorNetworkConfig,
294}
295
296impl VendorGenerator {
297    /// Create a new vendor generator.
298    pub fn new(seed: u64) -> Self {
299        Self::with_config(seed, VendorGeneratorConfig::default())
300    }
301
302    /// Create a new vendor generator with custom configuration.
303    pub fn with_config(seed: u64, config: VendorGeneratorConfig) -> Self {
304        Self {
305            rng: ChaCha8Rng::seed_from_u64(seed),
306            seed,
307            vendor_name_gen: VendorNameGenerator::new(),
308            address_gen: AddressGenerator::for_region(config.primary_region),
309            config,
310            vendor_counter: 0,
311            network_config: VendorNetworkConfig::default(),
312        }
313    }
314
315    /// Create a new vendor generator with network configuration.
316    pub fn with_network_config(
317        seed: u64,
318        config: VendorGeneratorConfig,
319        network_config: VendorNetworkConfig,
320    ) -> Self {
321        Self {
322            rng: ChaCha8Rng::seed_from_u64(seed),
323            seed,
324            vendor_name_gen: VendorNameGenerator::new(),
325            address_gen: AddressGenerator::for_region(config.primary_region),
326            config,
327            vendor_counter: 0,
328            network_config,
329        }
330    }
331
332    /// Set network configuration.
333    pub fn set_network_config(&mut self, network_config: VendorNetworkConfig) {
334        self.network_config = network_config;
335    }
336
337    /// Generate a single vendor.
338    pub fn generate_vendor(&mut self, company_code: &str, _effective_date: NaiveDate) -> Vendor {
339        self.vendor_counter += 1;
340
341        let vendor_id = format!("V-{:06}", self.vendor_counter);
342        let (category, name) = self.select_vendor_name();
343        let tax_id = self.generate_tax_id();
344        let _address = self.address_gen.generate_commercial(&mut self.rng);
345
346        // Store the spend category for potential future use
347        let _spend_category = category;
348
349        let mut vendor = Vendor::new(
350            &vendor_id,
351            &name,
352            datasynth_core::models::VendorType::Supplier,
353        );
354        vendor.tax_id = Some(tax_id);
355        vendor.country = self.config.default_country.clone();
356        vendor.currency = self.config.default_currency.clone();
357        // Note: category, effective_date, address are not fields on Vendor
358
359        // Set payment terms
360        vendor.payment_terms = self.select_payment_terms();
361
362        // Set behavior
363        vendor.behavior = self.select_vendor_behavior();
364
365        // Check if intercompany
366        if self.rng.gen::<f64>() < self.config.intercompany_rate {
367            vendor.is_intercompany = true;
368            vendor.intercompany_code = Some(format!("IC-{}", company_code));
369        }
370
371        // Generate bank accounts
372        if self.config.generate_bank_accounts {
373            let bank_account = self.generate_bank_account(&vendor.vendor_id);
374            vendor.bank_accounts.push(bank_account);
375
376            if self.rng.gen::<f64>() < self.config.multiple_bank_account_rate {
377                let bank_account2 = self.generate_bank_account(&vendor.vendor_id);
378                vendor.bank_accounts.push(bank_account2);
379            }
380        }
381
382        vendor
383    }
384
385    /// Generate an intercompany vendor (always intercompany).
386    pub fn generate_intercompany_vendor(
387        &mut self,
388        company_code: &str,
389        partner_company_code: &str,
390        effective_date: NaiveDate,
391    ) -> Vendor {
392        let mut vendor = self.generate_vendor(company_code, effective_date);
393        vendor.is_intercompany = true;
394        vendor.intercompany_code = Some(partner_company_code.to_string());
395        vendor.name = format!("{} - IC", partner_company_code);
396        vendor.payment_terms = PaymentTerms::Immediate; // IC usually immediate
397        vendor
398    }
399
400    /// Generate a vendor pool with specified count.
401    pub fn generate_vendor_pool(
402        &mut self,
403        count: usize,
404        company_code: &str,
405        effective_date: NaiveDate,
406    ) -> VendorPool {
407        let mut pool = VendorPool::new();
408
409        for _ in 0..count {
410            let vendor = self.generate_vendor(company_code, effective_date);
411            pool.add_vendor(vendor);
412        }
413
414        pool
415    }
416
417    /// Generate a vendor pool with intercompany vendors.
418    pub fn generate_vendor_pool_with_ic(
419        &mut self,
420        count: usize,
421        company_code: &str,
422        partner_company_codes: &[String],
423        effective_date: NaiveDate,
424    ) -> VendorPool {
425        let mut pool = VendorPool::new();
426
427        // Generate regular vendors
428        let regular_count = count.saturating_sub(partner_company_codes.len());
429        for _ in 0..regular_count {
430            let vendor = self.generate_vendor(company_code, effective_date);
431            pool.add_vendor(vendor);
432        }
433
434        // Generate IC vendors for each partner
435        for partner in partner_company_codes {
436            let vendor = self.generate_intercompany_vendor(company_code, partner, effective_date);
437            pool.add_vendor(vendor);
438        }
439
440        pool
441    }
442
443    /// Select a spend category based on distribution.
444    fn select_spend_category(&mut self) -> SpendCategory {
445        let roll: f64 = self.rng.gen();
446        let mut cumulative = 0.0;
447
448        for (category, prob) in &self.config.spend_category_distribution {
449            cumulative += prob;
450            if roll < cumulative {
451                return *category;
452            }
453        }
454
455        SpendCategory::OfficeSupplies
456    }
457
458    /// Select a vendor name using the enhanced realism module or legacy templates.
459    fn select_vendor_name(&mut self) -> (SpendCategory, String) {
460        let category = self.select_spend_category();
461
462        if self.config.use_enhanced_naming {
463            // Use the enhanced VendorNameGenerator from the realism module
464            let name = self.vendor_name_gen.generate(category, &mut self.rng);
465            (category, name)
466        } else {
467            // Fallback to simple category-based names
468            let name = format!("{:?} Vendor {}", category, self.vendor_counter);
469            (category, name)
470        }
471    }
472
473    /// Select payment terms based on distribution.
474    fn select_payment_terms(&mut self) -> PaymentTerms {
475        let roll: f64 = self.rng.gen();
476        let mut cumulative = 0.0;
477
478        for (terms, prob) in &self.config.payment_terms_distribution {
479            cumulative += prob;
480            if roll < cumulative {
481                return *terms;
482            }
483        }
484
485        PaymentTerms::Net30
486    }
487
488    /// Select vendor behavior based on distribution.
489    fn select_vendor_behavior(&mut self) -> VendorBehavior {
490        let roll: f64 = self.rng.gen();
491        let mut cumulative = 0.0;
492
493        for (behavior, prob) in &self.config.behavior_distribution {
494            cumulative += prob;
495            if roll < cumulative {
496                return *behavior;
497            }
498        }
499
500        VendorBehavior::Flexible
501    }
502
503    /// Generate a tax ID.
504    fn generate_tax_id(&mut self) -> String {
505        format!(
506            "{:02}-{:07}",
507            self.rng.gen_range(10..99),
508            self.rng.gen_range(1000000..9999999)
509        )
510    }
511
512    /// Generate a bank account.
513    fn generate_bank_account(&mut self, vendor_id: &str) -> BankAccount {
514        let bank_idx = self.rng.gen_range(0..BANK_NAMES.len());
515        let bank_name = BANK_NAMES[bank_idx];
516
517        let routing = format!("{:09}", self.rng.gen_range(100000000u64..999999999));
518        let account = format!("{:010}", self.rng.gen_range(1000000000u64..9999999999));
519
520        BankAccount {
521            bank_name: bank_name.to_string(),
522            bank_country: "US".to_string(),
523            account_number: account,
524            routing_code: routing,
525            holder_name: format!("Vendor {}", vendor_id),
526            is_primary: self.vendor_counter == 1,
527        }
528    }
529
530    /// Generate an address using the enhanced address generator.
531    #[allow(dead_code)]
532    fn generate_address(&mut self) -> String {
533        use datasynth_core::templates::AddressStyle;
534        let address = self.address_gen.generate_commercial(&mut self.rng);
535        address.format(AddressStyle::SingleLine)
536    }
537
538    /// Reset the generator.
539    pub fn reset(&mut self) {
540        self.rng = ChaCha8Rng::seed_from_u64(self.seed);
541        self.vendor_counter = 0;
542        self.vendor_name_gen = VendorNameGenerator::new();
543        self.address_gen = AddressGenerator::for_region(self.config.primary_region);
544    }
545
546    // ===== Vendor Network Generation =====
547
548    /// Generate a complete vendor network with tiered supply chain.
549    pub fn generate_vendor_network(
550        &mut self,
551        company_code: &str,
552        effective_date: NaiveDate,
553        total_annual_spend: Decimal,
554    ) -> VendorNetwork {
555        let mut network = VendorNetwork::new(company_code);
556        network.created_date = Some(effective_date);
557
558        if !self.network_config.enabled {
559            return network;
560        }
561
562        // Generate Tier 1 vendors
563        let tier1_count = self.network_config.tier1_count.sample(&mut self.rng);
564        let tier1_ids = self.generate_tier_vendors(
565            company_code,
566            effective_date,
567            tier1_count,
568            SupplyChainTier::Tier1,
569            None,
570            &mut network,
571        );
572
573        // Generate Tier 2 vendors (if depth >= 2)
574        if self.network_config.depth >= 2 {
575            for tier1_id in &tier1_ids {
576                let tier2_count = self.network_config.tier2_per_parent.sample(&mut self.rng);
577                let tier2_ids = self.generate_tier_vendors(
578                    company_code,
579                    effective_date,
580                    tier2_count,
581                    SupplyChainTier::Tier2,
582                    Some(tier1_id.clone()),
583                    &mut network,
584                );
585
586                // Update Tier 1 vendor's children
587                if let Some(rel) = network.get_relationship_mut(tier1_id) {
588                    rel.child_vendors = tier2_ids.clone();
589                }
590
591                // Generate Tier 3 vendors (if depth >= 3)
592                if self.network_config.depth >= 3 {
593                    for tier2_id in &tier2_ids {
594                        let tier3_count =
595                            self.network_config.tier3_per_parent.sample(&mut self.rng);
596                        let tier3_ids = self.generate_tier_vendors(
597                            company_code,
598                            effective_date,
599                            tier3_count,
600                            SupplyChainTier::Tier3,
601                            Some(tier2_id.clone()),
602                            &mut network,
603                        );
604
605                        // Update Tier 2 vendor's children
606                        if let Some(rel) = network.get_relationship_mut(tier2_id) {
607                            rel.child_vendors = tier3_ids;
608                        }
609                    }
610                }
611            }
612        }
613
614        // Assign annual spend to vendors
615        self.assign_annual_spend(&mut network, total_annual_spend);
616
617        // Calculate network statistics
618        network.calculate_statistics(effective_date);
619
620        network
621    }
622
623    /// Generate vendors for a specific tier.
624    fn generate_tier_vendors(
625        &mut self,
626        company_code: &str,
627        effective_date: NaiveDate,
628        count: usize,
629        tier: SupplyChainTier,
630        parent_id: Option<String>,
631        network: &mut VendorNetwork,
632    ) -> Vec<String> {
633        let mut vendor_ids = Vec::with_capacity(count);
634
635        for _ in 0..count {
636            // Generate base vendor
637            let vendor = self.generate_vendor(company_code, effective_date);
638            let vendor_id = vendor.vendor_id.clone();
639
640            // Create relationship
641            let mut relationship = VendorRelationship::new(
642                vendor_id.clone(),
643                self.select_relationship_type(),
644                tier,
645                self.generate_relationship_start_date(effective_date),
646            );
647
648            // Set parent if applicable
649            if let Some(ref parent) = parent_id {
650                relationship = relationship.with_parent(parent.clone());
651            }
652
653            // Assign cluster, strategic level, and spend tier
654            relationship = relationship
655                .with_cluster(self.select_cluster())
656                .with_strategic_importance(self.select_strategic_level())
657                .with_spend_tier(self.select_spend_tier());
658
659            // Set lifecycle stage
660            relationship.lifecycle_stage = self.generate_lifecycle_stage(effective_date);
661
662            // Initialize quality score based on cluster
663            relationship.quality_score = self.generate_quality_score(&relationship.cluster);
664
665            // Initialize payment history based on cluster
666            relationship.payment_history = self.generate_payment_history(&relationship.cluster);
667
668            // Generate dependency analysis for Tier 1 vendors
669            if tier == SupplyChainTier::Tier1 {
670                relationship.dependency = Some(self.generate_dependency(&vendor_id, &vendor.name));
671            }
672
673            network.add_relationship(relationship);
674            vendor_ids.push(vendor_id);
675        }
676
677        vendor_ids
678    }
679
680    /// Select a relationship type.
681    fn select_relationship_type(&mut self) -> VendorRelationshipType {
682        let roll: f64 = self.rng.gen();
683        if roll < 0.40 {
684            VendorRelationshipType::DirectSupplier
685        } else if roll < 0.55 {
686            VendorRelationshipType::ServiceProvider
687        } else if roll < 0.70 {
688            VendorRelationshipType::RawMaterialSupplier
689        } else if roll < 0.80 {
690            VendorRelationshipType::Manufacturer
691        } else if roll < 0.88 {
692            VendorRelationshipType::Distributor
693        } else if roll < 0.94 {
694            VendorRelationshipType::Contractor
695        } else {
696            VendorRelationshipType::OemPartner
697        }
698    }
699
700    /// Select a cluster based on distribution.
701    fn select_cluster(&mut self) -> VendorCluster {
702        let roll: f64 = self.rng.gen();
703        self.network_config.cluster_distribution.select(roll)
704    }
705
706    /// Select a strategic level based on distribution.
707    fn select_strategic_level(&mut self) -> StrategicLevel {
708        let roll: f64 = self.rng.gen();
709        let mut cumulative = 0.0;
710
711        for (level, prob) in &self.network_config.strategic_distribution {
712            cumulative += prob;
713            if roll < cumulative {
714                return *level;
715            }
716        }
717
718        StrategicLevel::Standard
719    }
720
721    /// Select a spend tier.
722    fn select_spend_tier(&mut self) -> SpendTier {
723        let roll: f64 = self.rng.gen();
724        if roll < 0.05 {
725            SpendTier::Platinum
726        } else if roll < 0.20 {
727            SpendTier::Gold
728        } else if roll < 0.50 {
729            SpendTier::Silver
730        } else {
731            SpendTier::Bronze
732        }
733    }
734
735    /// Generate a relationship start date (before effective date).
736    fn generate_relationship_start_date(&mut self, effective_date: NaiveDate) -> NaiveDate {
737        let days_back: i64 = self.rng.gen_range(90..3650); // 3 months to 10 years
738        effective_date - chrono::Duration::days(days_back)
739    }
740
741    /// Generate lifecycle stage based on probabilities.
742    fn generate_lifecycle_stage(&mut self, effective_date: NaiveDate) -> VendorLifecycleStage {
743        let roll: f64 = self.rng.gen();
744        if roll < 0.05 {
745            VendorLifecycleStage::Onboarding {
746                started: effective_date - chrono::Duration::days(self.rng.gen_range(1..60)),
747                expected_completion: effective_date
748                    + chrono::Duration::days(self.rng.gen_range(30..90)),
749            }
750        } else if roll < 0.12 {
751            VendorLifecycleStage::RampUp {
752                started: effective_date - chrono::Duration::days(self.rng.gen_range(60..180)),
753                target_volume_percent: self.rng.gen_range(50..80) as u8,
754            }
755        } else if roll < 0.85 {
756            VendorLifecycleStage::SteadyState {
757                since: effective_date - chrono::Duration::days(self.rng.gen_range(180..1825)),
758            }
759        } else if roll < 0.95 {
760            VendorLifecycleStage::Decline {
761                started: effective_date - chrono::Duration::days(self.rng.gen_range(30..180)),
762                reason: DeclineReason::QualityIssues,
763            }
764        } else {
765            VendorLifecycleStage::SteadyState {
766                since: effective_date - chrono::Duration::days(self.rng.gen_range(365..1825)),
767            }
768        }
769    }
770
771    /// Generate quality score based on cluster.
772    fn generate_quality_score(&mut self, cluster: &VendorCluster) -> VendorQualityScore {
773        let (base_delivery, base_quality, base_invoice, base_response) = match cluster {
774            VendorCluster::ReliableStrategic => (0.97, 0.96, 0.98, 0.95),
775            VendorCluster::StandardOperational => (0.92, 0.90, 0.93, 0.85),
776            VendorCluster::Transactional => (0.85, 0.82, 0.88, 0.75),
777            VendorCluster::Problematic => (0.70, 0.68, 0.75, 0.60),
778        };
779
780        // Add some variance
781        let delivery_variance: f64 = self.rng.gen_range(-0.05..0.05);
782        let quality_variance: f64 = self.rng.gen_range(-0.05..0.05);
783        let invoice_variance: f64 = self.rng.gen_range(-0.05..0.05);
784        let response_variance: f64 = self.rng.gen_range(-0.05..0.05);
785
786        VendorQualityScore {
787            delivery_score: (base_delivery + delivery_variance).clamp(0.0_f64, 1.0_f64),
788            quality_score: (base_quality + quality_variance).clamp(0.0_f64, 1.0_f64),
789            invoice_accuracy_score: (base_invoice + invoice_variance).clamp(0.0_f64, 1.0_f64),
790            responsiveness_score: (base_response + response_variance).clamp(0.0_f64, 1.0_f64),
791            last_evaluation: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
792            evaluation_count: self.rng.gen_range(1..20),
793        }
794    }
795
796    /// Generate payment history based on cluster.
797    fn generate_payment_history(&mut self, cluster: &VendorCluster) -> PaymentHistory {
798        let total = self.rng.gen_range(10..200) as u32;
799        let on_time_rate = cluster.invoice_accuracy_probability();
800        let on_time = (total as f64 * on_time_rate) as u32;
801        let early = (total as f64 * self.rng.gen_range(0.05..0.20)) as u32;
802        let late = total.saturating_sub(on_time).saturating_sub(early);
803
804        PaymentHistory {
805            total_invoices: total,
806            on_time_payments: on_time,
807            early_payments: early,
808            late_payments: late,
809            total_amount: Decimal::from(total) * Decimal::from(self.rng.gen_range(1000..50000)),
810            average_days_to_pay: self.rng.gen_range(20.0..45.0),
811            last_payment_date: None,
812            total_discounts: Decimal::from(early) * Decimal::from(self.rng.gen_range(50..500)),
813        }
814    }
815
816    /// Generate vendor dependency analysis.
817    fn generate_dependency(&mut self, vendor_id: &str, vendor_name: &str) -> VendorDependency {
818        let is_single_source = self.rng.gen::<f64>() < self.network_config.single_source_percent;
819
820        let substitutability = {
821            let roll: f64 = self.rng.gen();
822            if roll < 0.60 {
823                Substitutability::Easy
824            } else if roll < 0.90 {
825                Substitutability::Moderate
826            } else {
827                Substitutability::Difficult
828            }
829        };
830
831        let mut dep = VendorDependency::new(vendor_id, self.infer_spend_category(vendor_name));
832        dep.is_single_source = is_single_source;
833        dep.substitutability = substitutability;
834        dep.concentration_percent = self.rng.gen_range(0.01..0.20);
835
836        // Generate alternative vendors if not single source
837        if !is_single_source {
838            let alt_count = self.rng.gen_range(1..4);
839            for i in 0..alt_count {
840                dep.alternatives.push(format!("ALT-{}-{:03}", vendor_id, i));
841            }
842        }
843
844        dep
845    }
846
847    /// Infer spend category from vendor name (simplified).
848    fn infer_spend_category(&self, _vendor_name: &str) -> String {
849        "General".to_string()
850    }
851
852    /// Assign annual spend to vendors based on Pareto principle.
853    fn assign_annual_spend(&mut self, network: &mut VendorNetwork, total_spend: Decimal) {
854        let tier1_count = network.tier1_vendors.len();
855        if tier1_count == 0 {
856            return;
857        }
858
859        // Generate Pareto-distributed weights
860        let mut weights: Vec<f64> = (0..tier1_count)
861            .map(|_| {
862                // Pareto distribution with alpha = 1.5
863                let u: f64 = self.rng.gen_range(0.01..1.0);
864                u.powf(-1.0 / 1.5)
865            })
866            .collect();
867
868        let total_weight: f64 = weights.iter().sum();
869        for w in &mut weights {
870            *w /= total_weight;
871        }
872
873        // Sort to ensure highest weights get assigned to first vendors
874        weights.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
875
876        // Assign spend to Tier 1 vendors
877        for (idx, vendor_id) in network.tier1_vendors.clone().iter().enumerate() {
878            if let Some(rel) = network.get_relationship_mut(vendor_id) {
879                let weight = weights.get(idx).copied().unwrap_or(0.01);
880                let spend = total_spend * Decimal::from_f64_retain(weight).unwrap_or(Decimal::ZERO);
881                rel.annual_spend = spend;
882
883                // Update spend tier based on actual spend
884                if let Some(dep) = &mut rel.dependency {
885                    dep.concentration_percent = weight;
886                }
887            }
888        }
889
890        // Assign smaller amounts to Tier 2/3 (they supply to Tier 1, not directly)
891        let tier2_avg_spend = total_spend / Decimal::from(network.tier2_vendors.len().max(1))
892            * Decimal::from_f64_retain(0.05).unwrap_or(Decimal::ZERO);
893        for vendor_id in &network.tier2_vendors.clone() {
894            if let Some(rel) = network.get_relationship_mut(vendor_id) {
895                rel.annual_spend = tier2_avg_spend
896                    * Decimal::from_f64_retain(self.rng.gen_range(0.5..1.5))
897                        .unwrap_or(Decimal::ONE);
898            }
899        }
900
901        let tier3_avg_spend = total_spend / Decimal::from(network.tier3_vendors.len().max(1))
902            * Decimal::from_f64_retain(0.01).unwrap_or(Decimal::ZERO);
903        for vendor_id in &network.tier3_vendors.clone() {
904            if let Some(rel) = network.get_relationship_mut(vendor_id) {
905                rel.annual_spend = tier3_avg_spend
906                    * Decimal::from_f64_retain(self.rng.gen_range(0.5..1.5))
907                        .unwrap_or(Decimal::ONE);
908            }
909        }
910    }
911
912    /// Generate a vendor pool with network relationships.
913    pub fn generate_vendor_pool_with_network(
914        &mut self,
915        company_code: &str,
916        effective_date: NaiveDate,
917        total_annual_spend: Decimal,
918    ) -> (VendorPool, VendorNetwork) {
919        let network =
920            self.generate_vendor_network(company_code, effective_date, total_annual_spend);
921
922        // Create VendorPool from network relationships
923        let mut pool = VendorPool::new();
924        for _vendor_id in network
925            .tier1_vendors
926            .iter()
927            .chain(network.tier2_vendors.iter())
928            .chain(network.tier3_vendors.iter())
929        {
930            // Generate a basic vendor for each relationship
931            // In practice, you'd want to store the full Vendor in the relationship
932            let vendor = self.generate_vendor(company_code, effective_date);
933            pool.add_vendor(vendor);
934        }
935
936        (pool, network)
937    }
938}
939
940#[cfg(test)]
941mod tests {
942    use super::*;
943
944    #[test]
945    fn test_vendor_generation() {
946        let mut gen = VendorGenerator::new(42);
947        let vendor = gen.generate_vendor("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
948
949        assert!(!vendor.vendor_id.is_empty());
950        assert!(!vendor.name.is_empty());
951        assert!(vendor.tax_id.is_some());
952        assert!(!vendor.bank_accounts.is_empty());
953    }
954
955    #[test]
956    fn test_vendor_pool_generation() {
957        let mut gen = VendorGenerator::new(42);
958        let pool =
959            gen.generate_vendor_pool(10, "1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
960
961        assert_eq!(pool.vendors.len(), 10);
962    }
963
964    #[test]
965    fn test_intercompany_vendor() {
966        let mut gen = VendorGenerator::new(42);
967        let vendor = gen.generate_intercompany_vendor(
968            "1000",
969            "2000",
970            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
971        );
972
973        assert!(vendor.is_intercompany);
974        assert_eq!(vendor.intercompany_code, Some("2000".to_string()));
975    }
976
977    #[test]
978    fn test_deterministic_generation() {
979        let mut gen1 = VendorGenerator::new(42);
980        let mut gen2 = VendorGenerator::new(42);
981
982        let vendor1 = gen1.generate_vendor("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
983        let vendor2 = gen2.generate_vendor("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
984
985        assert_eq!(vendor1.vendor_id, vendor2.vendor_id);
986        assert_eq!(vendor1.name, vendor2.name);
987    }
988
989    #[test]
990    fn test_vendor_pool_with_ic() {
991        // Use config with 0 intercompany_rate to test explicit IC vendors only
992        let config = VendorGeneratorConfig {
993            intercompany_rate: 0.0,
994            ..Default::default()
995        };
996        let mut gen = VendorGenerator::with_config(42, config);
997        let pool = gen.generate_vendor_pool_with_ic(
998            10,
999            "1000",
1000            &["2000".to_string(), "3000".to_string()],
1001            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1002        );
1003
1004        assert_eq!(pool.vendors.len(), 10);
1005
1006        let ic_vendors: Vec<_> = pool.vendors.iter().filter(|v| v.is_intercompany).collect();
1007        assert_eq!(ic_vendors.len(), 2);
1008    }
1009
1010    #[test]
1011    fn test_enhanced_vendor_names() {
1012        let mut gen = VendorGenerator::new(42);
1013        let vendor = gen.generate_vendor("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
1014
1015        // Enhanced naming should produce more varied, realistic names
1016        assert!(!vendor.name.is_empty());
1017        // Should not be a simple generic name format
1018        assert!(!vendor.name.starts_with("Vendor "));
1019    }
1020
1021    // ===== Vendor Network Tests =====
1022
1023    #[test]
1024    fn test_vendor_network_generation() {
1025        let network_config = VendorNetworkConfig {
1026            enabled: true,
1027            depth: 2,
1028            tier1_count: TierCountConfig::new(5, 10),
1029            tier2_per_parent: TierCountConfig::new(2, 4),
1030            tier3_per_parent: TierCountConfig::new(1, 2),
1031            ..Default::default()
1032        };
1033
1034        let mut gen = VendorGenerator::with_network_config(
1035            42,
1036            VendorGeneratorConfig::default(),
1037            network_config,
1038        );
1039
1040        let network = gen.generate_vendor_network(
1041            "1000",
1042            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1043            Decimal::from(10_000_000),
1044        );
1045
1046        assert!(!network.tier1_vendors.is_empty());
1047        assert!(!network.tier2_vendors.is_empty());
1048        assert!(network.tier1_vendors.len() >= 5);
1049        assert!(network.tier1_vendors.len() <= 10);
1050    }
1051
1052    #[test]
1053    fn test_vendor_network_relationships() {
1054        let network_config = VendorNetworkConfig {
1055            enabled: true,
1056            depth: 2,
1057            tier1_count: TierCountConfig::new(3, 3),
1058            tier2_per_parent: TierCountConfig::new(2, 2),
1059            ..Default::default()
1060        };
1061
1062        let mut gen = VendorGenerator::with_network_config(
1063            42,
1064            VendorGeneratorConfig::default(),
1065            network_config,
1066        );
1067
1068        let network = gen.generate_vendor_network(
1069            "1000",
1070            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1071            Decimal::from(5_000_000),
1072        );
1073
1074        // Check that Tier 2 vendors have parents
1075        for tier2_id in &network.tier2_vendors {
1076            let rel = network.get_relationship(tier2_id).unwrap();
1077            assert!(rel.parent_vendor.is_some());
1078            assert_eq!(rel.tier, SupplyChainTier::Tier2);
1079        }
1080
1081        // Check that Tier 1 vendors have children
1082        for tier1_id in &network.tier1_vendors {
1083            let rel = network.get_relationship(tier1_id).unwrap();
1084            assert!(!rel.child_vendors.is_empty());
1085            assert_eq!(rel.tier, SupplyChainTier::Tier1);
1086        }
1087    }
1088
1089    #[test]
1090    fn test_vendor_network_spend_distribution() {
1091        let network_config = VendorNetworkConfig {
1092            enabled: true,
1093            depth: 1,
1094            tier1_count: TierCountConfig::new(10, 10),
1095            ..Default::default()
1096        };
1097
1098        let mut gen = VendorGenerator::with_network_config(
1099            42,
1100            VendorGeneratorConfig::default(),
1101            network_config,
1102        );
1103
1104        let total_spend = Decimal::from(10_000_000);
1105        let network = gen.generate_vendor_network(
1106            "1000",
1107            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1108            total_spend,
1109        );
1110
1111        // Check that spend is distributed
1112        let total_assigned: Decimal = network.relationships.values().map(|r| r.annual_spend).sum();
1113
1114        assert!(total_assigned > Decimal::ZERO);
1115    }
1116
1117    #[test]
1118    fn test_vendor_network_cluster_distribution() {
1119        let network_config = VendorNetworkConfig {
1120            enabled: true,
1121            depth: 1,
1122            tier1_count: TierCountConfig::new(100, 100),
1123            cluster_distribution: ClusterDistribution::default(),
1124            ..Default::default()
1125        };
1126
1127        let mut gen = VendorGenerator::with_network_config(
1128            42,
1129            VendorGeneratorConfig::default(),
1130            network_config,
1131        );
1132
1133        let network = gen.generate_vendor_network(
1134            "1000",
1135            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1136            Decimal::from(10_000_000),
1137        );
1138
1139        // Count clusters
1140        let mut cluster_counts: std::collections::HashMap<VendorCluster, usize> =
1141            std::collections::HashMap::new();
1142        for rel in network.relationships.values() {
1143            *cluster_counts.entry(rel.cluster).or_insert(0) += 1;
1144        }
1145
1146        // ReliableStrategic should be roughly 20%
1147        let reliable = cluster_counts
1148            .get(&VendorCluster::ReliableStrategic)
1149            .unwrap_or(&0);
1150        assert!(*reliable >= 10 && *reliable <= 35);
1151    }
1152
1153    #[test]
1154    fn test_cluster_distribution_validation() {
1155        let valid = ClusterDistribution::default();
1156        assert!(valid.validate().is_ok());
1157
1158        let invalid = ClusterDistribution {
1159            reliable_strategic: 0.5,
1160            standard_operational: 0.5,
1161            transactional: 0.5,
1162            problematic: 0.5,
1163        };
1164        assert!(invalid.validate().is_err());
1165    }
1166
1167    #[test]
1168    fn test_vendor_network_disabled() {
1169        let network_config = VendorNetworkConfig {
1170            enabled: false,
1171            ..Default::default()
1172        };
1173
1174        let mut gen = VendorGenerator::with_network_config(
1175            42,
1176            VendorGeneratorConfig::default(),
1177            network_config,
1178        );
1179
1180        let network = gen.generate_vendor_network(
1181            "1000",
1182            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
1183            Decimal::from(10_000_000),
1184        );
1185
1186        assert!(network.tier1_vendors.is_empty());
1187        assert!(network.relationships.is_empty());
1188    }
1189}