@@ -77,13 +77,35 @@ fn calc_final_eligible_rankings(
77
77
. collect ( )
78
78
}
79
79
80
+ fn calc_final_ranking_consensus_per_review ( rankings : & [ impl Borrow < VeteranRankingRow > ] ) -> Decimal {
81
+ let rankings_majority = Decimal :: from ( rankings. len ( ) ) / Decimal :: from ( 2 ) ;
82
+ let ranks = rankings. iter ( ) . counts_by ( |r| r. borrow ( ) . score ( ) ) ;
83
+
84
+ match ( ranks. get ( & FilteredOut ) , ranks. get ( & Excellent ) , ranks. get ( & Good ) ) {
85
+ ( Some ( filtered_out) , _, _) if Decimal :: from ( * filtered_out) >= rankings_majority => {
86
+ Decimal :: from ( * filtered_out) / Decimal :: from ( rankings. len ( ) )
87
+ }
88
+ ( _, Some ( excellent) , _) if Decimal :: from ( * excellent) > rankings_majority => {
89
+ Decimal :: from ( * excellent) / Decimal :: from ( rankings. len ( ) )
90
+ }
91
+ ( _, Some ( excellent) , Some ( good) ) => {
92
+ ( Decimal :: from ( * excellent) + Decimal :: from ( * good) ) / Decimal :: from ( rankings. len ( ) )
93
+ }
94
+ ( _, _, Some ( good) ) => {
95
+ Decimal :: from ( * good) / Decimal :: from ( rankings. len ( ) )
96
+ }
97
+ _ => Decimal :: ONE ,
98
+ }
99
+ }
100
+
80
101
pub fn calculate_veteran_advisors_incentives (
81
102
veteran_rankings : & [ VeteranRankingRow ] ,
82
103
total_rewards : Rewards ,
83
104
rewards_thresholds : EligibilityThresholds ,
84
105
reputation_thresholds : EligibilityThresholds ,
85
106
rewards_mod_args : Vec < ( Decimal , Decimal ) > ,
86
107
reputation_mod_args : Vec < ( Decimal , Decimal ) > ,
108
+ minimum_consensus : Decimal ,
87
109
) -> HashMap < VeteranAdvisorId , VeteranAdvisorIncentive > {
88
110
let final_rankings_per_review = veteran_rankings
89
111
. iter ( )
@@ -92,6 +114,13 @@ pub fn calculate_veteran_advisors_incentives(
92
114
. map ( |( review, rankings) | ( review, calc_final_ranking_per_review ( & rankings) ) )
93
115
. collect :: < BTreeMap < _ , _ > > ( ) ;
94
116
117
+ let final_rankings_consensus_per_review = veteran_rankings
118
+ . iter ( )
119
+ . into_group_map_by ( |ranking| ranking. review_id ( ) )
120
+ . into_iter ( )
121
+ . map ( |( review, rankings) | ( review, calc_final_ranking_consensus_per_review ( & rankings) ) )
122
+ . collect :: < BTreeMap < _ , _ > > ( ) ;
123
+
95
124
let rankings_per_vca = veteran_rankings
96
125
. iter ( )
97
126
. counts_by ( |ranking| ranking. vca . clone ( ) ) ;
@@ -103,7 +132,7 @@ pub fn calculate_veteran_advisors_incentives(
103
132
. get ( & ranking. review_id ( ) )
104
133
. unwrap ( )
105
134
. is_positive ( )
106
- == ranking. score ( ) . is_positive ( )
135
+ == ranking. score ( ) . is_positive ( ) || * final_rankings_consensus_per_review . get ( & ranking . review_id ( ) ) . unwrap ( ) < minimum_consensus
107
136
} )
108
137
. counts_by ( |ranking| ranking. vca . clone ( ) ) ;
109
138
@@ -156,6 +185,8 @@ mod tests {
156
185
const VCA_1 : & str = "vca1" ;
157
186
const VCA_2 : & str = "vca2" ;
158
187
const VCA_3 : & str = "vca3" ;
188
+ const SIMPLE_MINIMUM_CONSENSUS : Decimal = dec ! ( . 5 ) ;
189
+ const QUALIFIED_MINIMUM_CONSENSUS : Decimal = dec ! ( . 7 ) ;
159
190
160
191
struct RandomIterator ;
161
192
impl Iterator for RandomIterator {
@@ -231,6 +262,7 @@ mod tests {
231
262
. into_iter ( )
232
263
. zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
233
264
. collect ( ) ,
265
+ SIMPLE_MINIMUM_CONSENSUS ,
234
266
) ;
235
267
assert ! ( results. get( VCA_1 ) . is_none( ) ) ;
236
268
let res = results. get ( VCA_2 ) . unwrap ( ) ;
@@ -260,6 +292,7 @@ mod tests {
260
292
. into_iter ( )
261
293
. zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
262
294
. collect ( ) ,
295
+ SIMPLE_MINIMUM_CONSENSUS ,
263
296
) ;
264
297
let res1 = results. get ( VCA_1 ) . unwrap ( ) ;
265
298
assert_eq ! ( res1. reputation, 1 ) ;
@@ -283,12 +316,12 @@ mod tests {
283
316
( Rewards :: new ( 8 , 1 ) , Rewards :: ONE , Rewards :: ONE ) ,
284
317
( Rewards :: new ( 9 , 1 ) , Rewards :: new ( 125 , 2 ) , Rewards :: ONE ) ,
285
318
] ;
286
- for ( agreement , reward_modifier, reputation_modifier) in inputs {
319
+ for ( vca3_agreement , reward_modifier, reputation_modifier) in inputs {
287
320
let rankings = ( 0 ..100 )
288
321
. flat_map ( |i| {
289
322
let vcas =
290
323
vec ! [ VCA_1 . to_owned( ) , VCA_2 . to_owned( ) , VCA_3 . to_owned( ) ] . into_iter ( ) ;
291
- let ( good, filtered_out) = if Rewards :: from ( i) < agreement * Rewards :: from ( 100 )
324
+ let ( good, filtered_out) = if Rewards :: from ( i) < vca3_agreement * Rewards :: from ( 100 )
292
325
{
293
326
( 3 , 0 )
294
327
} else {
@@ -297,7 +330,7 @@ mod tests {
297
330
gen_dummy_rankings ( i. to_string ( ) , 0 , good, filtered_out, vcas) . into_iter ( )
298
331
} )
299
332
. collect :: < Vec < _ > > ( ) ;
300
- let results = calculate_veteran_advisors_incentives (
333
+ let results_simple_consensus = calculate_veteran_advisors_incentives (
301
334
& rankings,
302
335
total_rewards,
303
336
1 ..=200 ,
@@ -310,21 +343,58 @@ mod tests {
310
343
. into_iter ( )
311
344
. zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
312
345
. collect ( ) ,
346
+ SIMPLE_MINIMUM_CONSENSUS ,
313
347
) ;
314
- let expected_reward_portion = agreement * Rewards :: from ( 100 ) * reward_modifier;
315
- dbg ! ( expected_reward_portion) ;
316
- dbg ! ( agreement, reward_modifier, reputation_modifier) ;
317
- let expected_rewards = total_rewards
318
- / ( Rewards :: from ( 125 * 2 ) + expected_reward_portion)
319
- * expected_reward_portion;
320
- let res = results. get ( VCA_3 ) . unwrap ( ) ;
348
+ let vca3_expected_reward_portion_simple_consensus = vca3_agreement * Rewards :: from ( 100 ) * reward_modifier;
349
+ dbg ! ( vca3_expected_reward_portion_simple_consensus) ;
350
+ dbg ! ( vca3_agreement, reward_modifier, reputation_modifier) ;
351
+ let vca3_expected_rewards_simple_consensus = total_rewards
352
+ / ( Rewards :: from ( 125 * 2 ) + vca3_expected_reward_portion_simple_consensus)
353
+ * vca3_expected_reward_portion_simple_consensus;
354
+ let res_vca3_simple_consensus = results_simple_consensus. get ( VCA_3 ) . unwrap ( ) ;
355
+ assert_eq ! (
356
+ res_vca3_simple_consensus. reputation,
357
+ ( Rewards :: from( 100 ) * vca3_agreement * reputation_modifier)
358
+ . to_u64( )
359
+ . unwrap( )
360
+ ) ;
361
+ assert ! ( are_close( res_vca3_simple_consensus. rewards, vca3_expected_rewards_simple_consensus) ) ;
362
+
363
+
364
+ let results_qualified_consensus = calculate_veteran_advisors_incentives (
365
+ & rankings,
366
+ total_rewards,
367
+ 1 ..=200 ,
368
+ 1 ..=200 ,
369
+ THRESHOLDS
370
+ . into_iter ( )
371
+ . zip ( REWARDS_DISAGREEMENT_MODIFIERS . into_iter ( ) )
372
+ . collect ( ) ,
373
+ THRESHOLDS
374
+ . into_iter ( )
375
+ . zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
376
+ . collect ( ) ,
377
+ QUALIFIED_MINIMUM_CONSENSUS ,
378
+ ) ;
379
+
380
+ let vca3_expected_reward_portion_qualified_consensus = Rewards :: from ( 100 ) * dec ! ( 1.25 ) ; // low consensus so max reward modifier, agreement ratio doesn't count as all and rankings are all eligible
381
+ dbg ! ( vca3_expected_reward_portion_qualified_consensus) ;
382
+ dbg ! ( vca3_agreement, reward_modifier, reputation_modifier) ;
383
+
384
+ let vca3_expected_rewards_qualified_consensus = total_rewards
385
+ / ( Rewards :: from ( 125 * 2 ) + vca3_expected_reward_portion_qualified_consensus)
386
+ * vca3_expected_reward_portion_qualified_consensus; // 1/3 of the reward
387
+
388
+ let res_vca3_qualified_consensus = results_qualified_consensus. get ( VCA_3 ) . unwrap ( ) ;
389
+
390
+
321
391
assert_eq ! (
322
- res . reputation,
323
- ( Rewards :: from( 100 ) * agreement * reputation_modifier )
392
+ res_vca3_qualified_consensus . reputation,
393
+ ( Rewards :: from( 100 ) ) // all assessment are valid since consensus is low (2/3 < 0.7 )
324
394
. to_u64( )
325
395
. unwrap( )
326
396
) ;
327
- assert ! ( are_close( res . rewards, expected_rewards ) ) ;
397
+ assert ! ( are_close( res_vca3_qualified_consensus . rewards, vca3_expected_rewards_qualified_consensus ) ) ;
328
398
}
329
399
}
330
400
}
0 commit comments