//! Model pricing management — CRUD. //! //! All functions take `&DatabaseConnection` instead of `&AppService`. use chrono::Utc; use db::database::AppDatabase; use models::agents::PricingCurrency; use models::agents::model_pricing; use sea_orm::*; use uuid::Uuid; use crate::error::AgentError; #[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)] pub struct CreateModelPricingRequest { pub model_version_id: Uuid, pub input_price_per_1k_tokens: String, pub output_price_per_1k_tokens: String, pub currency: String, pub effective_from: chrono::DateTime, } #[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)] pub struct UpdateModelPricingRequest { pub input_price_per_1k_tokens: Option, pub output_price_per_1k_tokens: Option, pub currency: Option, pub effective_from: Option>, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct ModelPricingResponse { pub id: i64, pub model_version_id: Uuid, pub input_price_per_1k_tokens: String, pub output_price_per_1k_tokens: String, pub currency: String, pub effective_from: chrono::DateTime, } impl From for ModelPricingResponse { fn from(p: model_pricing::Model) -> Self { Self { id: p.id, model_version_id: p.model_version_id, input_price_per_1k_tokens: p.input_price_per_1k_tokens, output_price_per_1k_tokens: p.output_price_per_1k_tokens, currency: p.currency, effective_from: p.effective_from, } } } /// List pricing records for a model version. pub async fn list_pricing( db: &AppDatabase, model_version_id: Uuid, ) -> Result, AgentError> { let records = model_pricing::Entity::find() .filter(model_pricing::Column::ModelVersionId.eq(model_version_id)) .order_by_desc(model_pricing::Column::EffectiveFrom) .all(db) .await?; Ok(records.into_iter().map(ModelPricingResponse::from).collect()) } /// Get a single pricing record by ID. pub async fn get_pricing(db: &AppDatabase, id: i64) -> Result { let record = model_pricing::Entity::find_by_id(id) .one(db) .await? .ok_or_else(|| AgentError::NotFound(format!("Pricing record not found: {}", id)))?; Ok(ModelPricingResponse::from(record)) } /// Create a new pricing record. pub async fn create_pricing( db: &AppDatabase, request: CreateModelPricingRequest, ) -> Result { let _ = request .currency .parse::() .map_err(|_| AgentError::InvalidInput { field: "currency".into(), reason: "Invalid pricing currency".into(), })?; let active = model_pricing::ActiveModel { model_version_id: Set(request.model_version_id), input_price_per_1k_tokens: Set(request.input_price_per_1k_tokens), output_price_per_1k_tokens: Set(request.output_price_per_1k_tokens), currency: Set(request.currency), effective_from: Set(request.effective_from), ..Default::default() }; let record = active.insert(db).await?; Ok(ModelPricingResponse::from(record)) } /// Update a pricing record. pub async fn update_pricing( db: &AppDatabase, id: i64, request: UpdateModelPricingRequest, ) -> Result { let record = model_pricing::Entity::find_by_id(id) .one(db) .await? .ok_or_else(|| AgentError::NotFound(format!("Pricing record not found: {}", id)))?; let mut active: model_pricing::ActiveModel = record.into(); if let Some(v) = request.input_price_per_1k_tokens { active.input_price_per_1k_tokens = Set(v); } if let Some(v) = request.output_price_per_1k_tokens { active.output_price_per_1k_tokens = Set(v); } if let Some(v) = request.currency { let _ = v .parse::() .map_err(|_| AgentError::InvalidInput { field: "currency".into(), reason: "Invalid pricing currency".into(), })?; active.currency = Set(v); } if let Some(v) = request.effective_from { active.effective_from = Set(v); } let record = active.update(db).await?; Ok(ModelPricingResponse::from(record)) } /// Delete a pricing record. pub async fn delete_pricing(db: &AppDatabase, id: i64) -> Result<(), AgentError> { model_pricing::Entity::delete_by_id(id).exec(db).await?; Ok(()) }