@@ -77,13 +77,29 @@ fn calc_final_eligible_rankings(
7777 . collect ( )
7878}
7979
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 ) ) {
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+ _ => Decimal :: from ( * ranks. get ( & Good ) . unwrap ( ) ) / Decimal :: from ( rankings. len ( ) ) ,
92+ }
93+ }
94+
8095pub fn calculate_veteran_advisors_incentives (
8196 veteran_rankings : & [ VeteranRankingRow ] ,
8297 total_rewards : Rewards ,
8398 rewards_thresholds : EligibilityThresholds ,
8499 reputation_thresholds : EligibilityThresholds ,
85100 rewards_mod_args : Vec < ( Decimal , Decimal ) > ,
86101 reputation_mod_args : Vec < ( Decimal , Decimal ) > ,
102+ minimum_consensus : Decimal ,
87103) -> HashMap < VeteranAdvisorId , VeteranAdvisorIncentive > {
88104 let final_rankings_per_review = veteran_rankings
89105 . iter ( )
@@ -92,6 +108,13 @@ pub fn calculate_veteran_advisors_incentives(
92108 . map ( |( review, rankings) | ( review, calc_final_ranking_per_review ( & rankings) ) )
93109 . collect :: < BTreeMap < _ , _ > > ( ) ;
94110
111+ let final_rankings_consensus_per_review = veteran_rankings
112+ . iter ( )
113+ . into_group_map_by ( |ranking| ranking. review_id ( ) )
114+ . into_iter ( )
115+ . map ( |( review, rankings) | ( review, calc_final_ranking_consensus_per_review ( & rankings) ) )
116+ . collect :: < BTreeMap < _ , _ > > ( ) ;
117+
95118 let rankings_per_vca = veteran_rankings
96119 . iter ( )
97120 . counts_by ( |ranking| ranking. vca . clone ( ) ) ;
@@ -103,7 +126,7 @@ pub fn calculate_veteran_advisors_incentives(
103126 . get ( & ranking. review_id ( ) )
104127 . unwrap ( )
105128 . is_positive ( )
106- == ranking. score ( ) . is_positive ( )
129+ == ranking. score ( ) . is_positive ( ) || * final_rankings_consensus_per_review . get ( & ranking . review_id ( ) ) . unwrap ( ) < minimum_consensus
107130 } )
108131 . counts_by ( |ranking| ranking. vca . clone ( ) ) ;
109132
@@ -156,6 +179,8 @@ mod tests {
156179 const VCA_1 : & str = "vca1" ;
157180 const VCA_2 : & str = "vca2" ;
158181 const VCA_3 : & str = "vca3" ;
182+ const SIMPLE_MINIMUM_CONSENSUS : Decimal = dec ! ( . 5 ) ;
183+ const QUALIFIED_MINIMUM_CONSENSUS : Decimal = dec ! ( . 7 ) ;
159184
160185 struct RandomIterator ;
161186 impl Iterator for RandomIterator {
@@ -231,6 +256,7 @@ mod tests {
231256 . into_iter ( )
232257 . zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
233258 . collect ( ) ,
259+ SIMPLE_MINIMUM_CONSENSUS ,
234260 ) ;
235261 assert ! ( results. get( VCA_1 ) . is_none( ) ) ;
236262 let res = results. get ( VCA_2 ) . unwrap ( ) ;
@@ -260,6 +286,7 @@ mod tests {
260286 . into_iter ( )
261287 . zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
262288 . collect ( ) ,
289+ SIMPLE_MINIMUM_CONSENSUS ,
263290 ) ;
264291 let res1 = results. get ( VCA_1 ) . unwrap ( ) ;
265292 assert_eq ! ( res1. reputation, 1 ) ;
@@ -283,12 +310,12 @@ mod tests {
283310 ( Rewards :: new ( 8 , 1 ) , Rewards :: ONE , Rewards :: ONE ) ,
284311 ( Rewards :: new ( 9 , 1 ) , Rewards :: new ( 125 , 2 ) , Rewards :: ONE ) ,
285312 ] ;
286- for ( agreement , reward_modifier, reputation_modifier) in inputs {
313+ for ( vca3_agreement , reward_modifier, reputation_modifier) in inputs {
287314 let rankings = ( 0 ..100 )
288315 . flat_map ( |i| {
289316 let vcas =
290317 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 )
318+ let ( good, filtered_out) = if Rewards :: from ( i) < vca3_agreement * Rewards :: from ( 100 )
292319 {
293320 ( 3 , 0 )
294321 } else {
@@ -297,7 +324,38 @@ mod tests {
297324 gen_dummy_rankings ( i. to_string ( ) , 0 , good, filtered_out, vcas) . into_iter ( )
298325 } )
299326 . collect :: < Vec < _ > > ( ) ;
300- let results = calculate_veteran_advisors_incentives (
327+ let results_simple_consensus = calculate_veteran_advisors_incentives (
328+ & rankings,
329+ total_rewards,
330+ 1 ..=200 ,
331+ 1 ..=200 ,
332+ THRESHOLDS
333+ . into_iter ( )
334+ . zip ( REWARDS_DISAGREEMENT_MODIFIERS . into_iter ( ) )
335+ . collect ( ) ,
336+ THRESHOLDS
337+ . into_iter ( )
338+ . zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
339+ . collect ( ) ,
340+ SIMPLE_MINIMUM_CONSENSUS ,
341+ ) ;
342+ let vca3_expected_reward_portion_simple_consensus = vca3_agreement * Rewards :: from ( 100 ) * reward_modifier;
343+ dbg ! ( vca3_expected_reward_portion_simple_consensus) ;
344+ dbg ! ( vca3_agreement, reward_modifier, reputation_modifier) ;
345+ let vca3_expected_rewards_simple_consensus = total_rewards
346+ / ( Rewards :: from ( 125 * 2 ) + vca3_expected_reward_portion_simple_consensus)
347+ * vca3_expected_reward_portion_simple_consensus;
348+ let res_vca3_simple_consensus = results_simple_consensus. get ( VCA_3 ) . unwrap ( ) ;
349+ assert_eq ! (
350+ res_vca3_simple_consensus. reputation,
351+ ( Rewards :: from( 100 ) * vca3_agreement * reputation_modifier)
352+ . to_u64( )
353+ . unwrap( )
354+ ) ;
355+ assert ! ( are_close( res_vca3_simple_consensus. rewards, vca3_expected_rewards_simple_consensus) ) ;
356+
357+
358+ let results_qualified_consensus = calculate_veteran_advisors_incentives (
301359 & rankings,
302360 total_rewards,
303361 1 ..=200 ,
@@ -310,21 +368,27 @@ mod tests {
310368 . into_iter ( )
311369 . zip ( REPUTATION_DISAGREEMENT_MODIFIERS . into_iter ( ) )
312370 . collect ( ) ,
371+ QUALIFIED_MINIMUM_CONSENSUS ,
313372 ) ;
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 ( ) ;
373+
374+ 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
375+ dbg ! ( vca3_expected_reward_portion_qualified_consensus) ;
376+ dbg ! ( vca3_agreement, reward_modifier, reputation_modifier) ;
377+
378+ let vca3_expected_rewards_qualified_consensus = total_rewards
379+ / ( Rewards :: from ( 125 * 2 ) + vca3_expected_reward_portion_qualified_consensus)
380+ * vca3_expected_reward_portion_qualified_consensus; // 1/3 of the reward
381+
382+ let res_vca3_qualified_consensus = results_qualified_consensus. get ( VCA_3 ) . unwrap ( ) ;
383+
384+
321385 assert_eq ! (
322- res . reputation,
323- ( Rewards :: from( 100 ) * agreement * reputation_modifier )
386+ res_vca3_qualified_consensus . reputation,
387+ ( Rewards :: from( 100 ) ) // all assessment are valid since consensus is low (2/3 < 0.7 )
324388 . to_u64( )
325389 . unwrap( )
326390 ) ;
327- assert ! ( are_close( res . rewards, expected_rewards ) ) ;
391+ assert ! ( are_close( res_vca3_qualified_consensus . rewards, vca3_expected_rewards_qualified_consensus ) ) ;
328392 }
329393 }
330394}
0 commit comments