use std::collections::HashMap;
use crate::types::config::ConsensusRule as ConsensusRuleConfig;
use crate::types::responses::{Decision, ModelVote, Vote};
pub trait ConsensusRule: Send + Sync {
fn name(&self) -> &str;
fn evaluate(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> Decision;
fn min_required(&self) -> usize;
fn is_consensus_achieved(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> bool;
}
#[derive(Debug, Clone, Default)]
pub struct GoldenRule;
impl ConsensusRule for GoldenRule {
fn name(&self) -> &str {
"golden"
}
fn evaluate(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> Decision {
if votes.len() < self.min_required() {
return Decision::Revise; }
let all_pass = votes
.values()
.all(|v| v.vote == Vote::Pass && v.score >= min_score);
let any_fail = votes.values().any(|v| v.vote == Vote::Fail);
if all_pass {
Decision::Pass
} else if any_fail {
Decision::Block
} else {
Decision::Revise
}
}
fn min_required(&self) -> usize {
3 }
fn is_consensus_achieved(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> bool {
if votes.len() < self.min_required() {
return false;
}
matches!(self.evaluate(votes, min_score), Decision::Pass)
}
}
#[derive(Debug, Clone, Default)]
pub struct StrongRule;
impl ConsensusRule for StrongRule {
fn name(&self) -> &str {
"strong"
}
fn evaluate(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> Decision {
if votes.len() < self.min_required() {
return Decision::Revise; }
let pass_count = votes.values().filter(|v| v.vote == Vote::Pass).count();
let fail_count = votes.values().filter(|v| v.vote == Vote::Fail).count();
let avg_score = self.calculate_average_score(votes);
if pass_count == self.min_required() && avg_score >= min_score {
return Decision::Pass;
}
if fail_count == self.min_required() {
return Decision::Block;
}
Decision::Revise
}
fn min_required(&self) -> usize {
3
}
fn is_consensus_achieved(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> bool {
if votes.len() < self.min_required() {
return false;
}
let decision = self.evaluate(votes, min_score);
matches!(decision, Decision::Pass | Decision::Block)
}
}
impl StrongRule {
fn calculate_average_score(&self, votes: &HashMap<String, ModelVote>) -> u8 {
if votes.is_empty() {
return 0;
}
let total: u32 = votes.values().map(|v| v.score as u32).sum();
(total / votes.len() as u32) as u8
}
}
#[derive(Debug, Clone, Default)]
pub struct WeakRule;
impl ConsensusRule for WeakRule {
fn name(&self) -> &str {
"weak"
}
fn evaluate(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> Decision {
if votes.is_empty() {
return Decision::Block;
}
let pass_votes: Vec<_> = votes.values().filter(|v| v.vote == Vote::Pass).collect();
let fail_count = votes.values().filter(|v| v.vote == Vote::Fail).count();
if pass_votes.len() >= 2 {
let avg_pass_score = self.calculate_average_score_of(&pass_votes);
if avg_pass_score >= min_score {
return Decision::Pass;
}
}
if fail_count >= 2 {
return Decision::Block;
}
Decision::Revise
}
fn min_required(&self) -> usize {
2 }
fn is_consensus_achieved(&self, votes: &HashMap<String, ModelVote>, min_score: u8) -> bool {
if votes.len() < self.min_required() {
return false;
}
let decision = self.evaluate(votes, min_score);
matches!(decision, Decision::Pass | Decision::Block)
}
}
impl WeakRule {
fn calculate_average_score_of(&self, votes: &[&ModelVote]) -> u8 {
if votes.is_empty() {
return 0;
}
let total: u32 = votes.iter().map(|v| v.score as u32).sum();
(total / votes.len() as u32) as u8
}
}
pub fn create_rule(config: &ConsensusRuleConfig) -> Box<dyn ConsensusRule> {
match config {
ConsensusRuleConfig::Golden => Box::new(GoldenRule),
ConsensusRuleConfig::Strong => Box::new(StrongRule),
ConsensusRuleConfig::Weak => Box::new(WeakRule),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_vote(name: &str, vote: Vote, score: u8) -> (String, ModelVote) {
(name.to_string(), ModelVote::new(name, vote, score))
}
fn create_votes(votes: Vec<(&str, Vote, u8)>) -> HashMap<String, ModelVote> {
votes
.into_iter()
.map(|(n, v, s)| create_vote(n, v, s))
.collect()
}
#[test]
fn test_golden_rule_all_pass() {
let rule = GoldenRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Pass, 90),
("Qwen", Vote::Pass, 88),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Pass);
assert!(rule.is_consensus_achieved(&votes, 70));
}
#[test]
fn test_golden_rule_one_fail() {
let rule = GoldenRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Fail, 40),
("Qwen", Vote::Pass, 88),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Block);
assert!(!rule.is_consensus_achieved(&votes, 70));
}
#[test]
fn test_golden_rule_low_score() {
let rule = GoldenRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 60),
("Gemini", Vote::Pass, 65),
("Qwen", Vote::Pass, 68),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Revise);
}
#[test]
fn test_strong_rule_all_pass() {
let rule = StrongRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Pass, 90),
("Qwen", Vote::Pass, 88),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Pass);
assert!(rule.is_consensus_achieved(&votes, 70));
}
#[test]
fn test_strong_rule_not_unanimous_revise() {
let rule = StrongRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Pass, 90),
("Qwen", Vote::Warn, 65),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Revise);
}
#[test]
fn test_strong_rule_not_unanimous_fail() {
let rule = StrongRule;
let votes = create_votes(vec![
("Codex", Vote::Fail, 30),
("Gemini", Vote::Fail, 25),
("Qwen", Vote::Pass, 85),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Revise);
}
#[test]
fn test_strong_rule_all_fail() {
let rule = StrongRule;
let votes = create_votes(vec![
("Codex", Vote::Fail, 30),
("Gemini", Vote::Fail, 25),
("Qwen", Vote::Fail, 20),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Block);
}
#[test]
fn test_weak_rule_two_pass() {
let rule = WeakRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Pass, 90),
("Qwen", Vote::Fail, 30),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Pass);
assert!(rule.is_consensus_achieved(&votes, 70));
}
#[test]
fn test_weak_rule_two_fail() {
let rule = WeakRule;
let votes = create_votes(vec![
("Codex", Vote::Fail, 30),
("Gemini", Vote::Fail, 25),
("Qwen", Vote::Pass, 85),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Block);
}
#[test]
fn test_weak_rule_no_majority() {
let rule = WeakRule;
let votes = create_votes(vec![
("Codex", Vote::Pass, 85),
("Gemini", Vote::Warn, 60),
("Qwen", Vote::Fail, 30),
]);
assert_eq!(rule.evaluate(&votes, 70), Decision::Revise);
}
#[test]
fn test_create_rule() {
let golden = create_rule(&ConsensusRuleConfig::Golden);
assert_eq!(golden.name(), "golden");
let strong = create_rule(&ConsensusRuleConfig::Strong);
assert_eq!(strong.name(), "strong");
let weak = create_rule(&ConsensusRuleConfig::Weak);
assert_eq!(weak.name(), "weak");
}
}