use serde::{Deserialize, Serialize};
use crate::model::EmbeddingModel;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SkipReason {
ContentTooLarge {
size: usize,
max: usize,
},
InvalidEncoding(String),
ContentDeleted,
PermanentApiError(String),
ManualSkip(String),
}
impl std::fmt::Display for SkipReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SkipReason::ContentTooLarge { size, max } => {
write!(f, "content too large: {size} bytes (max {max})")
}
SkipReason::InvalidEncoding(enc) => write!(f, "invalid encoding: {enc}"),
SkipReason::ContentDeleted => write!(f, "content deleted"),
SkipReason::PermanentApiError(msg) => write!(f, "permanent API error: {msg}"),
SkipReason::ManualSkip(reason) => write!(f, "manually skipped: {reason}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum MigrationState {
#[serde(alias = "Planned")]
Planned,
#[serde(alias = "InProgress")]
InProgress {
processed: usize,
total: usize,
#[serde(default)]
skipped: usize,
},
#[serde(alias = "Paused")]
Paused {
processed: usize,
total: usize,
#[serde(default)]
skipped: usize,
reason: String,
},
#[serde(alias = "Completed")]
Completed {
processed: usize,
#[serde(default)]
skipped: usize,
duration_secs: f64,
},
#[serde(alias = "Failed")]
Failed {
processed: usize,
total: usize,
#[serde(default)]
skipped: usize,
error: String,
},
#[serde(alias = "Cancelled")]
Cancelled {
processed: usize,
total: usize,
#[serde(default)]
skipped: usize,
},
}
impl MigrationState {
#[inline]
pub fn is_resumable(&self) -> bool {
matches!(
self,
MigrationState::Paused { .. } | MigrationState::Failed { .. }
)
}
#[inline]
pub fn is_terminal(&self) -> bool {
matches!(
self,
MigrationState::Completed { .. } | MigrationState::Cancelled { .. }
)
}
#[inline]
pub fn is_active(&self) -> bool {
matches!(self, MigrationState::InProgress { .. })
}
pub fn progress(&self) -> Option<f64> {
match self {
MigrationState::Planned => Some(0.0),
MigrationState::InProgress {
processed, total, ..
}
| MigrationState::Paused {
processed, total, ..
}
| MigrationState::Failed {
processed, total, ..
}
| MigrationState::Cancelled {
processed, total, ..
} => {
if *total == 0 {
Some(1.0)
} else {
Some(*processed as f64 / *total as f64)
}
}
MigrationState::Completed { .. } => Some(1.0),
}
}
pub fn processed(&self) -> usize {
match self {
MigrationState::Planned => 0,
MigrationState::InProgress { processed, .. }
| MigrationState::Paused { processed, .. }
| MigrationState::Failed { processed, .. }
| MigrationState::Cancelled { processed, .. }
| MigrationState::Completed { processed, .. } => *processed,
}
}
pub fn skipped(&self) -> usize {
match self {
MigrationState::Planned => 0,
MigrationState::InProgress { skipped, .. }
| MigrationState::Paused { skipped, .. }
| MigrationState::Failed { skipped, .. }
| MigrationState::Cancelled { skipped, .. }
| MigrationState::Completed { skipped, .. } => *skipped,
}
}
pub fn total(&self) -> usize {
match self {
MigrationState::Planned | MigrationState::Completed { .. } => 0,
MigrationState::InProgress { total, .. }
| MigrationState::Paused { total, .. }
| MigrationState::Failed { total, .. }
| MigrationState::Cancelled { total, .. } => *total,
}
}
pub fn effective_total(&self) -> usize {
self.total().saturating_sub(self.skipped())
}
pub fn effective_coverage(&self) -> f64 {
let eff = self.effective_total();
if eff == 0 {
1.0
} else {
self.processed() as f64 / eff as f64
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrationPlan {
pub id: String,
pub source_model: EmbeddingModel,
pub target_model: EmbeddingModel,
pub total_embeddings: usize,
pub batch_size: usize,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrationProgress {
pub migration_id: String,
pub state: MigrationState,
#[serde(default)]
pub skipped: usize,
#[serde(default)]
pub effective_total: usize,
#[serde(default)]
pub effective_coverage: f64,
pub throughput: f64,
pub eta_secs: Option<f64>,
pub error_count: usize,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum MigrationError {
InvalidTransition {
from: String,
to: String,
},
}
impl std::fmt::Display for MigrationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MigrationError::InvalidTransition { from, to } => {
write!(f, "invalid migration transition from {from} to {to}")
}
}
}
}
impl std::error::Error for MigrationError {}