deep_causality/traits/observable/mod.rs
1/*
2 * SPDX-License-Identifier: MIT
3 * Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
4 */
5
6use std::fmt::Debug;
7
8use crate::{Identifiable, NumericalValue};
9
10/// Observable trait for objects that can be observed.
11///
12/// Requires:
13///
14/// - Debug - for debug printing
15/// - Identifiable - for unique identification
16///
17/// Provides methods:
18///
19/// - observation() - gets the numerical observation value
20/// - observed_effect() - gets the observed effect value
21/// - effect_observed() - checks if observation meets threshold and matches effect
22///
23/// effect_observed() checks:
24///
25/// - observation >= target_threshold
26/// - observed_effect == target_effect
27///
28pub trait Observable: Debug + Identifiable {
29 fn observation(&self) -> NumericalValue;
30 fn observed_effect(&self) -> NumericalValue;
31
32 /// Checks if the observed effect meets the target threshold and effect.
33 ///
34 /// Returns true if:
35 ///
36 /// - observation() >= target_threshold
37 /// - observed_effect() == target_effect
38 ///
39 /// Otherwise returns false.
40 ///
41 fn effect_observed(
42 &self,
43 target_threshold: NumericalValue,
44 target_effect: NumericalValue,
45 ) -> bool {
46 (self.observation() >= target_threshold) && (self.observed_effect() == target_effect)
47 }
48}
49
50/// ObservableReasoning trait provides reasoning methods for collections of Observable items.
51///
52/// Where T: Observable
53///
54/// Provides methods:
55///
56/// - len() - number of items
57/// - is_empty() - checks if empty
58/// - get_all_items() - returns all items
59///
60/// - number_observation() - counts items meeting threshold and effect
61/// - number_non_observation() - counts items not meeting criteria
62/// - percent_observation() - % of items meeting criteria
63/// - percent_non_observation() - % of items not meeting criteria
64///
65/// Uses T's effect_observed() method to check criteria.
66///
67pub trait ObservableReasoning<T>
68where
69 T: Observable,
70{
71 // Compiler generated methods using macros.
72 fn len(&self) -> usize;
73 fn is_empty(&self) -> bool;
74 fn get_all_items(&self) -> Vec<&T>;
75
76 //
77 // Default implementations.
78 //
79
80 /// Counts the number of observations meeting the criteria.
81 ///
82 /// Iterates through all items and filters based on:
83 ///
84 /// - item.effect_observed(target_threshold, target_effect)
85 ///
86 /// Then returns the count.
87 ///
88 fn number_observation(
89 &self,
90 target_threshold: NumericalValue,
91 target_effect: NumericalValue,
92 ) -> NumericalValue {
93 self.get_all_items()
94 .iter()
95 .filter(|o| o.effect_observed(target_threshold, target_effect))
96 .count() as NumericalValue
97 }
98
99 /// Counts the number of non-observations based on the criteria.
100 ///
101 /// Calculates this by:
102 ///
103 /// - self.len() - total number of items
104 /// - minus number_observation() count
105 ///
106 /// Returns the number of items not meeting criteria.
107 ///
108 fn number_non_observation(
109 &self,
110 target_threshold: NumericalValue,
111 target_effect: NumericalValue,
112 ) -> NumericalValue {
113 self.len() as NumericalValue - self.number_observation(target_threshold, target_effect)
114 }
115
116 /// Calculates the percentage of observations meeting the criteria.
117 ///
118 /// Divides the number_observation count by the total number of items.
119 ///
120 /// Returns value between 0.0 and 1.0 as a percentage.
121 ///
122 fn percent_observation(
123 &self,
124 target_threshold: NumericalValue,
125 target_effect: NumericalValue,
126 ) -> NumericalValue {
127 if self.is_empty() {
128 return 0.0;
129 }
130
131 self.number_observation(target_threshold, target_effect) / self.len() as NumericalValue
132 }
133
134 /// Calculates the percentage of non-observations based on the criteria.
135 ///
136 /// Returns 1.0 minus the percent_observation.
137 ///
138 fn percent_non_observation(
139 &self,
140 target_threshold: NumericalValue,
141 target_effect: NumericalValue,
142 ) -> NumericalValue {
143 1.0 - self.percent_observation(target_threshold, target_effect)
144 }
145}