use crate::resources::Resource;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HolacracyConfig {
pub assignment_mode: TaskAssignmentMode,
pub bidding: BiddingConfig,
pub max_tasks_per_member: usize,
pub max_roles_per_member: usize,
pub critical_priority_boost: f32,
pub skill_match_weight: f32,
pub workload_weight: f32,
pub interest_weight: f32,
pub enable_role_switching: bool,
pub role_switch_cooldown: u64,
pub min_skill_level_for_bid: f32,
pub max_circle_depth: usize,
}
impl HolacracyConfig {
pub fn new() -> Result<Self, String> {
let config = Self::default();
config.validate()?;
Ok(config)
}
pub fn validate(&self) -> Result<(), String> {
let total_weight = self.skill_match_weight + self.workload_weight + self.interest_weight;
if (total_weight - 1.0).abs() > 0.01 {
return Err(format!(
"Bid scoring weights must sum to 1.0, got {}",
total_weight
));
}
if self.skill_match_weight < 0.0
|| self.skill_match_weight > 1.0
|| self.workload_weight < 0.0
|| self.workload_weight > 1.0
|| self.interest_weight < 0.0
|| self.interest_weight > 1.0
{
return Err("All bid weights must be in range 0.0-1.0".to_string());
}
if self.min_skill_level_for_bid < 0.0 || self.min_skill_level_for_bid > 1.0 {
return Err("min_skill_level_for_bid must be in range 0.0-1.0".to_string());
}
self.bidding.validate()?;
if self.max_tasks_per_member == 0 {
return Err("max_tasks_per_member must be at least 1".to_string());
}
if self.max_roles_per_member == 0 {
return Err("max_roles_per_member must be at least 1".to_string());
}
if self.max_circle_depth == 0 {
return Err("max_circle_depth must be at least 1".to_string());
}
Ok(())
}
pub fn with_assignment_mode(mut self, mode: TaskAssignmentMode) -> Self {
self.assignment_mode = mode;
self
}
pub fn with_bidding_config(mut self, config: BiddingConfig) -> Self {
self.bidding = config;
self
}
pub fn with_max_tasks(mut self, max: usize) -> Self {
self.max_tasks_per_member = max;
self
}
pub fn with_max_roles(mut self, max: usize) -> Self {
self.max_roles_per_member = max;
self
}
pub fn with_skill_weights(mut self, skill: f32, workload: f32, interest: f32) -> Self {
self.skill_match_weight = skill;
self.workload_weight = workload;
self.interest_weight = interest;
self
}
pub fn with_role_switching(mut self, enabled: bool) -> Self {
self.enable_role_switching = enabled;
self
}
pub fn with_role_switch_cooldown(mut self, cooldown: u64) -> Self {
self.role_switch_cooldown = cooldown;
self
}
pub fn with_min_skill_level(mut self, level: f32) -> Self {
self.min_skill_level_for_bid = level;
self
}
pub fn with_max_circle_depth(mut self, depth: usize) -> Self {
self.max_circle_depth = depth;
self
}
}
impl Default for HolacracyConfig {
fn default() -> Self {
Self {
assignment_mode: TaskAssignmentMode::SemiAutonomous,
bidding: BiddingConfig::default(),
max_tasks_per_member: 5,
max_roles_per_member: 3,
critical_priority_boost: 2.0,
skill_match_weight: 0.5,
workload_weight: 0.3,
interest_weight: 0.2,
enable_role_switching: true,
role_switch_cooldown: 5,
min_skill_level_for_bid: 0.3,
max_circle_depth: 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum TaskAssignmentMode {
FullyAutonomous,
#[default]
SemiAutonomous,
Manual,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BiddingConfig {
pub bidding_duration: u64,
pub min_bids_required: usize,
pub allow_bid_retraction: bool,
pub retraction_penalty: f32,
pub allow_overbidding: bool,
pub overbid_penalty_multiplier: f32,
}
impl BiddingConfig {
pub fn validate(&self) -> Result<(), String> {
if self.bidding_duration == 0 {
return Err("bidding_duration must be at least 1".to_string());
}
if self.retraction_penalty < 0.0 || self.retraction_penalty > 1.0 {
return Err("retraction_penalty must be in range 0.0-1.0".to_string());
}
if self.overbid_penalty_multiplier < 0.0 {
return Err("overbid_penalty_multiplier must be non-negative".to_string());
}
Ok(())
}
pub fn with_duration(mut self, duration: u64) -> Self {
self.bidding_duration = duration;
self
}
pub fn with_min_bids(mut self, min: usize) -> Self {
self.min_bids_required = min;
self
}
pub fn with_retraction(mut self, allowed: bool, penalty: f32) -> Self {
self.allow_bid_retraction = allowed;
self.retraction_penalty = penalty;
self
}
pub fn with_overbidding(mut self, allowed: bool, penalty: f32) -> Self {
self.allow_overbidding = allowed;
self.overbid_penalty_multiplier = penalty;
self
}
}
impl Default for BiddingConfig {
fn default() -> Self {
Self {
bidding_duration: 3,
min_bids_required: 1,
allow_bid_retraction: true,
retraction_penalty: 0.1,
allow_overbidding: false,
overbid_penalty_multiplier: 0.5,
}
}
}
impl Resource for HolacracyConfig {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = HolacracyConfig::default();
assert_eq!(config.assignment_mode, TaskAssignmentMode::SemiAutonomous);
assert_eq!(config.max_tasks_per_member, 5);
assert_eq!(config.max_roles_per_member, 3);
}
#[test]
fn test_config_validation_success() {
let config = HolacracyConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validation_weights_sum() {
let config = HolacracyConfig {
skill_match_weight: 0.5,
workload_weight: 0.3,
interest_weight: 0.1, ..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_weight_range() {
let config = HolacracyConfig {
skill_match_weight: 1.5, ..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_min_skill_level() {
let config = HolacracyConfig {
min_skill_level_for_bid: 1.5, ..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_builder() {
let config = HolacracyConfig::default()
.with_assignment_mode(TaskAssignmentMode::FullyAutonomous)
.with_max_tasks(10)
.with_max_roles(5)
.with_role_switching(false);
assert_eq!(config.assignment_mode, TaskAssignmentMode::FullyAutonomous);
assert_eq!(config.max_tasks_per_member, 10);
assert_eq!(config.max_roles_per_member, 5);
assert!(!config.enable_role_switching);
}
#[test]
fn test_config_builder_with_skill_weights() {
let config = HolacracyConfig::default().with_skill_weights(0.6, 0.3, 0.1);
assert_eq!(config.skill_match_weight, 0.6);
assert_eq!(config.workload_weight, 0.3);
assert_eq!(config.interest_weight, 0.1);
assert!(config.validate().is_ok());
}
#[test]
fn test_bidding_config_default() {
let config = BiddingConfig::default();
assert_eq!(config.bidding_duration, 3);
assert_eq!(config.min_bids_required, 1);
assert!(config.allow_bid_retraction);
}
#[test]
fn test_bidding_config_validation_success() {
let config = BiddingConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_bidding_config_validation_duration() {
let config = BiddingConfig {
bidding_duration: 0,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_bidding_config_validation_penalty_range() {
let config = BiddingConfig {
retraction_penalty: 1.5,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_bidding_config_builder() {
let config = BiddingConfig::default()
.with_duration(5)
.with_min_bids(2)
.with_retraction(false, 0.0)
.with_overbidding(true, 0.3);
assert_eq!(config.bidding_duration, 5);
assert_eq!(config.min_bids_required, 2);
assert!(!config.allow_bid_retraction);
assert!(config.allow_overbidding);
assert_eq!(config.overbid_penalty_multiplier, 0.3);
}
#[test]
fn test_assignment_mode_default() {
assert_eq!(
TaskAssignmentMode::default(),
TaskAssignmentMode::SemiAutonomous
);
}
#[test]
fn test_config_new() {
let result = HolacracyConfig::new();
assert!(result.is_ok());
}
#[test]
fn test_config_validation_max_tasks() {
let config = HolacracyConfig {
max_tasks_per_member: 0,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_max_roles() {
let config = HolacracyConfig {
max_roles_per_member: 0,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_max_circle_depth() {
let config = HolacracyConfig {
max_circle_depth: 0,
..Default::default()
};
assert!(config.validate().is_err());
}
}