-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
884 additions
and
150 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use std::collections::HashMap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
/// Bandit evaluation event that needs to be logged to analytics storage. | ||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditEvent { | ||
pub flag_key: String, | ||
pub bandit_key: String, | ||
pub subject: String, | ||
pub action: String, | ||
pub action_probability: f64, | ||
pub optimality_gap: f64, | ||
pub model_version: String, | ||
pub timestamp: String, | ||
pub subject_numeric_attributes: HashMap<String, f64>, | ||
pub subject_categorical_attributes: HashMap<String, String>, | ||
pub action_numeric_attributes: HashMap<String, f64>, | ||
pub action_categorical_attributes: HashMap<String, String>, | ||
pub meta_data: HashMap<String, String>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
mod eval; | ||
mod event; | ||
mod models; | ||
|
||
pub use eval::BanditResult; | ||
pub use event::BanditEvent; | ||
pub use models::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#![allow(missing_docs)] | ||
|
||
use std::collections::HashMap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
type Timestamp = chrono::DateTime<chrono::Utc>; | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditResponse { | ||
pub bandits: HashMap<String, BanditConfiguration>, | ||
pub updated_at: Timestamp, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditConfiguration { | ||
pub bandit_key: String, | ||
pub model_name: String, | ||
pub model_version: String, | ||
pub model_data: BanditModelData, | ||
pub updated_at: Timestamp, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditModelData { | ||
pub gamma: f64, | ||
pub default_action_score: f64, | ||
pub action_probability_floor: f64, | ||
pub coefficients: HashMap<String, BanditCoefficients>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditCoefficients { | ||
pub action_key: String, | ||
pub intercept: f64, | ||
pub subject_numeric_coefficients: Vec<BanditNumericAttributeCoefficient>, | ||
pub subject_categorical_coefficients: Vec<BanditCategoricalAttributeCoefficient>, | ||
pub action_numeric_coefficients: Vec<BanditNumericAttributeCoefficient>, | ||
pub action_categorical_coefficients: Vec<BanditCategoricalAttributeCoefficient>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditNumericAttributeCoefficient { | ||
pub attribute_key: String, | ||
pub coefficient: f64, | ||
pub missing_value_coefficient: f64, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BanditCategoricalAttributeCoefficient { | ||
pub attribute_key: String, | ||
pub value_coefficients: HashMap<String, f64>, | ||
pub missing_value_coefficient: f64, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,66 @@ | ||
use std::sync::Arc; | ||
use std::collections::HashMap; | ||
|
||
use crate::ufc::UniversalFlagConfig; | ||
use crate::{ | ||
bandits::{BanditConfiguration, BanditResponse}, | ||
ufc::{BanditVariation, UniversalFlagConfig}, | ||
}; | ||
|
||
/// Remote configuration for the eppo client. It's a central piece that defines client behavior. | ||
#[derive(Default, Clone)] | ||
pub struct Configuration { | ||
/// UFC configuration. | ||
pub ufc: Option<Arc<UniversalFlagConfig>>, | ||
/// Flags configuration. | ||
pub flags: Option<UniversalFlagConfig>, | ||
/// Bandits configuration. | ||
pub bandits: Option<BanditResponse>, | ||
/// Mapping from flag key to flag variation value to bandit variation. | ||
pub flag_to_bandit_associations: | ||
HashMap</* flag_key: */ String, HashMap</* variation_key: */ String, BanditVariation>>, | ||
} | ||
|
||
impl Configuration { | ||
/// Create a new configuration from server responses. | ||
pub fn new( | ||
config: Option<UniversalFlagConfig>, | ||
bandits: Option<BanditResponse>, | ||
) -> Configuration { | ||
let flag_to_bandit_associations = config | ||
.as_ref() | ||
.map(get_flag_to_bandit_associations) | ||
.unwrap_or_default(); | ||
Configuration { | ||
flags: config, | ||
bandits, | ||
flag_to_bandit_associations, | ||
} | ||
} | ||
|
||
/// Return a bandit variant for the specified flag key and string flag variation. | ||
pub(crate) fn get_bandit_key<'a>(&'a self, flag_key: &str, variation: &str) -> Option<&'a str> { | ||
self.flag_to_bandit_associations | ||
.get(flag_key) | ||
.and_then(|x| x.get(variation)) | ||
.map(|variation| variation.key.as_str()) | ||
} | ||
|
||
/// Return bandit configuration for the given key. | ||
/// | ||
/// Returns `None` if bandits are missing for bandit does not exist. | ||
pub(crate) fn get_bandit<'a>(&'a self, bandit_key: &str) -> Option<&'a BanditConfiguration> { | ||
self.bandits.as_ref()?.bandits.get(bandit_key) | ||
} | ||
} | ||
|
||
fn get_flag_to_bandit_associations( | ||
config: &UniversalFlagConfig, | ||
) -> HashMap<String, HashMap<String, BanditVariation>> { | ||
config | ||
.bandits | ||
.iter() | ||
.flat_map(|(_, bandits)| bandits.iter()) | ||
.fold(HashMap::new(), |mut acc, variation| { | ||
acc.entry(variation.flag_key.clone()) | ||
.or_default() | ||
.insert(variation.variation_value.clone(), variation.clone()); | ||
acc | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use std::collections::HashMap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::{AttributeValue, Attributes}; | ||
|
||
/// `ContextAttributes` are subject or action attributes split by their semantics. | ||
#[derive(Debug, Clone, Default, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ContextAttributes { | ||
/// Numeric attributes are quantitative (e.g., real numbers) and define a scale. | ||
/// | ||
/// Not all numbers are numeric attributes. If a number is used to represent an enumeration or | ||
/// on/off values, it is a categorical attribute. | ||
pub numeric: HashMap<String, f64>, | ||
/// Categorical attributes are attributes that have a finite set of values that are not directly | ||
/// comparable (i.e., enumeration). | ||
pub categorical: HashMap<String, String>, | ||
} | ||
|
||
impl From<Attributes> for ContextAttributes { | ||
fn from(value: Attributes) -> Self { | ||
ContextAttributes::from_iter(value) | ||
} | ||
} | ||
|
||
impl<K, V> FromIterator<(K, V)> for ContextAttributes | ||
where | ||
K: ToOwned<Owned = String>, | ||
V: ToOwned<Owned = AttributeValue>, | ||
{ | ||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self { | ||
iter.into_iter() | ||
.fold(ContextAttributes::default(), |mut acc, (key, value)| { | ||
match value.to_owned() { | ||
AttributeValue::String(value) => { | ||
acc.categorical.insert(key.to_owned(), value); | ||
} | ||
AttributeValue::Number(value) => { | ||
acc.numeric.insert(key.to_owned(), value); | ||
} | ||
AttributeValue::Boolean(value) => { | ||
// TBD: shall we ignore boolean attributes instead? | ||
// | ||
// One argument for including it here is that this basically guarantees that | ||
// assignment evaluation inside bandit evaluation works the same way as if | ||
// `get_assignment()` was called with generic `Attributes`. | ||
// | ||
// We can go a step further and remove `AttributeValue::Boolean` altogether, | ||
// forcing it to be converted to a string before any evaluation. | ||
acc.categorical.insert(key.to_owned(), value.to_string()); | ||
} | ||
AttributeValue::Null => { | ||
// Nulls are missing values and are ignored. | ||
} | ||
} | ||
acc | ||
}) | ||
} | ||
} | ||
|
||
impl ContextAttributes { | ||
/// Convert contextual attributes to generic `Attributes`. | ||
pub fn to_generic_attributes(&self) -> Attributes { | ||
let mut result = HashMap::with_capacity(self.numeric.len() + self.categorical.capacity()); | ||
for (key, value) in self.numeric.iter() { | ||
result.insert(key.clone(), AttributeValue::Number(*value)); | ||
} | ||
for (key, value) in self.categorical.iter() { | ||
result.insert(key.clone(), AttributeValue::String(value.clone())); | ||
} | ||
result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.