@@ -20,17 +20,21 @@ pub use persistence::{load_feedback_store, load_feedback_store_from_path, save_f
2020#[ allow( unused_imports) ]
2121pub use record:: {
2222 apply_comment_dismissal_signal, apply_comment_feedback_signal,
23- apply_comment_feedback_signal_at, apply_comment_resolution_outcome_signal,
24- apply_comment_resolution_outcome_signal_at, record_comment_dismissal_stats,
25- record_comment_feedback_stats, record_comment_feedback_stats_at,
23+ apply_comment_feedback_signal_at, apply_comment_feedback_signal_with_weight,
24+ apply_comment_resolution_outcome_signal, apply_comment_resolution_outcome_signal_at,
25+ record_comment_dismissal_stats, record_comment_feedback_stats,
26+ record_comment_feedback_stats_at, record_comment_feedback_stats_with_weight,
2627 record_comment_resolution_stats, record_comment_resolution_stats_at, CommentResolutionOutcome ,
2728} ;
2829#[ allow( unused_imports) ]
29- pub use semantic:: { record_semantic_feedback_example, record_semantic_feedback_examples} ;
30+ pub use semantic:: {
31+ record_semantic_feedback_example, record_semantic_feedback_examples,
32+ record_semantic_feedback_examples_with_weight,
33+ } ;
3034#[ allow( unused_imports) ]
3135pub use store:: {
32- DecayedFeedbackStats , FeedbackExplanation , FeedbackPatternStats , FeedbackStore ,
33- FeedbackTypeStats ,
36+ DecayedFeedbackStats , FeedbackActorMetadata , FeedbackExplanation , FeedbackPatternStats ,
37+ FeedbackStore , FeedbackTypeStats ,
3438} ;
3539
3640#[ cfg( test) ]
@@ -53,6 +57,7 @@ mod tests {
5357 assert ! ( store. by_rule. is_empty( ) ) ;
5458 assert ! ( store. by_rule_file_pattern. is_empty( ) ) ;
5559 assert ! ( store. explanations_by_comment. is_empty( ) ) ;
60+ assert ! ( store. feedback_actor_by_comment. is_empty( ) ) ;
5661 }
5762
5863 #[ test]
@@ -83,14 +88,12 @@ mod tests {
8388 FeedbackPatternStats {
8489 accepted : 1 ,
8590 rejected : 2 ,
86- dismissed : 0 ,
87- addressed : 0 ,
88- not_addressed : 0 ,
8991 decayed : Some ( DecayedFeedbackStats {
9092 positive : 0.75 ,
9193 negative : 1.25 ,
9294 last_event_at : Some ( 123 ) ,
9395 } ) ,
96+ ..Default :: default ( )
9497 } ,
9598 ) ;
9699 store. by_comment_type . insert (
@@ -101,6 +104,7 @@ mod tests {
101104 dismissed : 3 ,
102105 addressed : 4 ,
103106 not_addressed : 5 ,
107+ ..Default :: default ( )
104108 } ,
105109 ) ;
106110 store. explanations_by_comment . insert (
@@ -116,6 +120,15 @@ mod tests {
116120 updated_at : "2026-03-15T00:00:00Z" . to_string ( ) ,
117121 } ,
118122 ) ;
123+ store. feedback_actor_by_comment . insert (
124+ "review-1::comment-1" . to_string ( ) ,
125+ FeedbackActorMetadata {
126+ github_login : Some ( "maintainer" . to_string ( ) ) ,
127+ github_role : Some ( "write" . to_string ( ) ) ,
128+ trust_weight : 1.5 ,
129+ updated_at : "2026-03-15T00:00:00Z" . to_string ( ) ,
130+ } ,
131+ ) ;
119132
120133 let json = serde_json:: to_string ( & store) . unwrap ( ) ;
121134 let deserialized: FeedbackStore = serde_json:: from_str ( & json) . unwrap ( ) ;
@@ -134,6 +147,12 @@ mod tests {
134147 Some ( 2.0 )
135148 ) ;
136149 assert_eq ! ( deserialized. explanations_by_comment. len( ) , 1 ) ;
150+ assert_eq ! (
151+ deserialized. feedback_actor_by_comment[ "review-1::comment-1" ]
152+ . github_role
153+ . as_deref( ) ,
154+ Some ( "write" )
155+ ) ;
137156 }
138157
139158 #[ test]
@@ -155,11 +174,7 @@ mod tests {
155174 fn pattern_stats_acceptance_rate_all_accepted ( ) {
156175 let stats = FeedbackPatternStats {
157176 accepted : 10 ,
158- rejected : 0 ,
159- dismissed : 0 ,
160- addressed : 0 ,
161- not_addressed : 0 ,
162- decayed : None ,
177+ ..Default :: default ( )
163178 } ;
164179 assert_eq ! ( stats. acceptance_rate( ) , 1.0 ) ;
165180 assert_eq ! ( stats. total( ) , 10 ) ;
@@ -168,12 +183,8 @@ mod tests {
168183 #[ test]
169184 fn pattern_stats_acceptance_rate_all_rejected ( ) {
170185 let stats = FeedbackPatternStats {
171- accepted : 0 ,
172186 rejected : 10 ,
173- dismissed : 0 ,
174- addressed : 0 ,
175- not_addressed : 0 ,
176- decayed : None ,
187+ ..Default :: default ( )
177188 } ;
178189 assert_eq ! ( stats. acceptance_rate( ) , 0.0 ) ;
179190 }
@@ -183,23 +194,48 @@ mod tests {
183194 let stats = FeedbackPatternStats {
184195 accepted : 3 ,
185196 rejected : 7 ,
186- dismissed : 0 ,
187- addressed : 0 ,
188- not_addressed : 0 ,
189- decayed : None ,
197+ ..Default :: default ( )
190198 } ;
191199 assert ! ( ( stats. acceptance_rate( ) - 0.3 ) . abs( ) < f32 :: EPSILON ) ;
192200 }
193201
202+ #[ test]
203+ fn pattern_stats_weighted_acceptance_rate_prefers_weighted_signal ( ) {
204+ let stats = FeedbackPatternStats {
205+ accepted : 1 ,
206+ rejected : 1 ,
207+ accepted_weight : 3.0 ,
208+ rejected_weight : 1.0 ,
209+ ..Default :: default ( )
210+ } ;
211+
212+ assert ! ( ( stats. weighted_acceptance_rate( ) - 0.75 ) . abs( ) < f32 :: EPSILON ) ;
213+ assert ! ( ( stats. weighted_total( ) - 4.0 ) . abs( ) < f32 :: EPSILON ) ;
214+ }
215+
216+ #[ test]
217+ fn pattern_stats_weighted_acceptance_rate_preserves_legacy_counts_without_weights ( ) {
218+ let stats = FeedbackPatternStats {
219+ accepted : 1 ,
220+ addressed : 1 ,
221+ rejected : 1 ,
222+ accepted_weight : 2.0 ,
223+ ..Default :: default ( )
224+ } ;
225+
226+ assert ! ( ( stats. weighted_positive_total( ) - 3.0 ) . abs( ) < f32 :: EPSILON ) ;
227+ assert ! ( ( stats. weighted_negative_total( ) - 1.0 ) . abs( ) < f32 :: EPSILON ) ;
228+ assert ! ( ( stats. weighted_acceptance_rate( ) - 0.75 ) . abs( ) < f32 :: EPSILON ) ;
229+ }
230+
194231 #[ test]
195232 fn pattern_stats_acceptance_rate_includes_outcome_signals ( ) {
196233 let stats = FeedbackPatternStats {
197234 accepted : 1 ,
198235 rejected : 1 ,
199- dismissed : 0 ,
200236 addressed : 3 ,
201237 not_addressed : 1 ,
202- decayed : None ,
238+ .. Default :: default ( )
203239 } ;
204240
205241 assert ! ( ( stats. acceptance_rate( ) - ( 4.0 / 6.0 ) ) . abs( ) < f32 :: EPSILON ) ;
@@ -416,6 +452,26 @@ mod tests {
416452 "Tenant isolation must stay explicit to avoid cross-account reads." ,
417453 "2026-03-15T00:01:00Z" ,
418454 ) ) ;
455+ assert ! ( store. record_feedback_actor(
456+ "review-1" ,
457+ "comment-1" ,
458+ FeedbackActorMetadata {
459+ github_login: Some ( "repo-owner" . to_string( ) ) ,
460+ github_role: Some ( "admin" . to_string( ) ) ,
461+ trust_weight: 2.0 ,
462+ updated_at: "2026-03-15T00:00:00Z" . to_string( ) ,
463+ }
464+ ) ) ;
465+ assert ! ( store. record_feedback_actor(
466+ "review-2" ,
467+ "comment-2" ,
468+ FeedbackActorMetadata {
469+ github_login: Some ( "triager" . to_string( ) ) ,
470+ github_role: Some ( "triage" . to_string( ) ) ,
471+ trust_weight: 1.25 ,
472+ updated_at: "2026-03-15T00:01:00Z" . to_string( ) ,
473+ }
474+ ) ) ;
419475
420476 let context = generate_feedback_context ( & store) ;
421477 assert ! (
0 commit comments