claudy 0.6.0

Modern multi-provider launcher for Claude CLI
use crate::domain::analytics::*;

pub trait AnalyticsStore: Send + Sync {
    fn initialize_schema(&self) -> anyhow::Result<()>;

    fn upsert_project(
        &self,
        encoded_dir: &str,
        display_name: &str,
        resolved_path: Option<&str>,
    ) -> anyhow::Result<i64>;
    fn get_project_by_encoded_dir(
        &self,
        encoded_dir: &str,
    ) -> anyhow::Result<Option<ProjectRecord>>;
    fn list_projects(&self) -> anyhow::Result<Vec<ProjectRecord>>;

    fn upsert_session(&self, session: &NewSession) -> anyhow::Result<i64>;
    fn update_session_completion(
        &self,
        session_id: i64,
        ended_at: &str,
        total_turns: i32,
        total_cost_usd: f64,
        total_duration_ms: i64,
    ) -> anyhow::Result<()>;
    fn get_sessions(
        &self,
        limit: u32,
        days: Option<u32>,
        project_id: Option<i64>,
    ) -> anyhow::Result<Vec<SessionRecord>>;
    fn get_session_by_uuid(&self, uuid: &str) -> anyhow::Result<Option<SessionRecord>>;

    fn insert_turn(&self, turn: &NewTurn) -> anyhow::Result<Option<i64>>;
    fn get_turns_by_session(&self, session_id: i64) -> anyhow::Result<Vec<TurnRecord>>;
    /// Number of turns already stored for a session (for incremental resume).
    fn get_turn_count(&self, session_id: i64) -> anyhow::Result<i64>;
    /// Set a turn's duration, keyed by (session, turn_number) rather than row id
    /// so a full re-ingest can backfill turns that already exist (their insert
    /// returns no id).
    fn update_turn_duration(
        &self,
        session_id: i64,
        turn_number: i32,
        duration_ms: i64,
    ) -> anyhow::Result<()>;

    fn insert_token_usage(&self, usage: &NewTokenUsage) -> anyhow::Result<()>;

    fn insert_tool_call(&self, call: &NewToolCall) -> anyhow::Result<()>;
    fn update_tool_call_result(
        &self,
        tool_use_id: &str,
        is_error: bool,
        result_summary: Option<&str>,
    ) -> anyhow::Result<()>;
    fn get_tool_calls_by_turn(&self, turn_id: i64) -> anyhow::Result<Vec<ToolCallRecord>>;

    /// Write a session's outcome counters into `session_outcomes`.
    ///
    /// Called once at the end of ingesting a session file. `mode` says whether
    /// the counters describe the whole session ([`OutcomeWriteMode::Replace`]) or only
    /// the tail a resumed parse read ([`OutcomeWriteMode::Accumulate`]).
    fn upsert_session_outcome(
        &self,
        outcomes: &NewSessionOutcome,
        mode: OutcomeWriteMode,
    ) -> anyhow::Result<()>;

    fn insert_channel_metric(&self, record: &ChannelMetricRecord) -> anyhow::Result<()>;

    fn get_checkpoint(&self, file_path: &str) -> anyhow::Result<Option<IngestionCheckpoint>>;
    fn upsert_checkpoint(
        &self,
        file_path: &str,
        file_modified: &str,
        byte_offset: i64,
        line_count: i64,
    ) -> anyhow::Result<()>;

    fn clear_recommendations(&self) -> anyhow::Result<()>;
    fn insert_recommendation(&self, rec: &Recommendation) -> anyhow::Result<()>;
    fn get_recommendations(&self) -> anyhow::Result<Vec<Recommendation>>;

    fn aggregate_token_trends(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<Vec<TokenTrendPoint>>;
    fn aggregate_tool_distribution(
        &self,
        days: Option<u32>,
        project_id: Option<i64>,
    ) -> anyhow::Result<Vec<ToolDistribution>>;
    fn aggregate_dashboard_stats(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<DashboardStats>;
    fn aggregate_cost_metrics(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<CostMetrics>;

    fn aggregate_prompt_efficiency(&self, limit: u32) -> anyhow::Result<Vec<PromptEfficiency>>;
    fn detect_tool_patterns(&self, min_frequency: u32) -> anyhow::Result<Vec<ToolPattern>>;
    fn compare_model_performance(&self) -> anyhow::Result<Vec<ModelPerformance>>;
    fn aggregate_session_comparisons(&self, limit: u32) -> anyhow::Result<Vec<SessionComparison>>;

    fn recalculate_costs(&self) -> anyhow::Result<u64>;

    /// Latest turn start and per-source last-seen, for freshness reporting.
    fn ingestion_freshness(&self) -> anyhow::Result<FreshnessReport>;

    /// Best-effort backfill of NULL `turns.model` from the session model (R4).
    fn backfill_null_turn_models(&self, session_id: i64, model: &str) -> anyhow::Result<u64>;
}

pub trait PricingStore: Send + Sync {
    fn upsert_model_pricing(&self, pricing: &ModelPricing) -> anyhow::Result<()>;
    fn batch_upsert_model_pricing(&self, pricings: &[ModelPricing]) -> anyhow::Result<()>;
    fn get_model_pricing(&self, model_id: &str) -> anyhow::Result<Option<ModelPricing>>;
    fn list_model_pricing(&self) -> anyhow::Result<Vec<ModelPricing>>;
}

pub trait AnalysisEngine: Send + Sync {
    fn compute_token_trends(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<Vec<TokenTrendPoint>>;
    fn compute_tool_distribution(
        &self,
        days: Option<u32>,
        project_id: Option<i64>,
    ) -> anyhow::Result<Vec<ToolDistribution>>;
    fn compute_cost_metrics(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<CostMetrics>;
    fn compute_dashboard_stats(
        &self,
        days: u32,
        project_id: Option<i64>,
    ) -> anyhow::Result<DashboardStats>;
    fn compute_prompt_efficiency(&self, limit: u32) -> anyhow::Result<Vec<PromptEfficiency>>;
    fn detect_tool_patterns(&self, min_frequency: u32) -> anyhow::Result<Vec<ToolPattern>>;
    fn compare_model_performance(&self) -> anyhow::Result<Vec<ModelPerformance>>;
    fn get_session_comparisons(&self, limit: u32) -> anyhow::Result<Vec<SessionComparison>>;
}