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