This commit is contained in:
2026-05-13 11:17:53 +02:00
parent 69385094ff
commit 24d21818cf
23 changed files with 736 additions and 65 deletions

View File

@@ -28,6 +28,7 @@ mod pool_listing;
mod pool_origin;
mod pool_token;
mod program_instruction_diagnostic;
mod program_instruction_discriminator_summary;
mod protocol_candidate;
mod protocol_candidate_summary;
mod swap;
@@ -38,22 +39,21 @@ mod trade_event;
mod transaction_classification;
mod wallet;
mod wallet_holding;
mod program_instruction_discriminator_summary;
mod wallet_participation;
pub(crate) use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalDexDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalMissingTradeEventDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairDiagnosticSummaryRow;
pub(crate) use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleRow;
pub(crate) use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersRow;
pub use program_instruction_discriminator_summary::ProgramInstructionDiscriminatorSummaryDto;
pub use analysis_signal::AnalysisSignalDto;
pub use chain_instruction::ChainInstructionDto;
pub use chain_slot::ChainSlotDto;
@@ -69,13 +69,14 @@ pub use launch_surface::LaunchSurfaceDto;
pub use launch_surface_key::LaunchSurfaceKeyDto;
pub use liquidity_event::LiquidityEventDto;
pub use local_pipeline_diagnostics::LocalDecodedEventDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDexDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalDuplicateDecodedEventTradeDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalEventClassificationDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalMissingTradeEventReasonSummaryDto;
pub use local_pipeline_diagnostics::LocalMultiTradeSignaturePairDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalNonActionablePairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairActionabilityDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairDiagnosticSummaryDto;
pub use local_pipeline_diagnostics::LocalPairGapDiagnosticSampleDto;
pub use local_pipeline_diagnostics::LocalPipelineDiagnosticCountersDto;
@@ -91,6 +92,7 @@ pub use pool_listing::PoolListingDto;
pub use pool_origin::PoolOriginDto;
pub use pool_token::PoolTokenDto;
pub use program_instruction_diagnostic::ProgramInstructionDiagnosticDto;
pub use program_instruction_discriminator_summary::ProgramInstructionDiscriminatorSummaryDto;
pub use protocol_candidate::ProtocolCandidateDto;
pub use protocol_candidate_summary::ProtocolCandidateSummaryDto;
pub use swap::SwapDto;

View File

@@ -65,14 +65,46 @@ pub struct LocalPipelineDiagnosticSummaryDto {
pub pool_count: i64,
/// Total known pairs.
pub pair_count: i64,
/// Stable explanation for legacy pair gap counters.
///
/// `pair_without_trade_count` and `pair_without_candle_count` are blocking
/// actionable gap counters, not literal catalog-wide counters.
pub pair_gap_counter_semantics: std::string::String,
/// Total pairs without any persisted trade event, including catalog-only and partial DEX pairs.
pub literal_pair_without_trade_count: i64,
/// Total pairs without any persisted candle, including catalog-only and partial DEX pairs.
pub literal_pair_without_candle_count: i64,
/// Total pairs that have at least one persisted trade event.
pub trade_materialized_pair_count: i64,
/// Total pairs that have at least one persisted candle bucket.
pub candle_materialized_pair_count: i64,
/// Total pairs with at least one successful decoded trade candidate.
pub actionable_pair_count: i64,
/// Total distinct candle timeframes currently materialized.
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 event among actionable successful trade candidates.
pub blocking_pair_without_trade_count: i64,
/// Total pairs without candle among actionable successful candle candidates.
pub blocking_pair_without_candle_count: i64,
/// Total pairs without trade event.
///
/// Legacy alias kept for compatibility. It has the same semantics as
/// `blocking_pair_without_trade_count`.
pub pair_without_trade_count: i64,
/// Total pairs without candle.
///
/// Legacy alias kept for compatibility. It has the same semantics as
/// `blocking_pair_without_candle_count`.
pub pair_without_candle_count: i64,
/// Diagnostics grouped by DEX.
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
/// Diagnostics grouped by pair.
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>,
/// Diagnostics grouped by pair materialization/actionability class.
pub pair_actionability_summaries:
std::vec::Vec<crate::LocalPairActionabilityDiagnosticSummaryDto>,
/// Diagnostics grouped by decoded event kind.
pub decoded_event_summaries: std::vec::Vec<crate::LocalDecodedEventDiagnosticSummaryDto>,
/// Diagnostics grouped by decoded event category, lifecycle kind and actionability.
@@ -157,6 +189,27 @@ pub struct LocalPairDiagnosticSummaryDto {
pub last_price_quote_per_base: std::option::Option<f64>,
}
/// Local pair diagnostics grouped by materialization/actionability class.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalPairActionabilityDiagnosticSummaryDto {
/// Pair actionability or materialization class.
pub pair_actionability: std::string::String,
/// Total pairs in this class.
pub pair_count: i64,
/// Total decoded events attached to pairs in this class.
pub decoded_event_count: i64,
/// Total decoded trade candidates attached to pairs in this class.
pub decoded_trade_candidate_count: i64,
/// Total decoded trade candidates on successful transactions attached to pairs in this class.
pub actionable_trade_candidate_count: i64,
/// Total decoded trade candidates on failed transactions attached to pairs in this class.
pub failed_trade_candidate_count: i64,
/// Total persisted trade events attached to pairs in this class.
pub trade_event_count: i64,
/// Total persisted candle buckets attached to pairs in this class.
pub pair_candle_count: i64,
}
/// Local decoded-event diagnostics summary.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDecodedEventDiagnosticSummaryDto {
@@ -314,8 +367,24 @@ pub struct LocalPipelineDiagnosticCountersDto {
pub pool_count: i64,
/// Total known pairs.
pub pair_count: i64,
/// Total pairs without any persisted trade event, including catalog-only and partial DEX pairs.
pub literal_pair_without_trade_count: i64,
/// Total pairs without any persisted candle, including catalog-only and partial DEX pairs.
pub literal_pair_without_candle_count: i64,
/// Total pairs that have at least one persisted trade event.
pub trade_materialized_pair_count: i64,
/// Total pairs that have at least one persisted candle bucket.
pub candle_materialized_pair_count: i64,
/// Total pairs with at least one successful decoded trade candidate.
pub actionable_pair_count: i64,
/// Total distinct candle timeframes currently materialized.
pub candle_bucket_timeframe_count: i64,
/// Total pairs with only non-actionable missing trade events.
pub non_actionable_pair_count: i64,
/// Total pairs without trade among actionable successful trade candidates.
pub blocking_pair_without_trade_count: i64,
/// Total pairs without candle among actionable successful candle candidates.
pub blocking_pair_without_candle_count: i64,
/// Total pairs without trade.
pub pair_without_trade_count: i64,
/// Total pairs without candle.
@@ -351,7 +420,15 @@ pub(crate) struct LocalPipelineDiagnosticCountersRow {
pub(crate) token_metadata_missing_count: i64,
pub(crate) pool_count: i64,
pub(crate) pair_count: i64,
pub(crate) literal_pair_without_trade_count: i64,
pub(crate) literal_pair_without_candle_count: i64,
pub(crate) trade_materialized_pair_count: i64,
pub(crate) candle_materialized_pair_count: i64,
pub(crate) actionable_pair_count: i64,
pub(crate) candle_bucket_timeframe_count: i64,
pub(crate) non_actionable_pair_count: i64,
pub(crate) blocking_pair_without_trade_count: i64,
pub(crate) blocking_pair_without_candle_count: i64,
pub(crate) pair_without_trade_count: i64,
pub(crate) pair_without_candle_count: i64,
}
@@ -389,6 +466,19 @@ pub(crate) struct LocalPairDiagnosticSummaryRow {
pub(crate) last_price_quote_per_base: std::option::Option<f64>,
}
/// SQL row for local pair actionability diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalPairActionabilityDiagnosticSummaryRow {
pub(crate) pair_actionability: std::string::String,
pub(crate) pair_count: i64,
pub(crate) decoded_event_count: i64,
pub(crate) decoded_trade_candidate_count: i64,
pub(crate) actionable_trade_candidate_count: i64,
pub(crate) failed_trade_candidate_count: i64,
pub(crate) trade_event_count: i64,
pub(crate) pair_candle_count: i64,
}
/// SQL row for local decoded-event diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDecodedEventDiagnosticSummaryRow {

View File

@@ -1,4 +1,3 @@
// file: kb_lib/src/db/entities/program_instruction_discriminator_row.rs
//! Program instruction discriminator diagnostic row entity.

View File

@@ -88,6 +88,7 @@ pub use local_pipeline_diagnostics::query_local_missing_trade_event_diagnostic_l
pub use local_pipeline_diagnostics::query_local_missing_trade_event_reason_list_summaries;
pub use local_pipeline_diagnostics::query_local_multi_trade_signature_pair_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_non_actionable_pair_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_actionability_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_diagnostic_list_summaries;
pub use local_pipeline_diagnostics::query_local_pair_without_candle_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_pair_without_trade_diagnostic_list_samples;

View File

@@ -188,6 +188,51 @@ SELECT
) AS token_metadata_missing_count,
(SELECT COUNT(*) FROM k_sol_pools) AS pool_count,
(SELECT COUNT(*) FROM k_sol_pairs) AS pair_count,
(
SELECT COUNT(*)
FROM (
SELECT pair.id
FROM k_sol_pairs pair
LEFT JOIN k_sol_trade_events te ON te.pair_id = pair.id
GROUP BY pair.id
HAVING COUNT(DISTINCT te.id) = 0
)
) AS literal_pair_without_trade_count,
(
SELECT COUNT(*)
FROM (
SELECT pair.id
FROM k_sol_pairs pair
LEFT JOIN k_sol_pair_candles pc ON pc.pair_id = pair.id
GROUP BY pair.id
HAVING COUNT(DISTINCT pc.bucket_start_unix || ':' || pc.timeframe_seconds) = 0
)
) AS literal_pair_without_candle_count,
(
SELECT COUNT(DISTINCT pair_id)
FROM k_sol_trade_events
) AS trade_materialized_pair_count,
(
SELECT COUNT(DISTINCT pair_id)
FROM k_sol_pair_candles
) AS candle_materialized_pair_count,
(
SELECT COUNT(*)
FROM (
SELECT pair.id
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
JOIN k_sol_dex_decoded_events dde ON dde.pool_account = p.address
JOIN k_sol_chain_transactions ct ON ct.id = dde.transaction_id
WHERE json_extract(dde.payload_json, '$.tradeCandidate') = 1
AND ct.err_json IS NULL
GROUP BY pair.id
)
) AS actionable_pair_count,
(
SELECT COUNT(DISTINCT timeframe_seconds)
FROM k_sol_pair_candles
) AS candle_bucket_timeframe_count,
(
SELECT COUNT(*)
FROM (
@@ -216,6 +261,24 @@ SELECT
AND COUNT(DISTINCT dde.id) > 0
)
) AS non_actionable_pair_count,
(
SELECT COUNT(*)
FROM (
SELECT pair.id
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
LEFT JOIN k_sol_dex_decoded_events dde ON dde.pool_account = p.address
LEFT JOIN k_sol_chain_transactions ct ON ct.id = dde.transaction_id
LEFT JOIN k_sol_trade_events te ON te.pair_id = pair.id
GROUP BY pair.id
HAVING COUNT(DISTINCT CASE
WHEN json_extract(dde.payload_json, '$.tradeCandidate') = 1
AND ct.id IS NOT NULL
AND ct.err_json IS NULL
THEN dde.id END) > 0
AND COUNT(DISTINCT te.id) = 0
)
) AS blocking_pair_without_trade_count,
(
SELECT COUNT(*)
FROM (
@@ -234,6 +297,24 @@ SELECT
AND COUNT(DISTINCT te.id) = 0
)
) AS pair_without_trade_count,
(
SELECT COUNT(*)
FROM (
SELECT pair.id
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
LEFT JOIN k_sol_dex_decoded_events dde ON dde.pool_account = p.address
LEFT JOIN k_sol_chain_transactions ct ON ct.id = dde.transaction_id
LEFT JOIN k_sol_pair_candles pc ON pc.pair_id = pair.id
GROUP BY pair.id
HAVING COUNT(DISTINCT CASE
WHEN json_extract(dde.payload_json, '$.candleCandidate') = 1
AND ct.id IS NOT NULL
AND ct.err_json IS NULL
THEN dde.id END) > 0
AND COUNT(DISTINCT pc.bucket_start_unix || ':' || pc.timeframe_seconds) = 0
)
) AS blocking_pair_without_candle_count,
(
SELECT COUNT(*)
FROM (
@@ -295,7 +376,15 @@ SELECT
token_metadata_missing_count: row.token_metadata_missing_count,
pool_count: row.pool_count,
pair_count: row.pair_count,
literal_pair_without_trade_count: row.literal_pair_without_trade_count,
literal_pair_without_candle_count: row.literal_pair_without_candle_count,
trade_materialized_pair_count: row.trade_materialized_pair_count,
candle_materialized_pair_count: row.candle_materialized_pair_count,
actionable_pair_count: row.actionable_pair_count,
candle_bucket_timeframe_count: row.candle_bucket_timeframe_count,
non_actionable_pair_count: row.non_actionable_pair_count,
blocking_pair_without_trade_count: row.blocking_pair_without_trade_count,
blocking_pair_without_candle_count: row.blocking_pair_without_candle_count,
pair_without_trade_count: row.pair_without_trade_count,
pair_without_candle_count: row.pair_without_candle_count,
actionable_missing_trade_event_count: row.actionable_missing_trade_event_count,
@@ -523,6 +612,112 @@ ORDER BY pair.id
}
}
/// Lists local pair materialization/actionability summaries.
pub async fn query_local_pair_actionability_diagnostic_list_summaries(
database: &crate::Database,
) -> Result<std::vec::Vec<crate::LocalPairActionabilityDiagnosticSummaryDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let rows_result = sqlx::query_as::<
sqlx::Sqlite,
crate::db::dtos::LocalPairActionabilityDiagnosticSummaryRow,
>(
r#"
WITH pair_state AS (
SELECT
pair.id AS pair_id,
COUNT(DISTINCT dde.id) AS decoded_event_count,
COUNT(DISTINCT CASE WHEN json_extract(dde.payload_json, '$.tradeCandidate') = 1 THEN dde.id END) AS decoded_trade_candidate_count,
COUNT(DISTINCT CASE
WHEN json_extract(dde.payload_json, '$.tradeCandidate') = 1
AND ct.id IS NOT NULL
AND ct.err_json IS NULL
THEN dde.id END) AS actionable_trade_candidate_count,
COUNT(DISTINCT CASE
WHEN json_extract(dde.payload_json, '$.tradeCandidate') = 1
AND ct.id IS NOT NULL
AND ct.err_json IS NOT NULL
THEN dde.id END) AS failed_trade_candidate_count,
COUNT(DISTINCT te.id) AS trade_event_count,
COUNT(DISTINCT pc.bucket_start_unix || ':' || pc.timeframe_seconds) AS pair_candle_count
FROM k_sol_pairs pair
JOIN k_sol_pools p ON p.id = pair.pool_id
LEFT JOIN k_sol_dex_decoded_events dde ON dde.pool_account = p.address
LEFT JOIN k_sol_chain_transactions ct ON ct.id = dde.transaction_id
LEFT JOIN k_sol_trade_events te ON te.pair_id = pair.id
LEFT JOIN k_sol_pair_candles pc ON pc.pair_id = pair.id
GROUP BY pair.id
), classified AS (
SELECT
CASE
WHEN trade_event_count > 0 THEN 'trade_materialized'
WHEN actionable_trade_candidate_count > 0 THEN 'actionable_without_materialized_trade'
WHEN failed_trade_candidate_count > 0 THEN 'failed_trade_candidate_only'
WHEN decoded_trade_candidate_count > 0 THEN 'non_actionable_trade_candidate_only'
WHEN decoded_event_count > 0 THEN 'decoded_without_trade_candidate'
ELSE 'catalog_only'
END AS pair_actionability,
pair_id,
decoded_event_count,
decoded_trade_candidate_count,
actionable_trade_candidate_count,
failed_trade_candidate_count,
trade_event_count,
pair_candle_count
FROM pair_state
)
SELECT
pair_actionability AS pair_actionability,
COUNT(pair_id) AS pair_count,
SUM(decoded_event_count) AS decoded_event_count,
SUM(decoded_trade_candidate_count) AS decoded_trade_candidate_count,
SUM(actionable_trade_candidate_count) AS actionable_trade_candidate_count,
SUM(failed_trade_candidate_count) AS failed_trade_candidate_count,
SUM(trade_event_count) AS trade_event_count,
SUM(pair_candle_count) AS pair_candle_count
FROM classified
GROUP BY pair_actionability
ORDER BY
CASE pair_actionability
WHEN 'trade_materialized' THEN 1
WHEN 'actionable_without_materialized_trade' THEN 2
WHEN 'failed_trade_candidate_only' THEN 3
WHEN 'non_actionable_trade_candidate_only' THEN 4
WHEN 'decoded_without_trade_candidate' THEN 5
ELSE 6
END,
pair_actionability
"#,
)
.fetch_all(pool)
.await;
let rows = match rows_result {
Ok(rows) => rows,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list local pair actionability diagnostic summaries on sqlite: {}",
error
)));
},
};
let mut summaries = std::vec::Vec::new();
for row in rows {
summaries.push(crate::LocalPairActionabilityDiagnosticSummaryDto {
pair_actionability: row.pair_actionability,
pair_count: row.pair_count,
decoded_event_count: row.decoded_event_count,
decoded_trade_candidate_count: row.decoded_trade_candidate_count,
actionable_trade_candidate_count: row.actionable_trade_candidate_count,
failed_trade_candidate_count: row.failed_trade_candidate_count,
trade_event_count: row.trade_event_count,
pair_candle_count: row.pair_candle_count,
});
}
return Ok(summaries);
},
}
}
/// Lists local decoded-event diagnostic summaries.
pub async fn query_local_decoded_event_diagnostic_list_summaries(
database: &crate::Database,