use super::UiState;
use crate::spend::Prices;
const PRICES: &str = "prices";
impl UiState {
pub fn prices(&self) -> Prices {
match self.world.root.get(PRICES) {
Some(value) => Prices::from_json(value),
None => Prices::default(),
}
}
pub fn set_prices(&mut self, prices: &Prices) {
if prices.is_empty() {
self.world.root.remove(PRICES);
} else {
self.world.root.insert(PRICES.to_owned(), prices.to_json());
}
self.world.save();
}
}
#[cfg(test)]
mod tests {
use crate::budgets::BudgetSpend;
use crate::ui_state::UiState;
use tempfile::tempdir;
fn opened(doc: &str) -> UiState {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
std::fs::write(&path, doc).unwrap();
UiState::open(path)
}
#[test]
fn absent_table_is_empty() {
assert!(opened(r#"{"v":1}"#).prices().is_empty());
}
#[test]
fn wrong_shape_is_empty() {
assert!(opened(r#"{"v":1,"prices":"nope"}"#).prices().is_empty());
}
#[test]
fn the_old_model_keyed_shape_reads_empty() {
assert!(
opened(r#"{"v":1,"prices":{"opus":{"input":15,"output":75}}}"#)
.prices()
.is_empty()
);
}
#[test]
fn a_row_written_is_read_back_and_the_last_delete_removes_the_key() {
let dir = tempdir().unwrap();
let path = dir.path().join("ui.json");
let mut ui = UiState::open(path.clone());
let mut table = crate::spend::Prices::default();
table.set(
"anthropic",
"opus",
Some(crate::spend::Price {
input: 15_000_000,
..Default::default()
}),
);
ui.set_prices(&table);
let text = std::fs::read_to_string(&path).unwrap();
assert!(text.contains("\"input\": 15"), "{text}");
assert_eq!(UiState::open(path.clone()).prices(), table);
table.set("anthropic", "opus", None);
ui.set_prices(&table);
let text = std::fs::read_to_string(&path).unwrap();
assert!(!text.contains("prices"), "{text}");
assert!(UiState::open(path).prices().is_empty());
}
#[test]
fn reads_rates_for_a_model() {
let prices = opened(
r#"{"v":1,"prices":{"anthropic":{"opus":{"input":15,"output":75,
"cache_read":1.5,"cache_write":18.75}}}}"#,
)
.prices();
let price = prices.of(Some("anthropic"), Some("opus")).unwrap();
assert_eq!(price.input, 15_000_000);
assert_eq!(price.cache_write, 18_750_000);
assert_eq!(
price.cost(BudgetSpend {
input_tokens: 1_000_000,
..BudgetSpend::default()
}),
15_000_000
);
}
}