1const MIN_SAMPLE_EPSILON: f64 = 1e-12;
2const CONFIDENCE_SHRINKAGE_Z: f64 = 1.281_551_565_545;
3const SAMPLE_GROWTH_SCALE: f64 = 8.0;
4
5pub const MIN_SAMPLES_PER_SIGNATURE: usize = 3;
6
7#[derive(Debug, Clone, Copy)]
8pub struct ConfidenceEnvelope {
9 pub raw_confidence: f64,
10 pub coverage_fraction: f64,
11 pub sample_units: f64,
12 pub signature_count: usize,
13}
14
15impl ConfidenceEnvelope {
16 pub fn bounded_sample_factor(self, min_samples_per_signature: usize) -> f64 {
17 sample_sufficiency_factor(
18 self.sample_units,
19 self.signature_count,
20 min_samples_per_signature,
21 )
22 .clamp(0.0, 1.0)
23 }
24
25 pub fn bounded_confidence(self, min_samples_per_signature: usize) -> f64 {
26 advisory_confidence(
27 self.raw_confidence,
28 self.coverage_fraction,
29 self.sample_units,
30 self.signature_count,
31 min_samples_per_signature,
32 )
33 }
34}
35
36pub fn sample_sufficiency_factor(
37 sample_units: f64,
38 signature_count: usize,
39 min_samples_per_signature: usize,
40) -> f64 {
41 if signature_count == 0 || min_samples_per_signature == 0 {
42 return 0.0;
43 }
44
45 let required = (signature_count * min_samples_per_signature) as f64;
46 if required <= MIN_SAMPLE_EPSILON {
47 return 0.0;
48 }
49
50 (sample_units / required).clamp(0.0, 1.0)
51}
52
53pub fn advisory_confidence(
54 base_confidence: f64,
55 coverage_fraction: f64,
56 sample_units: f64,
57 signature_count: usize,
58 min_samples_per_signature: usize,
59) -> f64 {
60 let bounded_confidence = base_confidence.clamp(0.0, 1.0);
61 let bounded_coverage = coverage_fraction.clamp(0.0, 1.0);
62 let sufficiency =
63 sample_sufficiency_factor(sample_units, signature_count, min_samples_per_signature);
64 let effective_samples = effective_sample_size(sample_units);
65 let sample_weight = sample_factor(effective_samples);
66
67 (bounded_confidence.min(bounded_coverage) * sample_weight * sufficiency).clamp(0.0, 1.0)
68}
69
70pub fn posterior_mean(alpha: f64, beta: f64) -> f64 {
71 let total = (alpha + beta).max(MIN_SAMPLE_EPSILON);
72 (alpha / total).clamp(0.0, 1.0)
73}
74
75pub fn posterior_variance(alpha: f64, beta: f64) -> f64 {
76 let total = (alpha + beta).max(MIN_SAMPLE_EPSILON);
77 (alpha * beta) / ((total * total) * (total + 1.0)).max(MIN_SAMPLE_EPSILON)
78}
79
80pub fn conservative_reliability(alpha: f64, beta: f64) -> f64 {
81 let mean = posterior_mean(alpha, beta);
82 let std_dev = posterior_variance(alpha, beta).sqrt();
83 let lower = mean - (CONFIDENCE_SHRINKAGE_Z * std_dev);
84 lower.clamp(0.0, 1.0)
85}
86
87pub fn effective_sample_size(total_samples: f64) -> f64 {
88 (total_samples - 2.0).max(0.0)
89}
90
91pub fn sample_factor(effective_sample_size: f64) -> f64 {
92 (1.0 - (-effective_sample_size / SAMPLE_GROWTH_SCALE).exp()).clamp(0.0, 1.0)
93}