#![cfg(any())]
use std::collections::BTreeMap;
use datasynth_config::schema::{
ConcentrationConfig, SourceConditionalRarityConfig, TradingPartnerPoolConfig,
};
use datasynth_core::error::SynthError;
use datasynth_core::models::journal_entry::JournalEntry;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use serde::Serialize;
pub trait ConcentrationPass: Send + Sync {
fn name(&self) -> &'static str;
fn apply(
&self,
entries: &mut [JournalEntry],
rng: &mut ChaCha8Rng,
) -> ConcentrationStats;
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct ConcentrationStats {
pub pass: &'static str,
pub entries_examined: usize,
pub entries_modified: usize,
pub extra: BTreeMap<&'static str, u64>,
}
pub struct ConcentrationPipeline {
passes: Vec<Box<dyn ConcentrationPass>>,
}
impl ConcentrationPipeline {
pub fn from_config(cfg: &ConcentrationConfig) -> Result<Self, SynthError> {
let mut passes: Vec<Box<dyn ConcentrationPass>> = Vec::new();
if !cfg.enabled {
return Ok(Self { passes });
}
if let Some(c) = cfg.source_conditional_rarity.as_ref() {
passes.push(Box::new(SourceConditionalRarityPass::new(c.clone())));
}
if let Some(c) = cfg.trading_partner_pool.as_ref() {
passes.push(Box::new(TradingPartnerPoolPass::new(c.clone())));
}
Ok(Self { passes })
}
pub fn run(
&self,
entries: &mut [JournalEntry],
seed: u64,
) -> Vec<ConcentrationStats> {
const STREAM_STRIDE: u64 = 0x9E37_79B9_7F4A_7C15;
self.passes
.iter()
.enumerate()
.map(|(idx, pass)| {
let stream_seed = seed.wrapping_add((idx as u64).wrapping_mul(STREAM_STRIDE));
let mut rng = ChaCha8Rng::seed_from_u64(stream_seed);
pass.apply(entries, &mut rng)
})
.collect()
}
pub fn is_active(&self) -> bool {
!self.passes.is_empty()
}
}
pub struct SourceConditionalRarityPass {
cfg: SourceConditionalRarityConfig,
}
impl SourceConditionalRarityPass {
pub fn new(cfg: SourceConditionalRarityConfig) -> Self {
Self { cfg }
}
}
impl ConcentrationPass for SourceConditionalRarityPass {
fn name(&self) -> &'static str {
"source_conditional_rarity"
}
fn apply(
&self,
entries: &mut [JournalEntry],
_rng: &mut ChaCha8Rng,
) -> ConcentrationStats {
let tagged: usize = unimplemented!("Phase 1: delegate to existing tagger");
ConcentrationStats {
pass: "source_conditional_rarity",
entries_examined: entries.len(),
entries_modified: tagged,
extra: BTreeMap::new(),
}
}
}
pub struct TradingPartnerPoolPass {
target_size: usize,
}
impl TradingPartnerPoolPass {
pub fn new(cfg: TradingPartnerPoolConfig) -> Self {
Self {
target_size: cfg.target_size.max(1),
}
}
#[inline]
fn pool_index(&self, tp: &str) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
let mut h = FNV_OFFSET;
for b in tp.as_bytes() {
h ^= *b as u64;
h = h.wrapping_mul(FNV_PRIME);
}
h % (self.target_size as u64)
}
}
impl ConcentrationPass for TradingPartnerPoolPass {
fn name(&self) -> &'static str {
"trading_partner_pool"
}
fn apply(
&self,
entries: &mut [JournalEntry],
_rng: &mut ChaCha8Rng,
) -> ConcentrationStats {
let mut modified_lines: usize = 0;
let mut entries_modified: usize = 0;
for je in entries.iter_mut() {
let mut je_touched = false;
for line in je.lines.iter_mut() {
if let Some(tp) = line.trading_partner.as_ref() {
let new_tp = format!("TP-{:06}", self.pool_index(tp));
if &new_tp != tp {
line.trading_partner = Some(new_tp);
modified_lines += 1;
je_touched = true;
}
}
}
if je_touched {
entries_modified += 1;
}
}
let mut extra = BTreeMap::new();
extra.insert("lines_modified", modified_lines as u64);
ConcentrationStats {
pass: "trading_partner_pool",
entries_examined: entries.len(),
entries_modified,
extra,
}
}
}
pub struct AccountPairSubstitutionPass {
}
impl AccountPairSubstitutionPass {
pub fn from_pmf_file(_path: &str) -> Result<Self, SynthError> {
unimplemented!("Phase 2: see 2026-05-23-concentration-pass-phase2-design.md")
}
}
impl ConcentrationPass for AccountPairSubstitutionPass {
fn name(&self) -> &'static str {
"account_pair_substitution"
}
fn apply(
&self,
_entries: &mut [JournalEntry],
_rng: &mut ChaCha8Rng,
) -> ConcentrationStats {
unimplemented!("Phase 2: see 2026-05-23-concentration-pass-phase2-design.md")
}
}
pub const STRUCTURAL_BRIDGE_ACCOUNTS: &[&str] = &[
"1100", "2000", "2900", "1150", "2050", "1030", "1599", ];
#[inline]
pub fn is_structural_bridge(account: &str) -> bool {
STRUCTURAL_BRIDGE_ACCOUNTS.iter().any(|a| *a == account)
}