// file: kb_demo_app/src/demo_pipeline2.rs

//! Tauri commands for the focused pipeline demo window.
//!
//! This demo is intentionally narrower than `Demo Pipeline`:
//! - read the local catalog of tokens / pools / pairs,
//! - trigger targeted backfills from the chain,
//! - load candles for one selected pair and timeframe.

use tauri::Manager;
use ts_rs::TS;

/// Request payload for program instruction discriminator summaries.
#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2ProgramInstructionDiscriminatorSummaryRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2ProgramInstructionDiscriminatorSummaryRequest {
    /// Program id to inspect.
    pub program_id: std::string::String,
    /// Maximum number of instruction rows to inspect before grouping.
    pub limit: u32,
}

/// Response payload for program instruction discriminator summaries.
#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload {
    /// Pretty JSON summary rows.
    pub summaries_json: std::string::String,
}

/// Request payload for protocol candidate summary diagnostics.
#[derive(Clone, Debug, serde::Deserialize, ts_rs::TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2ProtocolCandidateSummaryRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2ProtocolCandidateSummaryRequest {
    /// Maximum number of summary rows to return.
    pub limit: u32,
}

/// Response payload for protocol candidate summary diagnostics.
#[derive(Clone, Debug, serde::Serialize, ts_rs::TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2ProtocolCandidateSummaryPayload.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2ProtocolCandidateSummaryPayload {
    /// Pretty JSON summary rows.
    pub summaries_json: std::string::String,
}

/// Local diagnostics payload returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalDiagnosticsPayload.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalDiagnosticsPayload {
    /// Open database URL.
    pub database_url: std::string::String,
    /// Pretty JSON diagnostics summary.
    pub summary_json: std::string::String,
    /// Structured diagnostics summary.
    pub summary: DemoPipeline2LocalPipelineDiagnosticSummary,
}

/// Request payload for local pipeline validation.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalValidationRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalValidationRequest {
    /// Stable validation profile code to execute.
    pub profile_code: std::string::String,
}

/// Local validation payload returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalValidationPayload.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalValidationPayload {
    /// Open database URL.
    pub database_url: std::string::String,
    /// Pretty JSON diagnostics summary used by validation.
    pub summary_json: std::string::String,
    /// Pretty JSON validation run.
    pub validation_json: std::string::String,
    /// Structured validation run.
    pub run: DemoPipeline2LocalPipelineValidationRun,
}

/// Local pipeline validation run for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPipelineValidationRun.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPipelineValidationRun {
    /// Stable validation profile code used to produce the run.
    pub validation_profile_code: std::string::String,
    /// Whether the validation passed without blocking issues.
    pub validation_passed: bool,
    /// Number of blocking issues found by validation.
    #[ts(type = "number")]
    pub blocking_issue_count: i64,
    /// Number of non-blocking warnings found by validation.
    #[ts(type = "number")]
    pub warning_count: i64,
    /// Diagnostics summary that was validated.
    pub summary: DemoPipeline2LocalPipelineDiagnosticSummary,
    /// Detailed validation report produced from the diagnostics summary.
    pub report: DemoPipeline2LocalPipelineValidationReport,
}

/// Local pipeline validation report for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPipelineValidationReport.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPipelineValidationReport {
    /// Stable validation profile code used to produce the report.
    pub validation_profile_code: std::string::String,
    /// Whether the validation passed without blocking issues.
    pub validation_passed: bool,
    /// Number of blocking issues found by validation.
    #[ts(type = "number")]
    pub blocking_issue_count: i64,
    /// Number of non-blocking warnings found by validation.
    #[ts(type = "number")]
    pub warning_count: i64,
    /// Expected DEX codes used by the validation run.
    pub expected_dex_codes: std::vec::Vec<std::string::String>,
    /// Observed DEX codes found in diagnostics.
    pub observed_dex_codes: std::vec::Vec<std::string::String>,
    /// Total decoded useful non-trade events.
    #[ts(type = "number")]
    pub decoded_non_trade_useful_event_count: i64,
    /// Total decoded swap-like events that are intentionally non-actionable.
    #[ts(type = "number")]
    pub decoded_non_actionable_trade_event_count: i64,
    /// Total decoded events with unknown classification.
    #[ts(type = "number")]
    pub decoded_unknown_event_count: i64,
    /// Total persisted liquidity events.
    #[ts(type = "number")]
    pub liquidity_event_count: i64,
    /// Total persisted pool lifecycle events.
    #[ts(type = "number")]
    pub pool_lifecycle_event_count: i64,
    /// Total persisted fee events.
    #[ts(type = "number")]
    pub fee_event_count: i64,
    /// Total persisted reward events.
    #[ts(type = "number")]
    pub reward_event_count: i64,
    /// Total persisted pool administration events.
    #[ts(type = "number")]
    pub pool_admin_event_count: i64,
    /// Event coverage entries listed from upstream registry sources.
    #[ts(type = "number")]
    pub event_coverage_listed_entry_count: u64,
    /// Event coverage entries mapped to local decoder event kinds.
    #[ts(type = "number")]
    pub event_coverage_decoded_entry_count: u64,
    /// Event coverage entries observed in local corpus.
    #[ts(type = "number")]
    pub event_coverage_observed_entry_count: u64,
    /// Event coverage entries materialized into target tables.
    #[ts(type = "number")]
    pub event_coverage_materialized_entry_count: u64,
    /// Coverage rows whose mapped events are not locally observed yet.
    #[ts(type = "number")]
    pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
    /// Total known tokens.
    #[ts(type = "number")]
    pub token_count: i64,
    /// Total tokens missing symbol or name.
    #[ts(type = "number")]
    pub token_metadata_missing_count: i64,
    /// Total tokens used by trade-materialized pairs that still miss symbol or name.
    #[ts(type = "number")]
    pub tradable_token_metadata_missing_count: i64,
    /// Total quote-side tokens used by pairs that still miss symbol or name.
    #[ts(type = "number")]
    pub quote_token_metadata_missing_count: i64,
    /// Total pairs whose display symbol is missing or still falls back to raw mints.
    #[ts(type = "number")]
    pub pair_symbol_fallback_count: i64,
    /// Total pairs whose display symbol is present and does not include raw mints.
    #[ts(type = "number")]
    pub pair_symbol_resolved_count: i64,
    /// Total pairs whose quote token is WSOL.
    #[ts(type = "number")]
    pub wsol_quote_pair_count: i64,
    /// Total pairs whose quote token is a known stable quote.
    #[ts(type = "number")]
    pub stable_quote_pair_count: i64,
    /// Number of entries currently exposed by the DEX support matrix.
    #[ts(type = "number")]
    pub dex_support_matrix_entry_count: i64,
    /// DEX support matrix snapshot exposed with the validation report.
    pub dex_support_matrix: std::vec::Vec<DemoPipeline2DexSupportMatrixEntry>,
    /// Issues produced by validation.
    pub issues: std::vec::Vec<DemoPipeline2LocalPipelineValidationIssue>,
}

/// One DEX support matrix entry for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2DexSupportMatrixEntry.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2DexSupportMatrixEntry {
    /// Stable internal protocol or surface code.
    pub code: std::string::String,
    /// Human-readable protocol or surface name.
    pub display_name: std::string::String,
    /// Protocol family.
    pub family: std::string::String,
    /// Protocol version or `unknown` when not verified locally.
    pub version: std::string::String,
    /// Technical surface type: launch, bonding curve, AMM, CLMM, DLMM, router, aggregator or unknown.
    pub surface_type: std::string::String,
    /// Strategic surface role: dex_effective, aggregator_router, launch_surface or to_verify.
    pub surface_role: std::string::String,
    /// Primary Solana program id, when verified in local constants or docs.
    pub program_id: std::option::Option<std::string::String>,
    /// Optional router program id, when this entry uses a distinct router.
    pub router_program_id: std::option::Option<std::string::String>,
    /// Program id confidence: known, to_verify or unknown.
    pub program_id_status: std::string::String,
    /// Whether this protocol has been observed in the local replay corpus.
    pub observed: bool,
    /// Whether the code currently contains a decoder for this protocol.
    pub decoded: bool,
    /// Whether decoded events are currently materialized beyond raw decoded rows.
    pub materialized: bool,
    /// Whether this protocol can currently produce trade candidates.
    pub trade_candidate: bool,
    /// Whether this protocol can currently produce candle candidates.
    pub candle_candidate: bool,
    /// Whether this protocol can currently produce pair candidates.
    pub pair_candidate: bool,
    /// Whether this protocol can currently produce pool candidates.
    pub pool_candidate: bool,
    /// Operational support status.
    pub status: std::string::String,
    /// Confidence level attached to this matrix entry.
    pub confidence: std::string::String,
    /// Optional explicit skip reason for partial or ignored entries.
    pub skip_reason: std::option::Option<std::string::String>,
    /// Whether the entry should be inserted as an enabled DEX in the storage catalog.
    pub catalog_enabled: bool,
}

/// One local pipeline validation issue for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPipelineValidationIssue.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPipelineValidationIssue {
    /// Stable machine-readable issue code.
    pub code: std::string::String,
    /// Human-readable issue message.
    pub message: std::string::String,
    /// Optional subject associated with the issue.
    pub subject: std::option::Option<std::string::String>,
    /// Whether the issue blocks the validation report.
    pub blocking: bool,
}

/// Local pipeline diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPipelineDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
    /// Total persisted chain transactions.
    #[ts(type = "number")]
    pub transaction_count: i64,
    /// Total successful chain transactions.
    #[ts(type = "number")]
    pub ok_transaction_count: i64,
    /// Total failed chain transactions.
    #[ts(type = "number")]
    pub failed_transaction_count: i64,
    /// Total decoded DEX events.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Total decoded DEX trade candidates.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Total decoded DEX candle candidates.
    #[ts(type = "number")]
    pub decoded_candle_candidate_count: i64,
    /// Total decoded useful non-trade events.
    #[ts(type = "number")]
    pub decoded_non_trade_useful_event_count: i64,
    /// Total decoded swap-like events that are intentionally non-actionable.
    #[ts(type = "number")]
    pub decoded_non_actionable_trade_event_count: i64,
    /// Total decoded events with unknown classification.
    #[ts(type = "number")]
    pub decoded_unknown_event_count: i64,
    /// Total persisted liquidity events.
    #[ts(type = "number")]
    pub liquidity_event_count: i64,
    /// Total persisted pool lifecycle events.
    #[ts(type = "number")]
    pub pool_lifecycle_event_count: i64,
    /// Total persisted fee events.
    #[ts(type = "number")]
    pub fee_event_count: i64,
    /// Total persisted reward events.
    #[ts(type = "number")]
    pub reward_event_count: i64,
    /// Total persisted pool administration events.
    #[ts(type = "number")]
    pub pool_admin_event_count: i64,
    /// Event coverage entries listed from upstream registry sources.
    #[ts(type = "number")]
    pub event_coverage_listed_entry_count: u64,
    /// Event coverage entries mapped to local decoder event kinds.
    #[ts(type = "number")]
    pub event_coverage_decoded_entry_count: u64,
    /// Event coverage entries observed in local corpus.
    #[ts(type = "number")]
    pub event_coverage_observed_entry_count: u64,
    /// Event coverage entries materialized into target tables.
    #[ts(type = "number")]
    pub event_coverage_materialized_entry_count: u64,
    /// Coverage rows whose mapped events are not locally observed yet.
    #[ts(type = "number")]
    pub event_coverage_upstream_git_mapped_unverified_entry_count: u64,
    /// Whether the local persisted pipeline has no blocking diagnostic issue.
    pub diagnostics_clean: bool,
    /// Number of blocking diagnostic issues.
    #[ts(type = "number")]
    pub blocking_issue_count: i64,
    /// Total trade candidates without trade event, including ignored failed transactions.
    #[ts(type = "number")]
    pub missing_trade_event_count: i64,
    /// Explicit alias for decoded trade candidates without linked trade event.
    #[ts(type = "number")]
    pub decoded_trade_candidate_without_trade_event_count: i64,
    /// Trade candidates without linked trade event on successful transactions.
    #[ts(type = "number")]
    pub decoded_trade_candidate_without_trade_event_on_ok_transaction_count: i64,
    /// Trade candidates without linked trade event on failed transactions.
    #[ts(type = "number")]
    pub decoded_trade_candidate_without_trade_event_on_failed_transaction_count: i64,
    /// Trade candidates without linked trade event and without explicit base/quote payload amounts.
    /// Actionable missing trade events on successful transactions.
    #[ts(type = "number")]
    pub actionable_missing_trade_event_count: i64,
    /// Ignored missing trade events caused by failed transactions.
    #[ts(type = "number")]
    pub ignored_failed_transaction_trade_candidate_count: i64,
    #[ts(type = "number")]
    pub decoded_trade_candidate_without_amount_payload_count: i64,
    /// Total persisted trade events.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Total invalid trade events.
    #[ts(type = "number")]
    pub invalid_trade_event_count: i64,
    /// Total persisted pair candles.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
    /// Real duplicate trade rows grouped by decoded event id.
    #[ts(type = "number")]
    pub duplicate_decoded_event_trade_count: i64,
    /// Multi-trade groups sharing the same signature and pair id.
    #[ts(type = "number")]
    pub multi_trade_signature_pair_count: i64,
    /// Total duplicate candle buckets.
    #[ts(type = "number")]
    pub duplicate_candle_bucket_count: i64,
    /// Total known tokens.
    #[ts(type = "number")]
    pub token_count: i64,
    /// Total tokens missing symbol or name.
    #[ts(type = "number")]
    pub token_metadata_missing_count: i64,
    /// Total tokens used by trade-materialized pairs that still miss symbol or name.
    #[ts(type = "number")]
    pub tradable_token_metadata_missing_count: i64,
    /// Total quote-side tokens used by pairs that still miss symbol or name.
    #[ts(type = "number")]
    pub quote_token_metadata_missing_count: i64,
    /// Total pairs whose display symbol is missing or still falls back to raw mints.
    #[ts(type = "number")]
    pub pair_symbol_fallback_count: i64,
    /// Total pairs whose display symbol is present and does not include raw mints.
    #[ts(type = "number")]
    pub pair_symbol_resolved_count: i64,
    /// Total pairs whose quote token is WSOL.
    #[ts(type = "number")]
    pub wsol_quote_pair_count: i64,
    /// Total pairs whose quote token is a known stable quote.
    #[ts(type = "number")]
    pub stable_quote_pair_count: i64,
    /// Total known pools.
    #[ts(type = "number")]
    pub pool_count: i64,
    /// Total known pairs.
    #[ts(type = "number")]
    pub pair_count: i64,
    /// Stable explanation for legacy pair gap counters.
    pub pair_gap_counter_semantics: std::string::String,
    /// Total pairs without any persisted trade event.
    #[ts(type = "number")]
    pub literal_pair_without_trade_count: i64,
    /// Total pairs without any persisted candle.
    #[ts(type = "number")]
    pub literal_pair_without_candle_count: i64,
    /// Total pairs that have at least one persisted trade event.
    #[ts(type = "number")]
    pub trade_materialized_pair_count: i64,
    /// Total pairs that have at least one persisted candle bucket.
    #[ts(type = "number")]
    pub candle_materialized_pair_count: i64,
    /// Total pairs with at least one successful decoded trade candidate.
    #[ts(type = "number")]
    pub actionable_pair_count: i64,
    /// Total distinct candle timeframes currently materialized.
    #[ts(type = "number")]
    pub candle_bucket_timeframe_count: i64,
    /// Whether candle rows are bucketed aggregates rather than one row per trade.
    pub candles_are_bucketed: bool,
    /// Total pairs without trade among actionable successful trade candidates.
    #[ts(type = "number")]
    pub blocking_pair_without_trade_count: i64,
    /// Total pairs without candle among actionable successful candle candidates.
    #[ts(type = "number")]
    pub blocking_pair_without_candle_count: i64,
    /// Total pairs without trade. Legacy alias for blocking/actionable gaps.
    #[ts(type = "number")]
    pub pair_without_trade_count: i64,
    /// Total pairs without candle. Legacy alias for blocking/actionable gaps.
    #[ts(type = "number")]
    pub pair_without_candle_count: i64,
    /// Diagnostics grouped by DEX.
    pub dex_summaries: std::vec::Vec<DemoPipeline2LocalDexDiagnosticSummary>,
    /// Diagnostics grouped by pair.
    pub pair_summaries: std::vec::Vec<DemoPipeline2LocalPairDiagnosticSummary>,
    /// Diagnostics grouped by pair materialization/actionability class.
    pub pair_actionability_summaries:
        std::vec::Vec<DemoPipeline2LocalPairActionabilityDiagnosticSummary>,
    /// Diagnostics grouped by pair trading-readiness class.
    pub pair_trading_readiness_summaries:
        std::vec::Vec<DemoPipeline2LocalPairTradingReadinessDiagnosticSummary>,
    /// Diagnostics grouped by decoded event kind.
    pub decoded_event_summaries: std::vec::Vec<DemoPipeline2LocalDecodedEventDiagnosticSummary>,
    /// Diagnostics grouped by decoded event classification.
    pub event_classification_summaries:
        std::vec::Vec<DemoPipeline2LocalEventClassificationDiagnosticSummary>,
    /// Missing trade events grouped by diagnostic reason.
    pub missing_trade_event_reason_summaries:
        std::vec::Vec<DemoPipeline2LocalMissingTradeEventReasonSummary>,
    /// Samples of launch-origin attributions.
    pub launch_origin_samples: std::vec::Vec<DemoPipeline2LocalLaunchOriginDiagnosticSample>,
    /// Samples of pool-origin rows and their optional launch linkage.
    pub pool_origin_samples: std::vec::Vec<DemoPipeline2LocalPoolOriginDiagnosticSample>,
    /// Prioritized samples of tokens whose display metadata is still incomplete.
    pub token_metadata_gap_samples:
        std::vec::Vec<DemoPipeline2LocalTokenMetadataGapDiagnosticSample>,
    /// Total pairs with only non-actionable missing trade events.
    #[ts(type = "number")]
    pub non_actionable_pair_count: i64,
    /// Pair summaries for non-actionable missing trade events.
    pub non_actionable_pair_summaries:
        std::vec::Vec<DemoPipeline2LocalNonActionablePairDiagnosticSummary>,
    /// Samples of decoded trade candidates without linked trade event.
    pub missing_trade_event_samples:
        std::vec::Vec<DemoPipeline2LocalMissingTradeEventDiagnosticSample>,
    /// Samples of duplicated trade rows by decoded event id.
    pub duplicate_decoded_event_trade_samples:
        std::vec::Vec<DemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample>,
    /// Samples of multi-trade signature/pair groups.
    pub multi_trade_signature_pair_samples:
        std::vec::Vec<DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample>,
    /// Samples of pairs without trade.
    pub pair_without_trade_samples: std::vec::Vec<DemoPipeline2LocalPairGapDiagnosticSample>,
    /// Samples of pairs without candle.
    pub pair_without_candle_samples: std::vec::Vec<DemoPipeline2LocalPairGapDiagnosticSample>,
}

/// Local DEX diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalDexDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalDexDiagnosticSummary {
    /// DEX code.
    pub dex_code: std::string::String,
    /// Pool count.
    #[ts(type = "number")]
    pub pool_count: i64,
    /// Pair count.
    #[ts(type = "number")]
    pub pair_count: i64,
    /// Decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Decoded candle candidate count.
    #[ts(type = "number")]
    pub decoded_candle_candidate_count: i64,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Pair candle count.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
}

/// Local pair diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPairDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPairDiagnosticSummary {
    /// Pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Pool address.
    pub pool_address: std::string::String,
    /// DEX code.
    pub dex_code: std::string::String,
    /// Base mint.
    pub base_mint: std::string::String,
    /// Base symbol.
    pub base_symbol: std::option::Option<std::string::String>,
    /// Quote mint.
    pub quote_mint: std::string::String,
    /// Quote symbol.
    pub quote_symbol: std::option::Option<std::string::String>,
    /// Pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
    /// Decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Decoded candle candidate count.
    #[ts(type = "number")]
    pub decoded_candle_candidate_count: i64,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Invalid trade event count.
    #[ts(type = "number")]
    pub invalid_trade_event_count: i64,
    /// Pair candle count.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
    /// Last known price.
    #[ts(type = "number | null")]
    pub last_price_quote_per_base: std::option::Option<f64>,
    /// Pair trading-readiness class derived from base/quote orientation.
    pub pair_trading_readiness: std::string::String,
    /// Quote asset class used by the readiness classifier.
    pub quote_asset_class: std::string::String,
    /// Whether the pair likely requires a router or aggregator before direct execution.
    pub trading_route_required: bool,
}

/// Local pair actionability diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPairActionabilityDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPairActionabilityDiagnosticSummary {
    /// Pair actionability or materialization class.
    pub pair_actionability: std::string::String,
    /// Pair count.
    #[ts(type = "number")]
    pub pair_count: i64,
    /// Decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Actionable trade candidate count.
    #[ts(type = "number")]
    pub actionable_trade_candidate_count: i64,
    /// Failed trade candidate count.
    #[ts(type = "number")]
    pub failed_trade_candidate_count: i64,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Pair candle count.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
}

/// Local pair trading-readiness diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPairTradingReadinessDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
    /// Pair trading-readiness class.
    pub pair_trading_readiness: std::string::String,
    /// Quote asset class.
    pub quote_asset_class: std::string::String,
    /// Whether a router or aggregator is required before direct execution.
    pub trading_route_required: bool,
    /// Pair count.
    #[ts(type = "number")]
    pub pair_count: i64,
    /// Decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Pair candle count.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
}

/// Local decoded-event diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalDecodedEventDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalDecodedEventDiagnosticSummary {
    /// Protocol name.
    pub protocol_name: std::string::String,
    /// Event kind.
    pub event_kind: std::string::String,
    /// Event category.
    pub event_category: std::option::Option<std::string::String>,
    /// Event lifecycle kind.
    pub event_lifecycle_kind: std::option::Option<std::string::String>,
    /// Event actionability class.
    pub event_actionability: std::option::Option<std::string::String>,
    /// Whether the event is useful but not trade/candle materialized.
    pub non_trade_useful: std::option::Option<bool>,
    /// Trade candidate flag.
    pub trade_candidate: std::option::Option<bool>,
    /// Candle candidate flag.
    pub candle_candidate: std::option::Option<bool>,
    /// Event count.
    #[ts(type = "number")]
    pub event_count: i64,
    /// Linked trade-event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
}

/// Local decoded-event classification summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalEventClassificationDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalEventClassificationDiagnosticSummary {
    /// Event category.
    pub event_category: std::string::String,
    /// Event lifecycle kind.
    pub event_lifecycle_kind: std::string::String,
    /// Event actionability class.
    pub event_actionability: std::string::String,
    /// Whether the event is useful but not trade/candle materialized.
    pub non_trade_useful: bool,
    /// Event count.
    #[ts(type = "number")]
    pub event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Decoded candle candidate count.
    #[ts(type = "number")]
    pub decoded_candle_candidate_count: i64,
    /// Linked trade-event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
}

/// Local missing-trade-event reason summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalMissingTradeEventReasonSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalMissingTradeEventReasonSummary {
    /// Diagnostic reason.
    pub reason: std::string::String,
    /// Whether grouped source transactions failed.
    pub transaction_failed: bool,
    /// Whether grouped missing trade events are actionable.
    pub actionable: bool,
    /// Total missing trade events in this group.
    #[ts(type = "number")]
    pub event_count: i64,
    /// Total events in this group with an explicit base amount payload.
    #[ts(type = "number")]
    pub has_base_amount_payload_count: i64,
    /// Total events in this group with an explicit quote amount payload.
    #[ts(type = "number")]
    pub has_quote_amount_payload_count: i64,
    /// Total events in this group with an explicit price payload.
    #[ts(type = "number")]
    pub has_price_payload_count: i64,
}

/// Local non-actionable pair diagnostic summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalNonActionablePairDiagnosticSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalNonActionablePairDiagnosticSummary {
    /// Pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Pool address.
    pub pool_address: std::string::String,
    /// DEX code.
    pub dex_code: std::string::String,
    /// Base token mint.
    pub base_mint: std::string::String,
    /// Base token symbol.
    pub base_symbol: std::option::Option<std::string::String>,
    /// Quote token mint.
    pub quote_mint: std::string::String,
    /// Quote token symbol.
    pub quote_symbol: std::option::Option<std::string::String>,
    /// Pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
    /// Total decoded trade candidates attached to the pool.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Total non-actionable missing trade events attached to the pair.
    #[ts(type = "number")]
    pub non_actionable_missing_trade_event_count: i64,
    /// Total missing trade candidates caused by failed transactions.
    #[ts(type = "number")]
    pub failed_transaction_candidate_count: i64,
    /// Total successful missing trade candidates without token mints.
    #[ts(type = "number")]
    pub ok_transaction_without_token_mints_count: i64,
    /// Total successful missing trade candidates without amount payload.
    #[ts(type = "number")]
    pub ok_transaction_without_amount_payload_count: i64,
    /// Distinct non-actionable diagnostic reasons seen for this pair.
    pub reason_summary: std::option::Option<std::string::String>,
    /// Total trade events attached to the pair.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Total candle buckets attached to the pair.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
}

/// Local missing-trade-event diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalMissingTradeEventDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalMissingTradeEventDiagnosticSample {
    /// Decoded event id.
    #[ts(type = "number")]
    pub decoded_event_id: i64,
    /// Chain transaction id.
    #[ts(type = "number | null")]
    pub transaction_id: std::option::Option<i64>,
    /// Transaction signature.
    pub signature: std::option::Option<std::string::String>,
    /// Protocol name.
    pub protocol_name: std::string::String,
    /// Event kind.
    pub event_kind: std::string::String,
    /// Pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Whether the source transaction failed.
    pub transaction_failed: bool,
    /// Whether this missing trade event is actionable for validation.
    pub actionable: bool,
    /// Diagnostic reason explaining why no trade event was linked.
    pub reason: std::string::String,
    /// Whether payload has an explicit base amount.
    pub has_base_amount_payload: bool,
    /// Whether payload has an explicit quote amount.
    pub has_quote_amount_payload: bool,
    /// Whether payload has an explicit price.
    pub has_price_payload: bool,
}

/// Local duplicate decoded-event trade diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample {
    /// Decoded event id.
    #[ts(type = "number")]
    pub decoded_event_id: i64,
    /// Protocol name.
    pub protocol_name: std::option::Option<std::string::String>,
    /// Event kind.
    pub event_kind: std::option::Option<std::string::String>,
    /// Pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Trade event ids.
    pub trade_event_ids: std::option::Option<std::string::String>,
    /// Signatures.
    pub signatures: std::option::Option<std::string::String>,
}

/// Local multi-trade signature/pair diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample {
    /// Transaction signature.
    pub signature: std::string::String,
    /// Pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Pool address.
    pub pool_address: std::option::Option<std::string::String>,
    /// DEX code.
    pub dex_code: std::option::Option<std::string::String>,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Distinct decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Trade event ids.
    pub trade_event_ids: std::option::Option<std::string::String>,
    /// Decoded event ids.
    pub decoded_event_ids: std::option::Option<std::string::String>,
}

/// Local pair gap diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPairGapDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPairGapDiagnosticSample {
    /// Pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Pool address.
    pub pool_address: std::string::String,
    /// DEX code.
    pub dex_code: std::string::String,
    /// Base mint.
    pub base_mint: std::string::String,
    /// Base symbol.
    pub base_symbol: std::option::Option<std::string::String>,
    /// Quote mint.
    pub quote_mint: std::string::String,
    /// Quote symbol.
    pub quote_symbol: std::option::Option<std::string::String>,
    /// Pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
    /// Decoded event count.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Decoded trade candidate count.
    #[ts(type = "number")]
    pub decoded_trade_candidate_count: i64,
    /// Trade event count.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Pair candle count.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
}

/// Local launch-origin diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalLaunchOriginDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalLaunchOriginDiagnosticSample {
    /// Launch attribution id.
    #[ts(type = "number")]
    pub launch_attribution_id: i64,
    /// Launch surface code.
    pub launch_surface_code: std::string::String,
    /// Launch surface display name.
    pub launch_surface_name: std::string::String,
    /// Transaction signature.
    pub transaction_signature: std::string::String,
    /// Decoded event id.
    #[ts(type = "number")]
    pub decoded_event_id: i64,
    /// Effective protocol recorded on the decoded event.
    pub protocol_name: std::string::String,
    /// Match kind used for attribution.
    pub match_kind: std::string::String,
    /// Matched key value.
    pub matched_value: std::string::String,
    /// Optional pool id.
    #[ts(type = "number | null")]
    pub pool_id: std::option::Option<i64>,
    /// Optional pool address.
    pub pool_address: std::option::Option<std::string::String>,
    /// Optional pair id.
    #[ts(type = "number | null")]
    pub pair_id: std::option::Option<i64>,
    /// Optional pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
}

/// Local pool-origin diagnostic sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalPoolOriginDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalPoolOriginDiagnosticSample {
    /// Pool-origin id.
    #[ts(type = "number")]
    pub pool_origin_id: i64,
    /// Effective DEX code attached to the pool.
    pub dex_code: std::string::String,
    /// Pool id.
    #[ts(type = "number")]
    pub pool_id: i64,
    /// Pool address.
    pub pool_address: std::string::String,
    /// Optional pair id.
    #[ts(type = "number | null")]
    pub pair_id: std::option::Option<i64>,
    /// Optional pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
    /// Optional launch surface code.
    pub launch_surface_code: std::option::Option<std::string::String>,
    /// Founding transaction signature.
    pub founding_signature: std::string::String,
    /// Founding effective protocol.
    pub founding_protocol_name: std::string::String,
    /// Founding decoded event kind.
    pub founding_event_kind: std::string::String,
}

/// Local token metadata gap sample for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2LocalTokenMetadataGapDiagnosticSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
    /// Token id.
    #[ts(type = "number")]
    pub token_id: i64,
    /// Mint address.
    pub mint: std::string::String,
    /// Current symbol, when present.
    pub symbol: std::option::Option<std::string::String>,
    /// Current name, when present.
    pub name: std::option::Option<std::string::String>,
    /// Current mint decimals, when known.
    #[ts(type = "number | null")]
    pub decimals: std::option::Option<i64>,
    /// Token program id.
    pub token_program: std::string::String,
    /// Whether this token row is flagged as quote token.
    pub is_quote_token: bool,
    /// Whether this token appears in at least one trade-materialized pair.
    pub used_by_trade_materialized_pair: bool,
    /// Whether this token appears on the quote side of at least one pair.
    pub used_as_quote_token: bool,
    /// Number of trade-materialized pairs using this token.
    #[ts(type = "number")]
    pub trade_materialized_pair_count: i64,
    /// Number of catalog pairs using this token.
    #[ts(type = "number")]
    pub total_pair_count: i64,
    /// Prioritization bucket.
    pub priority: std::string::String,
}

/// One token item for the local catalog.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2TokenItem.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2TokenItem {
    /// Token mint.
    pub mint: std::string::String,
    /// Optional token symbol.
    pub symbol: std::option::Option<std::string::String>,
    /// Optional token name.
    pub name: std::option::Option<std::string::String>,
}

/// One pool item for the local catalog.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2PoolItem.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2PoolItem {
    /// Pool address.
    pub pool_address: std::string::String,
    /// Optional internal pair id when known.
    #[ts(type = "number | null")]
    pub pair_id: std::option::Option<i64>,
    /// Optional DEX code.
    pub dex_code: std::option::Option<std::string::String>,
}

/// One pair item for the local catalog.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2PairItem.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2PairItem {
    /// Internal pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Related pool address.
    pub pool_address: std::string::String,
    /// Optional pair symbol.
    pub symbol: std::option::Option<std::string::String>,
    /// Optional DEX code.
    pub dex_code: std::option::Option<std::string::String>,
    /// Optional local trade count.
    #[ts(type = "number | null")]
    pub trade_count: std::option::Option<i64>,
    /// Optional local last price.
    #[ts(type = "number | null")]
    pub last_price_quote_per_base: std::option::Option<f64>,
}

/// Full local catalog payload.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2CatalogPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2CatalogPayload {
    /// Open database URL.
    pub database_url: std::string::String,
    /// Observed token list.
    pub tokens: std::vec::Vec<DemoPipeline2TokenItem>,
    /// Known pool list.
    pub pools: std::vec::Vec<DemoPipeline2PoolItem>,
    /// Known pair list.
    pub pairs: std::vec::Vec<DemoPipeline2PairItem>,
}

/// Request payload for token backfill.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2BackfillTokenRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2BackfillTokenRequest {
    /// Token mint to backfill.
    pub token_mint: std::string::String,
    /// Optional HTTP role.
    pub http_role: std::option::Option<std::string::String>,
    /// Limit for signatures fetched from the mint.
    pub mint_signature_limit: u32,
    /// Limit for signatures fetched from each discovered pool.
    pub pool_signature_limit: u32,
}

/// Request payload for pool backfill.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2BackfillPoolRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2BackfillPoolRequest {
    /// Pool address to backfill.
    pub pool_address: std::string::String,
    /// Optional HTTP role.
    pub http_role: std::option::Option<std::string::String>,
    /// Limit for signatures fetched from the pool.
    pub pool_signature_limit: u32,
}

/// Request payload for single-signature backfill.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2BackfillSignatureRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2BackfillSignatureRequest {
    /// Transaction signature to resolve and replay.
    pub signature: std::string::String,
    /// Optional HTTP role.
    pub http_role: std::option::Option<std::string::String>,
}

/// Request payload for batch signature backfill.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/DemoPipeline2BackfillSignaturesBatchRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2BackfillSignaturesBatchRequest {
    /// Transaction signatures to resolve and replay.
    pub signatures: std::vec::Vec<std::string::String>,
    /// Optional HTTP role.
    pub http_role: std::option::Option<std::string::String>,
    /// Whether the batch should continue after a hard per-signature error.
    pub continue_on_error: bool,
}

/// Shared backfill response payload.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2BackfillPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2BackfillPayload {
    /// Object key used by the backfill.
    pub object_key: std::string::String,
    /// Mode: `tokenMint`, `poolAddress` or `signature`.
    pub mode: std::string::String,
    /// HTTP role used.
    pub http_role: std::string::String,
    /// Pretty JSON summary.
    pub summary_json: std::string::String,
    /// Refreshed local catalog after backfill.
    pub catalog: DemoPipeline2CatalogPayload,
}

/// Request payload for pair candles.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2PairCandlesRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2PairCandlesRequest {
    /// Pair id to load.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Timeframe in seconds.
    #[ts(type = "number")]
    pub timeframe_seconds: i64,
    /// Whether materialized candles should be preferred when available.
    pub prefer_materialized: bool,
}

/// Candle payload returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/DemoPipeline2PairCandlesPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct DemoPipeline2PairCandlesPayload {
    /// Pair id.
    #[ts(type = "number")]
    pub pair_id: i64,
    /// Timeframe in seconds.
    #[ts(type = "number")]
    pub timeframe_seconds: i64,
    /// Pretty JSON array of candles.
    pub candles_json: std::string::String,
}

/// Lists program instruction discriminator summaries for one program id.
#[tauri::command]
pub(crate) async fn demo_pipeline2_get_program_instruction_discriminator_summaries(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2ProgramInstructionDiscriminatorSummaryRequest,
) -> Result<DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload, std::string::String> {
    if request.program_id.trim().is_empty() {
        return Err("program id must not be empty".to_string());
    }
    if request.limit == 0 {
        return Err("instruction discriminator summary limit must be > 0".to_string());
    }
    let rows_result = kb_lib::query_program_instruction_discriminator_summaries_list_by_program_id(
        state.database.as_ref(),
        request.program_id.as_str(),
        request.limit,
    )
    .await;
    let rows = match rows_result {
        Ok(rows) => rows,
        Err(error) => {
            return Err(format!(
                "cannot list instruction discriminator summaries for program_id '{}': {}",
                request.program_id, error
            ));
        },
    };
    let summaries_json_result = serde_json::to_string_pretty(&rows);
    let summaries_json = match summaries_json_result {
        Ok(summaries_json) => summaries_json,
        Err(error) => {
            return Err(format!("cannot serialize instruction discriminator summaries: {}", error));
        },
    };
    return Ok(DemoPipeline2ProgramInstructionDiscriminatorSummaryPayload { summaries_json });
}

/// Lists protocol candidate summaries ordered by investigation priority.
#[tauri::command]
pub(crate) async fn demo_pipeline2_get_protocol_candidate_summaries(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2ProtocolCandidateSummaryRequest,
) -> Result<DemoPipeline2ProtocolCandidateSummaryPayload, std::string::String> {
    if request.limit == 0 {
        return Err("protocol candidate summary limit must be > 0".to_string());
    }
    let summaries_result = kb_lib::query_protocol_candidate_summaries_list_by_priority(
        state.database.as_ref(),
        request.limit,
    )
    .await;
    let summaries = match summaries_result {
        Ok(summaries) => summaries,
        Err(error) => {
            return Err(format!(
                "cannot list protocol candidate summaries with limit '{}': {}",
                request.limit, error
            ));
        },
    };
    let summaries_json_result = serde_json::to_string_pretty(&summaries);
    let summaries_json = match summaries_json_result {
        Ok(summaries_json) => summaries_json,
        Err(error) => {
            return Err(format!("cannot serialize protocol candidate summaries: {}", error));
        },
    };
    return Ok(DemoPipeline2ProtocolCandidateSummaryPayload { summaries_json });
}

/// Runs local pipeline diagnostics from persisted data only.
#[tauri::command]
pub(crate) async fn demo_pipeline2_diagnose_local_pipeline(
    state: tauri::State<'_, crate::AppState>,
) -> Result<DemoPipeline2LocalDiagnosticsPayload, std::string::String> {
    let database = state.database.clone();
    let service = kb_lib::LocalPipelineDiagnosticsService::new(database.clone());
    let summary_result = service.diagnose().await;
    let summary = match summary_result {
        Ok(summary) => summary,
        Err(error) => {
            return Err(format!("local pipeline diagnostics failed: {}", error));
        },
    };
    let ui_summary = demo_pipeline2_map_local_diagnostics_summary(summary);
    let summary_json_result = serde_json::to_string_pretty(&ui_summary);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!("cannot serialize local pipeline diagnostics: {}", error));
        },
    };
    Ok(DemoPipeline2LocalDiagnosticsPayload {
        database_url: database.database_url().to_string(),
        summary_json,
        summary: ui_summary,
    })
}

/// Validates the local pipeline with a selected multi-DEX validation profile.
#[tauri::command]
pub(crate) async fn demo_pipeline2_validate_local_pipeline(
    state: tauri::State<'_, crate::AppState>,
    request: std::option::Option<DemoPipeline2LocalValidationRequest>,
) -> Result<DemoPipeline2LocalValidationPayload, std::string::String> {
    let database = state.database.clone();
    let service = kb_lib::LocalPipelineValidationService::new(database.clone());
    let profile_code = match request {
        Some(request) => request.profile_code,
        None => "0.7.48-pre_event_coverage_db_checkpoint".to_string(),
    };
    let run_result = match profile_code.as_str() {
        "0.7.27" | "0.7.27_dexes_non_regression" => {
            service.validate_v0_7_27_current_database().await
        },
        "0.7.28" | "0.7.28_multi_dex_non_regression" => {
            service.validate_v0_7_28_current_database().await
        },
        "0.7.29" | "0.7.29_multi_dex_matrix_baseline" => {
            service.validate_v0_7_29_current_database().await
        },
        "0.7.30" | "0.7.30_non_trade_event_classification" => {
            service.validate_v0_7_30_current_database().await
        },
        "0.7.31" | "0.7.31_trade_event_actionability_policy" => {
            service.validate_v0_7_31_current_database().await
        },
        "0.7.32" | "0.7.32_validation_report_semantics" => {
            service.validate_v0_7_32_current_database().await
        },
        "0.7.33" | "0.7.33_pair_trading_readiness" => {
            service.validate_v0_7_33_current_database().await
        },
        "0.7.34" | "0.7.34_non_trade_liquidity_lifecycle" => {
            service.validate_v0_7_34_current_database().await
        },
        "0.7.35" | "0.7.35_non_trade_fee_reward_admin" => {
            service.validate_v0_7_35_current_database().await
        },
        "0.7.36" | "0.7.36_meteora_family_consolidation" => {
            service.validate_v0_7_36_current_database().await
        },
        "0.7.37" | "0.7.37_token_metadata_catalog_enrichment" => {
            service.validate_v0_7_37_current_database().await
        },
        "0.7.38" | "0.7.38_token_metadata_gap_prioritization" => {
            service.validate_v0_7_38_current_database().await
        },
        "0.7.39"
        | "0.7.39_dex_first_effective_swap_surfaces"
        | "0.7.39_launch_surface_origin_baseline" => {
            service.validate_v0_7_39_current_database().await
        },
        "0.7.40" | "0.7.40_raydium_effective_surfaces" => {
            service.validate_v0_7_40_current_database().await
        },
        "0.7.41" | "0.7.41_raydium_amm_v4_swap_decoder" => {
            service.validate_v0_7_41_current_database().await
        },
        "0.7.42" | "0.7.42_raydium_family_event_coverage" => {
            service.validate_v0_7_42_current_database().await
        },
        "0.7.43" | "0.7.43_meteora_effective_surfaces" => {
            service.validate_v0_7_43_current_database().await
        },
        "0.7.48-pre" | "0.7.48-pre_event_coverage_db_checkpoint" => {
            service.validate_v0_7_48_pre_current_database().await
        },
        other => Err(kb_lib::Error::InvalidState(format!(
            "unsupported local pipeline validation profile: {other}"
        ))),
    };
    let run = match run_result {
        Ok(run) => run,
        Err(error) => {
            return Err(format!("local pipeline validation failed: {}", error));
        },
    };
    let ui_run = demo_pipeline2_map_local_validation_run(run);
    let summary_json_result = serde_json::to_string_pretty(&ui_run.summary);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!("cannot serialize local pipeline validation summary: {}", error));
        },
    };
    let validation_json_result = serde_json::to_string_pretty(&ui_run);
    let validation_json = match validation_json_result {
        Ok(validation_json) => validation_json,
        Err(error) => {
            return Err(format!("cannot serialize local pipeline validation run: {}", error));
        },
    };
    Ok(DemoPipeline2LocalValidationPayload {
        database_url: database.database_url().to_string(),
        summary_json,
        validation_json,
        run: ui_run,
    })
}

/// Opens the `Demo Pipeline 2` window.
#[tauri::command]
pub(crate) fn open_demo_pipeline2_window(
    app_handle: tauri::AppHandle,
) -> Result<(), std::string::String> {
    let existing_window_option = app_handle.get_webview_window("demo_pipeline2");

    let demo_window = match existing_window_option {
        Some(demo_window) => demo_window,
        None => {
            let builder = tauri::WebviewWindowBuilder::new(
                &app_handle,
                "demo_pipeline2",
                tauri::WebviewUrl::App("demo_pipeline2.html".into()),
            )
            .title("Demo Pipeline 2")
            .inner_size(1480.0, 920.0)
            .min_inner_size(1100.0, 720.0)
            .center()
            .visible(true)
            .transparent(false)
            .decorations(true);
            let build_result = builder.build();
            match build_result {
                Ok(window) => window,
                Err(error) => {
                    return Err(format!("cannot create demo_pipeline2 window: {error:?}"));
                },
            }
        },
    };
    let show_result = demo_window.show();
    if let Err(error) = show_result {
        return Err(format!("cannot show demo_pipeline2 window: {error:?}"));
    }
    let focus_result = demo_window.set_focus();
    if let Err(error) = focus_result {
        return Err(format!("cannot focus demo_pipeline2 window: {error:?}"));
    }
    Ok(())
}

/// Returns the local catalog of observed tokens, pools and pairs.
#[tauri::command]
pub(crate) async fn demo_pipeline2_get_catalog(
    state: tauri::State<'_, crate::AppState>,
) -> Result<DemoPipeline2CatalogPayload, std::string::String> {
    demo_pipeline2_build_catalog(state.database.clone()).await
}

/// Runs a targeted token backfill then returns the refreshed catalog.
#[tauri::command]
pub(crate) async fn demo_pipeline2_backfill_token_mint(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2BackfillTokenRequest,
) -> Result<DemoPipeline2BackfillPayload, std::string::String> {
    let token_mint = request.token_mint.trim().to_string();
    if token_mint.is_empty() {
        return Err("token mint must not be empty".to_string());
    }
    if request.mint_signature_limit == 0 {
        return Err("mintSignatureLimit must be > 0".to_string());
    }
    if request.pool_signature_limit == 0 {
        return Err("poolSignatureLimit must be > 0".to_string());
    }
    let http_role = demo_pipeline2_normalize_http_role(request.http_role);
    let database = state.database.clone();
    let http_pool = std::sync::Arc::new(state.http_pool.clone());
    let service = kb_lib::TokenBackfillService::new(http_pool, database.clone(), http_role.clone());
    let result = service
        .backfill_token_by_mint(
            token_mint.as_str(),
            request.mint_signature_limit as usize,
            request.pool_signature_limit as usize,
        )
        .await;
    let backfill = match result {
        Ok(backfill) => backfill,
        Err(error) => {
            return Err(format!(
                "cannot backfill token mint '{}' with role '{}': {}",
                token_mint, http_role, error
            ));
        },
    };
    let summary_json_result = serde_json::to_string_pretty(&backfill);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!(
                "cannot serialize token backfill result for '{}': {}",
                token_mint, error
            ));
        },
    };
    let catalog = demo_pipeline2_build_catalog(database).await?;
    Ok(DemoPipeline2BackfillPayload {
        object_key: token_mint,
        mode: "tokenMint".to_string(),
        http_role,
        summary_json,
        catalog,
    })
}

/// Runs a targeted pool backfill then returns the refreshed catalog.
#[tauri::command]
pub(crate) async fn demo_pipeline2_backfill_pool_address(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2BackfillPoolRequest,
) -> Result<DemoPipeline2BackfillPayload, std::string::String> {
    let pool_address = request.pool_address.trim().to_string();
    if pool_address.is_empty() {
        return Err("pool address must not be empty".to_string());
    }
    if request.pool_signature_limit == 0 {
        return Err("poolSignatureLimit must be > 0".to_string());
    }
    let http_role = demo_pipeline2_normalize_http_role(request.http_role);
    let database = state.database.clone();
    let http_pool = std::sync::Arc::new(state.http_pool.clone());
    let service = kb_lib::TokenBackfillService::new(http_pool, database.clone(), http_role.clone());
    let result = service
        .backfill_pool_by_address(pool_address.as_str(), request.pool_signature_limit as usize)
        .await;
    let backfill = match result {
        Ok(backfill) => backfill,
        Err(error) => {
            return Err(format!(
                "cannot backfill pool address '{}' with role '{}': {}",
                pool_address, http_role, error
            ));
        },
    };
    let summary_json_result = serde_json::to_string_pretty(&backfill);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!(
                "cannot serialize pool backfill result for '{}': {}",
                pool_address, error
            ));
        },
    };
    let catalog = demo_pipeline2_build_catalog(database).await?;
    Ok(DemoPipeline2BackfillPayload {
        object_key: pool_address,
        mode: "poolAddress".to_string(),
        http_role,
        summary_json,
        catalog,
    })
}

/// Runs a targeted single-signature backfill then returns the refreshed catalog.
#[tauri::command]
pub(crate) async fn demo_pipeline2_backfill_signature(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2BackfillSignatureRequest,
) -> Result<DemoPipeline2BackfillPayload, std::string::String> {
    let signature = request.signature.trim().to_string();
    if signature.is_empty() {
        return Err("signature must not be empty".to_string());
    }
    let http_role = demo_pipeline2_normalize_http_role(request.http_role);
    let database = state.database.clone();
    let http_pool = std::sync::Arc::new(state.http_pool.clone());
    let service = kb_lib::TokenBackfillService::new(http_pool, database.clone(), http_role.clone());
    let result = service.backfill_signature(signature.as_str()).await;
    let backfill = match result {
        Ok(backfill) => backfill,
        Err(error) => {
            return Err(format!(
                "cannot backfill signature '{}' with role '{}': {}",
                signature, http_role, error
            ));
        },
    };
    let summary_json_result = serde_json::to_string_pretty(&backfill);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!(
                "cannot serialize signature backfill result for '{}': {}",
                signature, error
            ));
        },
    };
    let catalog_result = demo_pipeline2_build_catalog(database).await;
    let catalog = match catalog_result {
        Ok(catalog) => catalog,
        Err(error) => return Err(error),
    };
    Ok(DemoPipeline2BackfillPayload {
        object_key: signature,
        mode: "signature".to_string(),
        http_role,
        summary_json,
        catalog,
    })
}

/// Runs a targeted batch signature backfill then returns the refreshed catalog.
#[tauri::command]
pub(crate) async fn demo_pipeline2_backfill_signatures_batch(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2BackfillSignaturesBatchRequest,
) -> Result<DemoPipeline2BackfillPayload, std::string::String> {
    let mut signatures = std::vec::Vec::<std::string::String>::new();
    for signature in request.signatures {
        let trimmed_signature = signature.trim().to_string();
        if trimmed_signature.is_empty() {
            continue;
        }
        signatures.push(trimmed_signature);
    }
    if signatures.is_empty() {
        return Err("signature batch must contain at least one signature".to_string());
    }
    let http_role = demo_pipeline2_normalize_http_role(request.http_role);
    let database = state.database.clone();
    let http_pool = std::sync::Arc::new(state.http_pool.clone());
    let service = kb_lib::TokenBackfillService::new(http_pool, database.clone(), http_role.clone());
    let result = service
        .backfill_signatures(signatures.as_slice(), request.continue_on_error)
        .await;
    let backfill = match result {
        Ok(backfill) => backfill,
        Err(error) => {
            return Err(format!(
                "cannot backfill signature batch with role '{}': {}",
                http_role, error
            ));
        },
    };
    let summary_json_result = serde_json::to_string_pretty(&backfill);
    let summary_json = match summary_json_result {
        Ok(summary_json) => summary_json,
        Err(error) => {
            return Err(format!(
                "cannot serialize signature batch backfill result: {}",
                error
            ));
        },
    };
    let catalog_result = demo_pipeline2_build_catalog(database).await;
    let catalog = match catalog_result {
        Ok(catalog) => catalog,
        Err(error) => return Err(error),
    };
    Ok(DemoPipeline2BackfillPayload {
        object_key: format!("{} signatures", backfill.unique_signature_count),
        mode: "signatureBatch".to_string(),
        http_role,
        summary_json,
        catalog,
    })
}

/// Loads candles for one pair and one timeframe.
#[tauri::command]
pub(crate) async fn demo_pipeline2_get_pair_candles(
    state: tauri::State<'_, crate::AppState>,
    request: DemoPipeline2PairCandlesRequest,
) -> Result<DemoPipeline2PairCandlesPayload, std::string::String> {
    if request.pair_id <= 0 {
        return Err("pairId must be > 0".to_string());
    }
    if request.timeframe_seconds <= 0 {
        return Err("timeframeSeconds must be > 0".to_string());
    }
    let query_service = kb_lib::PairCandleQueryService::new(state.database.clone());
    let candles_result = query_service
        .list_pair_candles(
            request.pair_id,
            request.timeframe_seconds,
            None,
            None,
            request.prefer_materialized,
        )
        .await;
    let candles = match candles_result {
        Ok(candles) => candles,
        Err(error) => {
            return Err(format!(
                "cannot load candles for pair '{}' timeframe '{}': {}",
                request.pair_id, request.timeframe_seconds, error
            ));
        },
    };
    let candles_json_result = serde_json::to_string_pretty(&candles);
    let candles_json = match candles_json_result {
        Ok(candles_json) => candles_json,
        Err(error) => {
            return Err(format!(
                "cannot serialize candles for pair '{}' timeframe '{}': {}",
                request.pair_id, request.timeframe_seconds, error
            ));
        },
    };
    Ok(DemoPipeline2PairCandlesPayload {
        pair_id: request.pair_id,
        timeframe_seconds: request.timeframe_seconds,
        candles_json,
    })
}

async fn demo_pipeline2_build_catalog(
    database: std::sync::Arc<kb_lib::Database>,
) -> Result<DemoPipeline2CatalogPayload, std::string::String> {
    let dexes_result = kb_lib::query_dexs_list(database.as_ref()).await;
    let dexes = match dexes_result {
        Ok(dexes) => dexes,
        Err(error) => {
            return Err(format!("cannot list DEXes: {}", error));
        },
    };
    let mut dex_code_by_id = std::collections::BTreeMap::<i64, std::string::String>::new();
    for dex in dexes {
        if let Some(dex_id) = dex.id {
            dex_code_by_id.insert(dex_id, dex.code);
        }
    }
    let tokens_result = kb_lib::query_tokens_list(database.as_ref()).await;
    let db_tokens = match tokens_result {
        Ok(db_tokens) => db_tokens,
        Err(error) => {
            return Err(format!("cannot list tokens: {}", error));
        },
    };
    let mut tokens = std::vec::Vec::<DemoPipeline2TokenItem>::new();
    for token in db_tokens {
        tokens.push(DemoPipeline2TokenItem {
            mint: token.mint,
            symbol: token.symbol,
            name: token.name,
        });
    }
    let pools_result = kb_lib::query_pools_list(database.as_ref()).await;
    let pools = match pools_result {
        Ok(pools) => pools,
        Err(error) => {
            return Err(format!("cannot list pools: {}", error));
        },
    };
    let pairs_result = kb_lib::query_pairs_list(database.as_ref()).await;
    let pairs = match pairs_result {
        Ok(pairs) => pairs,
        Err(error) => {
            return Err(format!("cannot list pairs: {}", error));
        },
    };
    let mut pair_by_pool_id = std::collections::BTreeMap::<i64, kb_lib::PairDto>::new();
    for pair in &pairs {
        pair_by_pool_id.insert(pair.pool_id, pair.clone());
    }
    let mut pair_items = std::vec::Vec::<DemoPipeline2PairItem>::new();
    for pair in pairs {
        let pair_id = match pair.id {
            Some(pair_id) => pair_id,
            None => continue,
        };
        let pool_result = kb_lib::query_pools_get_by_address(database.as_ref(), "").await;
        let _ = pool_result;
        let pool_address = {
            let all_pools_result = kb_lib::query_pools_list(database.as_ref()).await;
            let all_pools = match all_pools_result {
                Ok(all_pools) => all_pools,
                Err(error) => {
                    return Err(format!("cannot reload pools for pair catalog: {}", error));
                },
            };
            let mut found_address = std::string::String::new();
            for pool in all_pools {
                let pool_id = match pool.id {
                    Some(pool_id) => pool_id,
                    None => continue,
                };
                if pool_id == pair.pool_id {
                    found_address = pool.address;
                    break;
                }
            }
            found_address
        };
        let pair_metric_result =
            kb_lib::query_pair_metrics_get_by_pair_id(database.as_ref(), pair_id).await;
        let pair_metric_option = match pair_metric_result {
            Ok(pair_metric_option) => pair_metric_option,
            Err(error) => {
                return Err(format!("cannot fetch pair metric for pair '{}': {}", pair_id, error));
            },
        };
        let trade_count = pair_metric_option.as_ref().map(|metric| metric.trade_count);
        let last_price_quote_per_base =
            pair_metric_option.and_then(|metric| metric.last_price_quote_per_base);
        pair_items.push(DemoPipeline2PairItem {
            pair_id,
            pool_address,
            symbol: pair.symbol,
            dex_code: dex_code_by_id.get(&pair.dex_id).cloned(),
            trade_count,
            last_price_quote_per_base,
        });
    }
    let mut pool_items = std::vec::Vec::<DemoPipeline2PoolItem>::new();
    for pool in pools {
        let pool_id = match pool.id {
            Some(pool_id) => pool_id,
            None => continue,
        };
        let pair_id = pair_by_pool_id.get(&pool_id).and_then(|pair| pair.id);
        pool_items.push(DemoPipeline2PoolItem {
            pool_address: pool.address,
            pair_id,
            dex_code: dex_code_by_id.get(&pool.dex_id).cloned(),
        });
    }
    tokens.sort_by(|left, right| left.mint.cmp(&right.mint));
    pool_items.sort_by(|left, right| left.pool_address.cmp(&right.pool_address));
    pair_items.sort_by(|left, right| left.pair_id.cmp(&right.pair_id));
    Ok(DemoPipeline2CatalogPayload {
        database_url: database.database_url().to_string(),
        tokens,
        pools: pool_items,
        pairs: pair_items,
    })
}

/// Replays the local pipeline from persisted raw transactions.
#[tauri::command]
pub(crate) async fn demo_pipeline2_replay_local_pipeline(
    state: tauri::State<'_, crate::AppState>,
    limit: std::option::Option<i64>,
    refresh_missing_token_metadata: bool,
    token_metadata_limit: std::option::Option<i64>,
    skip_certified_dex_decode: bool,
    force_decode_replay: bool,
    defer_instruction_observation_index_refresh: bool,
) -> Result<kb_lib::LocalPipelineReplayResult, std::string::String> {
    let config = kb_lib::LocalPipelineReplayConfig {
        limit,
        refresh_missing_token_metadata,
        token_metadata_limit,
        reset_market_materialization_before_replay: true,
        skip_certified_dex_decode,
        force_decode_replay,
        defer_instruction_observation_index_refresh,
    };
    let database = state.database.clone();
    let service = if refresh_missing_token_metadata {
        kb_lib::LocalPipelineReplayService::new_with_http_pool(
            std::sync::Arc::new(state.http_pool.clone()),
            database,
            "history_backfill".to_string(),
        )
    } else {
        kb_lib::LocalPipelineReplayService::new(database)
    };
    let replay_result = service.replay_local_pipeline(&config).await;
    match replay_result {
        Ok(result) => Ok(result),
        Err(error) => Err(format!("local pipeline replay failed: {}", error)),
    }
}

fn demo_pipeline2_map_local_validation_run(
    run: kb_lib::LocalPipelineValidationRunDto,
) -> DemoPipeline2LocalPipelineValidationRun {
    let validation_profile_code = run.validation_profile_code;
    let validation_passed = run.validation_passed;
    let blocking_issue_count = run.blocking_issue_count;
    let warning_count = run.warning_count;
    let summary = demo_pipeline2_map_local_diagnostics_summary(run.summary);
    let report = demo_pipeline2_map_local_validation_report(run.report);
    return DemoPipeline2LocalPipelineValidationRun {
        validation_profile_code,
        validation_passed,
        blocking_issue_count,
        warning_count,
        summary,
        report,
    };
}

fn demo_pipeline2_map_local_validation_report(
    report: kb_lib::LocalPipelineValidationReportDto,
) -> DemoPipeline2LocalPipelineValidationReport {
    let mut issues = std::vec::Vec::new();
    for issue in report.issues {
        issues.push(demo_pipeline2_map_local_validation_issue(issue));
    }
    let mut dex_support_matrix = std::vec::Vec::new();
    for entry in report.dex_support_matrix {
        dex_support_matrix.push(demo_pipeline2_map_dex_support_matrix_entry(entry));
    }
    return DemoPipeline2LocalPipelineValidationReport {
        validation_profile_code: report.validation_profile_code,
        validation_passed: report.validation_passed,
        blocking_issue_count: report.blocking_issue_count,
        warning_count: report.warning_count,
        expected_dex_codes: report.expected_dex_codes,
        observed_dex_codes: report.observed_dex_codes,
        decoded_non_trade_useful_event_count: report.decoded_non_trade_useful_event_count,
        decoded_non_actionable_trade_event_count: report.decoded_non_actionable_trade_event_count,
        decoded_unknown_event_count: report.decoded_unknown_event_count,
        liquidity_event_count: report.liquidity_event_count,
        pool_lifecycle_event_count: report.pool_lifecycle_event_count,
        fee_event_count: report.fee_event_count,
        reward_event_count: report.reward_event_count,
        pool_admin_event_count: report.pool_admin_event_count,
        event_coverage_listed_entry_count: report.event_coverage_listed_entry_count,
        event_coverage_decoded_entry_count: report.event_coverage_decoded_entry_count,
        event_coverage_observed_entry_count: report.event_coverage_observed_entry_count,
        event_coverage_materialized_entry_count: report.event_coverage_materialized_entry_count,
        event_coverage_upstream_git_mapped_unverified_entry_count: report
            .event_coverage_upstream_git_mapped_unverified_entry_count,
        token_count: report.token_count,
        token_metadata_missing_count: report.token_metadata_missing_count,
        tradable_token_metadata_missing_count: report.tradable_token_metadata_missing_count,
        quote_token_metadata_missing_count: report.quote_token_metadata_missing_count,
        pair_symbol_fallback_count: report.pair_symbol_fallback_count,
        pair_symbol_resolved_count: report.pair_symbol_resolved_count,
        wsol_quote_pair_count: report.wsol_quote_pair_count,
        stable_quote_pair_count: report.stable_quote_pair_count,
        dex_support_matrix_entry_count: report.dex_support_matrix_entry_count,
        dex_support_matrix,
        issues,
    };
}

fn demo_pipeline2_map_dex_support_matrix_entry(
    entry: kb_lib::DexSupportMatrixEntryDto,
) -> DemoPipeline2DexSupportMatrixEntry {
    return DemoPipeline2DexSupportMatrixEntry {
        code: entry.code,
        display_name: entry.display_name,
        family: entry.family,
        version: entry.version,
        surface_type: entry.surface_type,
        surface_role: entry.surface_role,
        program_id: entry.program_id,
        router_program_id: entry.router_program_id,
        program_id_status: entry.program_id_status,
        observed: entry.observed,
        decoded: entry.decoded,
        materialized: entry.materialized,
        trade_candidate: entry.trade_candidate,
        candle_candidate: entry.candle_candidate,
        pair_candidate: entry.pair_candidate,
        pool_candidate: entry.pool_candidate,
        status: entry.status,
        confidence: entry.confidence,
        skip_reason: entry.skip_reason,
        catalog_enabled: entry.catalog_enabled,
    };
}

fn demo_pipeline2_map_local_validation_issue(
    issue: kb_lib::LocalPipelineValidationIssueDto,
) -> DemoPipeline2LocalPipelineValidationIssue {
    return DemoPipeline2LocalPipelineValidationIssue {
        code: issue.code,
        message: issue.message,
        subject: issue.subject,
        blocking: issue.blocking,
    };
}

fn demo_pipeline2_map_local_diagnostics_summary(
    summary: kb_lib::LocalPipelineDiagnosticSummaryDto,
) -> DemoPipeline2LocalPipelineDiagnosticSummary {
    let mut dex_summaries = std::vec::Vec::new();
    for dex_summary in summary.dex_summaries {
        dex_summaries.push(demo_pipeline2_map_local_dex_diagnostic_summary(dex_summary));
    }
    let mut pair_summaries = std::vec::Vec::new();
    for pair_summary in summary.pair_summaries {
        pair_summaries.push(demo_pipeline2_map_local_pair_diagnostic_summary(pair_summary));
    }
    let mut pair_actionability_summaries = std::vec::Vec::new();
    for pair_actionability_summary in summary.pair_actionability_summaries {
        pair_actionability_summaries.push(
            demo_pipeline2_map_local_pair_actionability_diagnostic_summary(
                pair_actionability_summary,
            ),
        );
    }
    let mut pair_trading_readiness_summaries = std::vec::Vec::new();
    for pair_trading_readiness_summary in summary.pair_trading_readiness_summaries {
        pair_trading_readiness_summaries.push(
            demo_pipeline2_map_local_pair_trading_readiness_diagnostic_summary(
                pair_trading_readiness_summary,
            ),
        );
    }
    let mut decoded_event_summaries = std::vec::Vec::new();
    for decoded_event_summary in summary.decoded_event_summaries {
        decoded_event_summaries
            .push(demo_pipeline2_map_local_decoded_event_diagnostic_summary(decoded_event_summary));
    }
    let mut event_classification_summaries = std::vec::Vec::new();
    for classification_summary in summary.event_classification_summaries {
        event_classification_summaries.push(
            demo_pipeline2_map_local_event_classification_diagnostic_summary(
                classification_summary,
            ),
        );
    }
    let mut missing_trade_event_reason_summaries = std::vec::Vec::new();
    for reason_summary in summary.missing_trade_event_reason_summaries {
        missing_trade_event_reason_summaries
            .push(demo_pipeline2_map_missing_trade_event_reason_summary(reason_summary));
    }
    let mut non_actionable_pair_summaries = std::vec::Vec::new();
    for pair_summary in summary.non_actionable_pair_summaries {
        non_actionable_pair_summaries
            .push(demo_pipeline2_map_non_actionable_pair_diagnostic_summary(pair_summary));
    }
    let mut missing_trade_event_samples = std::vec::Vec::new();
    for sample in summary.missing_trade_event_samples {
        missing_trade_event_samples.push(demo_pipeline2_map_missing_trade_event_sample(sample));
    }

    let mut duplicate_decoded_event_trade_samples = std::vec::Vec::new();
    for sample in summary.duplicate_decoded_event_trade_samples {
        duplicate_decoded_event_trade_samples
            .push(demo_pipeline2_map_duplicate_decoded_event_trade_sample(sample));
    }
    let mut multi_trade_signature_pair_samples = std::vec::Vec::new();
    for sample in summary.multi_trade_signature_pair_samples {
        multi_trade_signature_pair_samples
            .push(demo_pipeline2_map_multi_trade_signature_pair_sample(sample));
    }
    let mut launch_origin_samples = std::vec::Vec::new();
    for sample in summary.launch_origin_samples {
        launch_origin_samples.push(demo_pipeline2_map_launch_origin_sample(sample));
    }
    let mut pool_origin_samples = std::vec::Vec::new();
    for sample in summary.pool_origin_samples {
        pool_origin_samples.push(demo_pipeline2_map_pool_origin_sample(sample));
    }
    let mut token_metadata_gap_samples = std::vec::Vec::new();
    for sample in summary.token_metadata_gap_samples {
        token_metadata_gap_samples.push(demo_pipeline2_map_token_metadata_gap_sample(sample));
    }
    let mut pair_without_trade_samples = std::vec::Vec::new();
    for sample in summary.pair_without_trade_samples {
        pair_without_trade_samples.push(demo_pipeline2_map_pair_gap_sample(sample));
    }
    let mut pair_without_candle_samples = std::vec::Vec::new();
    for sample in summary.pair_without_candle_samples {
        pair_without_candle_samples.push(demo_pipeline2_map_pair_gap_sample(sample));
    }
    DemoPipeline2LocalPipelineDiagnosticSummary {
        transaction_count: summary.transaction_count,
        ok_transaction_count: summary.ok_transaction_count,
        failed_transaction_count: summary.failed_transaction_count,
        decoded_event_count: summary.decoded_event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        decoded_candle_candidate_count: summary.decoded_candle_candidate_count,
        decoded_non_trade_useful_event_count: summary.decoded_non_trade_useful_event_count,
        decoded_non_actionable_trade_event_count: summary.decoded_non_actionable_trade_event_count,
        decoded_unknown_event_count: summary.decoded_unknown_event_count,
        liquidity_event_count: summary.liquidity_event_count,
        pool_lifecycle_event_count: summary.pool_lifecycle_event_count,
        fee_event_count: summary.fee_event_count,
        reward_event_count: summary.reward_event_count,
        pool_admin_event_count: summary.pool_admin_event_count,
        event_coverage_listed_entry_count: summary.event_coverage_listed_entry_count,
        event_coverage_decoded_entry_count: summary.event_coverage_decoded_entry_count,
        event_coverage_observed_entry_count: summary.event_coverage_observed_entry_count,
        event_coverage_materialized_entry_count: summary.event_coverage_materialized_entry_count,
        event_coverage_upstream_git_mapped_unverified_entry_count: summary
            .event_coverage_upstream_git_mapped_unverified_entry_count,
        diagnostics_clean: summary.diagnostics_clean,
        blocking_issue_count: summary.blocking_issue_count,
        missing_trade_event_count: summary.missing_trade_event_count,
        decoded_trade_candidate_without_trade_event_count: summary
            .decoded_trade_candidate_without_trade_event_count,
        decoded_trade_candidate_without_trade_event_on_ok_transaction_count: summary
            .decoded_trade_candidate_without_trade_event_on_ok_transaction_count,
        decoded_trade_candidate_without_trade_event_on_failed_transaction_count: summary
            .decoded_trade_candidate_without_trade_event_on_failed_transaction_count,
        actionable_missing_trade_event_count: summary.actionable_missing_trade_event_count,
        ignored_failed_transaction_trade_candidate_count: summary
            .ignored_failed_transaction_trade_candidate_count,
        decoded_trade_candidate_without_amount_payload_count: summary
            .decoded_trade_candidate_without_amount_payload_count,
        trade_event_count: summary.trade_event_count,
        invalid_trade_event_count: summary.invalid_trade_event_count,
        pair_candle_count: summary.pair_candle_count,
        duplicate_decoded_event_trade_count: summary.duplicate_decoded_event_trade_count,
        multi_trade_signature_pair_count: summary.multi_trade_signature_pair_count,
        duplicate_candle_bucket_count: summary.duplicate_candle_bucket_count,
        token_count: summary.token_count,
        token_metadata_missing_count: summary.token_metadata_missing_count,
        tradable_token_metadata_missing_count: summary.tradable_token_metadata_missing_count,
        quote_token_metadata_missing_count: summary.quote_token_metadata_missing_count,
        pair_symbol_fallback_count: summary.pair_symbol_fallback_count,
        pair_symbol_resolved_count: summary.pair_symbol_resolved_count,
        wsol_quote_pair_count: summary.wsol_quote_pair_count,
        stable_quote_pair_count: summary.stable_quote_pair_count,
        pool_count: summary.pool_count,
        pair_count: summary.pair_count,
        pair_gap_counter_semantics: summary.pair_gap_counter_semantics,
        literal_pair_without_trade_count: summary.literal_pair_without_trade_count,
        literal_pair_without_candle_count: summary.literal_pair_without_candle_count,
        trade_materialized_pair_count: summary.trade_materialized_pair_count,
        candle_materialized_pair_count: summary.candle_materialized_pair_count,
        actionable_pair_count: summary.actionable_pair_count,
        candle_bucket_timeframe_count: summary.candle_bucket_timeframe_count,
        candles_are_bucketed: summary.candles_are_bucketed,
        blocking_pair_without_trade_count: summary.blocking_pair_without_trade_count,
        blocking_pair_without_candle_count: summary.blocking_pair_without_candle_count,
        pair_without_trade_count: summary.pair_without_trade_count,
        pair_without_candle_count: summary.pair_without_candle_count,
        dex_summaries,
        pair_summaries,
        pair_actionability_summaries,
        pair_trading_readiness_summaries,
        decoded_event_summaries,
        event_classification_summaries,
        missing_trade_event_reason_summaries,
        launch_origin_samples,
        pool_origin_samples,
        token_metadata_gap_samples,
        non_actionable_pair_count: summary.non_actionable_pair_count,
        non_actionable_pair_summaries,
        missing_trade_event_samples,
        duplicate_decoded_event_trade_samples,
        multi_trade_signature_pair_samples,
        pair_without_trade_samples,
        pair_without_candle_samples,
    }
}

fn demo_pipeline2_map_local_dex_diagnostic_summary(
    summary: kb_lib::LocalDexDiagnosticSummaryDto,
) -> DemoPipeline2LocalDexDiagnosticSummary {
    DemoPipeline2LocalDexDiagnosticSummary {
        dex_code: summary.dex_code,
        pool_count: summary.pool_count,
        pair_count: summary.pair_count,
        decoded_event_count: summary.decoded_event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        decoded_candle_candidate_count: summary.decoded_candle_candidate_count,
        trade_event_count: summary.trade_event_count,
        pair_candle_count: summary.pair_candle_count,
    }
}

fn demo_pipeline2_map_local_pair_diagnostic_summary(
    summary: kb_lib::LocalPairDiagnosticSummaryDto,
) -> DemoPipeline2LocalPairDiagnosticSummary {
    DemoPipeline2LocalPairDiagnosticSummary {
        pair_id: summary.pair_id,
        pool_address: summary.pool_address,
        dex_code: summary.dex_code,
        base_mint: summary.base_mint,
        base_symbol: summary.base_symbol,
        quote_mint: summary.quote_mint,
        quote_symbol: summary.quote_symbol,
        pair_symbol: summary.pair_symbol,
        decoded_event_count: summary.decoded_event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        decoded_candle_candidate_count: summary.decoded_candle_candidate_count,
        trade_event_count: summary.trade_event_count,
        invalid_trade_event_count: summary.invalid_trade_event_count,
        pair_candle_count: summary.pair_candle_count,
        last_price_quote_per_base: summary.last_price_quote_per_base,
        pair_trading_readiness: summary.pair_trading_readiness,
        quote_asset_class: summary.quote_asset_class,
        trading_route_required: summary.trading_route_required,
    }
}

fn demo_pipeline2_map_local_pair_actionability_diagnostic_summary(
    summary: kb_lib::LocalPairActionabilityDiagnosticSummaryDto,
) -> DemoPipeline2LocalPairActionabilityDiagnosticSummary {
    DemoPipeline2LocalPairActionabilityDiagnosticSummary {
        pair_actionability: summary.pair_actionability,
        pair_count: summary.pair_count,
        decoded_event_count: summary.decoded_event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        actionable_trade_candidate_count: summary.actionable_trade_candidate_count,
        failed_trade_candidate_count: summary.failed_trade_candidate_count,
        trade_event_count: summary.trade_event_count,
        pair_candle_count: summary.pair_candle_count,
    }
}

fn demo_pipeline2_map_local_pair_trading_readiness_diagnostic_summary(
    summary: kb_lib::LocalPairTradingReadinessDiagnosticSummaryDto,
) -> DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
    DemoPipeline2LocalPairTradingReadinessDiagnosticSummary {
        pair_trading_readiness: summary.pair_trading_readiness,
        quote_asset_class: summary.quote_asset_class,
        trading_route_required: summary.trading_route_required,
        pair_count: summary.pair_count,
        decoded_event_count: summary.decoded_event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        trade_event_count: summary.trade_event_count,
        pair_candle_count: summary.pair_candle_count,
    }
}

fn demo_pipeline2_map_local_decoded_event_diagnostic_summary(
    summary: kb_lib::LocalDecodedEventDiagnosticSummaryDto,
) -> DemoPipeline2LocalDecodedEventDiagnosticSummary {
    DemoPipeline2LocalDecodedEventDiagnosticSummary {
        protocol_name: summary.protocol_name,
        event_kind: summary.event_kind,
        event_category: summary.event_category,
        event_lifecycle_kind: summary.event_lifecycle_kind,
        event_actionability: summary.event_actionability,
        non_trade_useful: summary.non_trade_useful,
        trade_candidate: summary.trade_candidate,
        candle_candidate: summary.candle_candidate,
        event_count: summary.event_count,
        trade_event_count: summary.trade_event_count,
    }
}

fn demo_pipeline2_map_local_event_classification_diagnostic_summary(
    summary: kb_lib::LocalEventClassificationDiagnosticSummaryDto,
) -> DemoPipeline2LocalEventClassificationDiagnosticSummary {
    DemoPipeline2LocalEventClassificationDiagnosticSummary {
        event_category: summary.event_category,
        event_lifecycle_kind: summary.event_lifecycle_kind,
        event_actionability: summary.event_actionability,
        non_trade_useful: summary.non_trade_useful,
        event_count: summary.event_count,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        decoded_candle_candidate_count: summary.decoded_candle_candidate_count,
        trade_event_count: summary.trade_event_count,
    }
}

fn demo_pipeline2_map_missing_trade_event_reason_summary(
    summary: kb_lib::LocalMissingTradeEventReasonSummaryDto,
) -> DemoPipeline2LocalMissingTradeEventReasonSummary {
    return DemoPipeline2LocalMissingTradeEventReasonSummary {
        reason: summary.reason,
        transaction_failed: summary.transaction_failed,
        actionable: summary.actionable,
        event_count: summary.event_count,
        has_base_amount_payload_count: summary.has_base_amount_payload_count,
        has_quote_amount_payload_count: summary.has_quote_amount_payload_count,
        has_price_payload_count: summary.has_price_payload_count,
    };
}

fn demo_pipeline2_map_non_actionable_pair_diagnostic_summary(
    summary: kb_lib::LocalNonActionablePairDiagnosticSummaryDto,
) -> DemoPipeline2LocalNonActionablePairDiagnosticSummary {
    return DemoPipeline2LocalNonActionablePairDiagnosticSummary {
        pair_id: summary.pair_id,
        pool_address: summary.pool_address,
        dex_code: summary.dex_code,
        base_mint: summary.base_mint,
        base_symbol: summary.base_symbol,
        quote_mint: summary.quote_mint,
        quote_symbol: summary.quote_symbol,
        pair_symbol: summary.pair_symbol,
        decoded_trade_candidate_count: summary.decoded_trade_candidate_count,
        non_actionable_missing_trade_event_count: summary.non_actionable_missing_trade_event_count,
        failed_transaction_candidate_count: summary.failed_transaction_candidate_count,
        ok_transaction_without_token_mints_count: summary.ok_transaction_without_token_mints_count,
        ok_transaction_without_amount_payload_count: summary
            .ok_transaction_without_amount_payload_count,
        reason_summary: summary.reason_summary,
        trade_event_count: summary.trade_event_count,
        pair_candle_count: summary.pair_candle_count,
    };
}

fn demo_pipeline2_map_missing_trade_event_sample(
    sample: kb_lib::LocalMissingTradeEventDiagnosticSampleDto,
) -> DemoPipeline2LocalMissingTradeEventDiagnosticSample {
    return DemoPipeline2LocalMissingTradeEventDiagnosticSample {
        decoded_event_id: sample.decoded_event_id,
        transaction_id: sample.transaction_id,
        signature: sample.signature,
        protocol_name: sample.protocol_name,
        event_kind: sample.event_kind,
        pool_account: sample.pool_account,
        transaction_failed: sample.transaction_failed,
        actionable: sample.actionable,
        reason: sample.reason,
        has_base_amount_payload: sample.has_base_amount_payload,
        has_quote_amount_payload: sample.has_quote_amount_payload,
        has_price_payload: sample.has_price_payload,
    };
}

fn demo_pipeline2_map_duplicate_decoded_event_trade_sample(
    sample: kb_lib::LocalDuplicateDecodedEventTradeDiagnosticSampleDto,
) -> DemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample {
    return DemoPipeline2LocalDuplicateDecodedEventTradeDiagnosticSample {
        decoded_event_id: sample.decoded_event_id,
        protocol_name: sample.protocol_name,
        event_kind: sample.event_kind,
        pool_account: sample.pool_account,
        trade_event_count: sample.trade_event_count,
        trade_event_ids: sample.trade_event_ids,
        signatures: sample.signatures,
    };
}

fn demo_pipeline2_map_multi_trade_signature_pair_sample(
    sample: kb_lib::LocalMultiTradeSignaturePairDiagnosticSampleDto,
) -> DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample {
    return DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample {
        signature: sample.signature,
        pair_id: sample.pair_id,
        pool_address: sample.pool_address,
        dex_code: sample.dex_code,
        trade_event_count: sample.trade_event_count,
        decoded_event_count: sample.decoded_event_count,
        trade_event_ids: sample.trade_event_ids,
        decoded_event_ids: sample.decoded_event_ids,
    };
}

fn demo_pipeline2_map_launch_origin_sample(
    sample: kb_lib::LocalLaunchOriginDiagnosticSampleDto,
) -> DemoPipeline2LocalLaunchOriginDiagnosticSample {
    return DemoPipeline2LocalLaunchOriginDiagnosticSample {
        launch_attribution_id: sample.launch_attribution_id,
        launch_surface_code: sample.launch_surface_code,
        launch_surface_name: sample.launch_surface_name,
        transaction_signature: sample.transaction_signature,
        decoded_event_id: sample.decoded_event_id,
        protocol_name: sample.protocol_name,
        match_kind: sample.match_kind,
        matched_value: sample.matched_value,
        pool_id: sample.pool_id,
        pool_address: sample.pool_address,
        pair_id: sample.pair_id,
        pair_symbol: sample.pair_symbol,
    };
}

fn demo_pipeline2_map_pool_origin_sample(
    sample: kb_lib::LocalPoolOriginDiagnosticSampleDto,
) -> DemoPipeline2LocalPoolOriginDiagnosticSample {
    return DemoPipeline2LocalPoolOriginDiagnosticSample {
        pool_origin_id: sample.pool_origin_id,
        dex_code: sample.dex_code,
        pool_id: sample.pool_id,
        pool_address: sample.pool_address,
        pair_id: sample.pair_id,
        pair_symbol: sample.pair_symbol,
        launch_surface_code: sample.launch_surface_code,
        founding_signature: sample.founding_signature,
        founding_protocol_name: sample.founding_protocol_name,
        founding_event_kind: sample.founding_event_kind,
    };
}

fn demo_pipeline2_map_token_metadata_gap_sample(
    sample: kb_lib::LocalTokenMetadataGapDiagnosticSampleDto,
) -> DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
    return DemoPipeline2LocalTokenMetadataGapDiagnosticSample {
        token_id: sample.token_id,
        mint: sample.mint,
        symbol: sample.symbol,
        name: sample.name,
        decimals: sample.decimals,
        token_program: sample.token_program,
        is_quote_token: sample.is_quote_token,
        used_by_trade_materialized_pair: sample.used_by_trade_materialized_pair,
        used_as_quote_token: sample.used_as_quote_token,
        trade_materialized_pair_count: sample.trade_materialized_pair_count,
        total_pair_count: sample.total_pair_count,
        priority: sample.priority,
    };
}

fn demo_pipeline2_map_pair_gap_sample(
    sample: kb_lib::LocalPairGapDiagnosticSampleDto,
) -> DemoPipeline2LocalPairGapDiagnosticSample {
    return DemoPipeline2LocalPairGapDiagnosticSample {
        pair_id: sample.pair_id,
        pool_address: sample.pool_address,
        dex_code: sample.dex_code,
        base_mint: sample.base_mint,
        base_symbol: sample.base_symbol,
        quote_mint: sample.quote_mint,
        quote_symbol: sample.quote_symbol,
        pair_symbol: sample.pair_symbol,
        decoded_event_count: sample.decoded_event_count,
        decoded_trade_candidate_count: sample.decoded_trade_candidate_count,
        trade_event_count: sample.trade_event_count,
        pair_candle_count: sample.pair_candle_count,
    };
}

fn demo_pipeline2_normalize_http_role(
    role: std::option::Option<std::string::String>,
) -> std::string::String {
    match role {
        Some(role) => {
            let trimmed = role.trim().to_string();
            if trimmed.is_empty() { "history_backfill".to_string() } else { trimmed }
        },
        None => "history_backfill".to_string(),
    }
}
