use pagesift_stat::beta::Posterior;
use pagesift_stat::decide::{Cost, ShadowPrices, Why, choose};
use pagesift_stat::rng::Rng;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Plan {
Http,
EmbeddedJson,
HttpVerifyBrowser,
Lightpanda,
FullBrowser,
}
impl Plan {
pub fn as_str(self) -> &'static str {
match self {
Self::Http => "http",
Self::EmbeddedJson => "embedded-json",
Self::HttpVerifyBrowser => "http+verify(browser)",
Self::Lightpanda => "lightpanda",
Self::FullBrowser => "full-browser",
}
}
pub fn uses_browser(self) -> bool {
matches!(
self,
Self::HttpVerifyBrowser | Self::Lightpanda | Self::FullBrowser
)
}
}
#[derive(Debug, Clone)]
pub struct Option_ {
pub plan: Plan,
pub post: Posterior,
pub value: f64,
pub cost: Cost,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Constraints {
pub http_only: bool,
pub browser_only: bool,
pub barrier: bool,
}
pub fn choose_plan(
options: &[Option_],
limits: Constraints,
prices: &ShadowPrices,
rng: &mut Rng,
) -> Option<Plan> {
let allowed: Vec<&Option_> = options
.iter()
.filter(|o| {
let browser = o.plan.uses_browser();
!(limits.http_only && browser)
&& !(limits.browser_only && !browser)
&& !(limits.barrier && browser)
})
.collect();
let arms: Vec<_> = allowed.iter().map(|o| (o.post, o.value, o.cost)).collect();
choose(&arms, prices, rng).map(|i| allowed[i].plan)
}
pub fn should_verify(
quality: f64,
gain: &Posterior,
value: f64,
cost: &Cost,
prices: &ShadowPrices,
rng: &mut Rng,
) -> (bool, Why) {
pagesift_stat::decide::decide(
gain,
(1.0 - quality.clamp(0.0, 1.0)) * value,
cost,
prices,
rng,
)
}
#[cfg(test)]
mod tests {
use super::*;
use pagesift_stat::beta::{BetaCounts, Prior};
use pagesift_stat::decide::Resource;
fn post(success: u32, failure: u32) -> Posterior {
let mut c = BetaCounts::default();
c.observe_weighted(success.into(), failure.into());
c.posterior(Prior::UNIFORM)
}
#[test]
fn respects_explicit_engine_and_barriers() {
let opts = [
Option_ {
plan: Plan::Http,
post: post(1, 9),
value: 1.0,
cost: Cost::default(),
},
Option_ {
plan: Plan::FullBrowser,
post: post(9, 1),
value: 1.0,
cost: Cost::default(),
},
];
let mut rng = Rng::seed(7);
let prices = ShadowPrices::default();
let http_only = Constraints {
http_only: true,
..Default::default()
};
assert_eq!(
choose_plan(&opts, http_only, &prices, &mut rng),
Some(Plan::Http)
);
let barrier = Constraints {
barrier: true,
..Default::default()
};
assert_eq!(
choose_plan(&opts, barrier, &prices, &mut rng),
Some(Plan::Http)
);
let both = Constraints {
http_only: true,
browser_only: true,
..Default::default()
};
assert_eq!(choose_plan(&opts, both, &prices, &mut rng), None);
}
#[test]
fn verification_stops_when_quality_is_high_or_the_browser_is_expensive() {
let gain = post(50, 50);
let cost = Cost::default().with(Resource::Browser, 1.0);
let mut rng = Rng::seed(3);
let idle = ShadowPrices::default();
assert!(should_verify(0.2, &gain, 1.0, &cost, &idle, &mut rng).0);
assert!(
!should_verify(
1.0,
&gain,
1.0,
&cost,
&idle.with(Resource::Browser, 0.01),
&mut rng
)
.0
);
let busy = ShadowPrices::default().with(Resource::Browser, f64::INFINITY);
assert!(!should_verify(0.2, &gain, 1.0, &cost, &busy, &mut rng).0);
}
}