0.7.32
This commit is contained in:
@@ -37,6 +37,7 @@ pub use dtos::LocalMissingTradeEventDiagnosticSampleDto;
|
||||
pub use dtos::LocalMissingTradeEventReasonSummaryDto;
|
||||
pub use dtos::LocalMultiTradeSignaturePairDiagnosticSampleDto;
|
||||
pub use dtos::LocalNonActionablePairDiagnosticSummaryDto;
|
||||
pub use dtos::LocalPairActionabilityDiagnosticSummaryDto;
|
||||
pub use dtos::LocalPairDiagnosticSummaryDto;
|
||||
pub use dtos::LocalPairGapDiagnosticSampleDto;
|
||||
pub use dtos::LocalPipelineDiagnosticCountersDto;
|
||||
@@ -150,6 +151,7 @@ pub use queries::query_local_missing_trade_event_diagnostic_list_samples;
|
||||
pub use queries::query_local_missing_trade_event_reason_list_summaries;
|
||||
pub use queries::query_local_multi_trade_signature_pair_diagnostic_list_samples;
|
||||
pub use queries::query_local_non_actionable_pair_diagnostic_list_summaries;
|
||||
pub use queries::query_local_pair_actionability_diagnostic_list_summaries;
|
||||
pub use queries::query_local_pair_diagnostic_list_summaries;
|
||||
pub use queries::query_local_pair_without_candle_diagnostic_list_samples;
|
||||
pub use queries::query_local_pair_without_trade_diagnostic_list_samples;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
// file: kb_lib/src/db/entities/program_instruction_discriminator_row.rs
|
||||
|
||||
//! Program instruction discriminator diagnostic row entity.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -848,19 +848,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn matrix_lookup_by_program_id_returns_expected_protocol() {
|
||||
let entry = match crate::dex_support_matrix_entry_by_program_id(crate::METEORA_DLMM_PROGRAM_ID)
|
||||
{
|
||||
Some(entry) => entry,
|
||||
None => panic!("expected meteora_dlmm program id lookup"),
|
||||
};
|
||||
let entry =
|
||||
match crate::dex_support_matrix_entry_by_program_id(crate::METEORA_DLMM_PROGRAM_ID) {
|
||||
Some(entry) => entry,
|
||||
None => panic!("expected meteora_dlmm program id lookup"),
|
||||
};
|
||||
assert_eq!(entry.code, "meteora_dlmm");
|
||||
|
||||
let raydium_entry = match crate::dex_support_matrix_entry_by_program_id(
|
||||
crate::RAYDIUM_AMM_V4_PROGRAM_ID,
|
||||
) {
|
||||
Some(entry) => entry,
|
||||
None => panic!("expected raydium AMM v4 program id lookup"),
|
||||
};
|
||||
let raydium_entry =
|
||||
match crate::dex_support_matrix_entry_by_program_id(crate::RAYDIUM_AMM_V4_PROGRAM_ID) {
|
||||
Some(entry) => entry,
|
||||
None => panic!("expected raydium AMM v4 program id lookup"),
|
||||
};
|
||||
assert_eq!(raydium_entry.code, "raydium_amm_v4");
|
||||
}
|
||||
|
||||
|
||||
@@ -357,6 +357,8 @@ pub use db::LocalMissingTradeEventReasonSummaryDto;
|
||||
pub use db::LocalMultiTradeSignaturePairDiagnosticSampleDto;
|
||||
/// Local pair diagnostics for pairs whose missing trade events are non-actionable.
|
||||
pub use db::LocalNonActionablePairDiagnosticSummaryDto;
|
||||
/// Local pair diagnostics grouped by materialization/actionability class.
|
||||
pub use db::LocalPairActionabilityDiagnosticSummaryDto;
|
||||
/// Local pair diagnostics summary.
|
||||
pub use db::LocalPairDiagnosticSummaryDto;
|
||||
/// Sample of a pair gap.
|
||||
@@ -571,6 +573,8 @@ pub use db::query_local_missing_trade_event_reason_list_summaries;
|
||||
pub use db::query_local_multi_trade_signature_pair_diagnostic_list_samples;
|
||||
/// Lists pair summaries for non-actionable missing trade events.
|
||||
pub use db::query_local_non_actionable_pair_diagnostic_list_summaries;
|
||||
/// Lists local pair materialization/actionability summaries.
|
||||
pub use db::query_local_pair_actionability_diagnostic_list_summaries;
|
||||
/// Lists local pair diagnostic summaries.
|
||||
pub use db::query_local_pair_diagnostic_list_summaries;
|
||||
/// Lists samples of pairs without candles.
|
||||
|
||||
@@ -35,6 +35,13 @@ impl LocalPipelineDiagnosticsService {
|
||||
Ok(pair_summaries) => pair_summaries,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let pair_actionability_summaries_result =
|
||||
crate::query_local_pair_actionability_diagnostic_list_summaries(self.database.as_ref())
|
||||
.await;
|
||||
let pair_actionability_summaries = match pair_actionability_summaries_result {
|
||||
Ok(summaries) => summaries,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let decoded_event_summaries_result =
|
||||
crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref())
|
||||
.await;
|
||||
@@ -160,10 +167,21 @@ impl LocalPipelineDiagnosticsService {
|
||||
token_metadata_missing_count: counters.token_metadata_missing_count,
|
||||
pool_count: counters.pool_count,
|
||||
pair_count: counters.pair_count,
|
||||
pair_gap_counter_semantics: "blocking_actionable_pairs_only".to_string(),
|
||||
literal_pair_without_trade_count: counters.literal_pair_without_trade_count,
|
||||
literal_pair_without_candle_count: counters.literal_pair_without_candle_count,
|
||||
trade_materialized_pair_count: counters.trade_materialized_pair_count,
|
||||
candle_materialized_pair_count: counters.candle_materialized_pair_count,
|
||||
actionable_pair_count: counters.actionable_pair_count,
|
||||
candle_bucket_timeframe_count: counters.candle_bucket_timeframe_count,
|
||||
candles_are_bucketed: counters.candle_bucket_timeframe_count > 0,
|
||||
blocking_pair_without_trade_count: counters.blocking_pair_without_trade_count,
|
||||
blocking_pair_without_candle_count: counters.blocking_pair_without_candle_count,
|
||||
pair_without_trade_count: counters.pair_without_trade_count,
|
||||
pair_without_candle_count: counters.pair_without_candle_count,
|
||||
dex_summaries,
|
||||
pair_summaries,
|
||||
pair_actionability_summaries,
|
||||
decoded_event_summaries,
|
||||
event_classification_summaries,
|
||||
missing_trade_event_reason_summaries,
|
||||
|
||||
@@ -40,6 +40,8 @@ pub struct LocalPipelineValidationConfig {
|
||||
pub require_candles_per_dex: bool,
|
||||
/// Whether non-actionable classification groups must have no linked trade events.
|
||||
pub require_no_non_actionable_trade_events_materialized: bool,
|
||||
/// Whether the DEX support matrix must satisfy internal semantic invariants.
|
||||
pub require_dex_support_matrix_semantics: bool,
|
||||
}
|
||||
|
||||
impl Default for LocalPipelineValidationConfig {
|
||||
@@ -59,6 +61,7 @@ impl Default for LocalPipelineValidationConfig {
|
||||
require_trade_events_per_dex: true,
|
||||
require_candles_per_dex: true,
|
||||
require_no_non_actionable_trade_events_materialized: true,
|
||||
require_dex_support_matrix_semantics: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -86,6 +89,7 @@ impl LocalPipelineValidationConfig {
|
||||
require_trade_events_per_dex: true,
|
||||
require_candles_per_dex: true,
|
||||
require_no_non_actionable_trade_events_materialized: true,
|
||||
require_dex_support_matrix_semantics: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ impl LocalPipelineValidationConfig {
|
||||
require_trade_events_per_dex: false,
|
||||
require_candles_per_dex: false,
|
||||
require_no_non_actionable_trade_events_materialized: true,
|
||||
require_dex_support_matrix_semantics: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,6 +154,7 @@ impl LocalPipelineValidationConfig {
|
||||
require_trade_events_per_dex: false,
|
||||
require_candles_per_dex: false,
|
||||
require_no_non_actionable_trade_events_materialized: true,
|
||||
require_dex_support_matrix_semantics: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,6 +180,17 @@ impl LocalPipelineValidationConfig {
|
||||
config.require_no_non_actionable_trade_events_materialized = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
/// Builds the `0.7.32` validation report semantics config.
|
||||
///
|
||||
/// This profile keeps the `0.7.31` trade-actionability policy and validates
|
||||
/// the support matrix semantics used to interpret partial/planned DEXes.
|
||||
pub fn v0_7_32_validation_report_semantics() -> Self {
|
||||
let mut config = Self::v0_7_31_trade_event_actionability_policy();
|
||||
config.profile_code = "0.7.32_validation_report_semantics".to_string();
|
||||
config.require_dex_support_matrix_semantics = true;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/// A single local pipeline validation issue.
|
||||
@@ -318,6 +335,14 @@ impl LocalPipelineValidationService {
|
||||
crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
|
||||
/// Diagnoses the current database with the `0.7.32` validation report semantics profile.
|
||||
pub async fn validate_v0_7_32_current_database(
|
||||
&self,
|
||||
) -> Result<crate::LocalPipelineValidationRunDto, crate::Error> {
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_32_validation_report_semantics();
|
||||
return self.validate_current_database(&config).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates a diagnostics summary without performing database access.
|
||||
@@ -429,6 +454,9 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.require_dex_support_matrix_semantics {
|
||||
validate_dex_support_matrix_semantics(&mut issues);
|
||||
}
|
||||
if config.require_all_expected_dexes {
|
||||
for expected_dex_code in &expected_dex_codes {
|
||||
if !observed_dex_codes.contains(expected_dex_code) {
|
||||
@@ -504,6 +532,65 @@ pub fn validate_local_pipeline_diagnostics_summary(
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_dex_support_matrix_semantics(
|
||||
issues: &mut std::vec::Vec<crate::LocalPipelineValidationIssueDto>,
|
||||
) {
|
||||
let entries = crate::dex_support_matrix_entry_dtos();
|
||||
for entry in entries {
|
||||
let entry_code = entry.code.clone();
|
||||
if entry.status == "supported" {
|
||||
let supported_materialization_ok = entry.decoded
|
||||
&& entry.materialized
|
||||
&& entry.trade_candidate
|
||||
&& entry.candle_candidate
|
||||
&& entry.pair_candidate
|
||||
&& entry.pool_candidate
|
||||
&& entry.catalog_enabled;
|
||||
if !supported_materialization_ok {
|
||||
issues.push(crate::LocalPipelineValidationIssueDto {
|
||||
code: "supported_dex_matrix_entry_not_fully_materialized".to_string(),
|
||||
message: format!(
|
||||
"supported DEX '{}' must expose decoded/materialized trade, candle, pair and pool capabilities",
|
||||
entry_code
|
||||
),
|
||||
subject: Some(entry_code.clone()),
|
||||
blocking: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if entry.status == "partial" {
|
||||
let has_skip_reason = match &entry.skip_reason {
|
||||
Some(skip_reason) => !skip_reason.trim().is_empty(),
|
||||
None => false,
|
||||
};
|
||||
if !has_skip_reason {
|
||||
issues.push(crate::LocalPipelineValidationIssueDto {
|
||||
code: "partial_dex_matrix_entry_without_skip_reason".to_string(),
|
||||
message: format!(
|
||||
"partial DEX '{}' must expose an explicit skip reason",
|
||||
entry_code
|
||||
),
|
||||
subject: Some(entry_code.clone()),
|
||||
blocking: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (entry.status == "planned" || entry.status == "to_verify" || entry.status == "unknown")
|
||||
&& entry.catalog_enabled
|
||||
{
|
||||
issues.push(crate::LocalPipelineValidationIssueDto {
|
||||
code: "inactive_dex_matrix_entry_catalog_enabled".to_string(),
|
||||
message: format!(
|
||||
"inactive DEX '{}' must not be enabled in the runtime catalog",
|
||||
entry_code
|
||||
),
|
||||
subject: Some(entry_code.clone()),
|
||||
blocking: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_local_pipeline_validation_run(
|
||||
summary: crate::LocalPipelineDiagnosticSummaryDto,
|
||||
config: &crate::LocalPipelineValidationConfig,
|
||||
@@ -563,6 +650,16 @@ mod tests {
|
||||
token_metadata_missing_count: 0,
|
||||
pool_count: 27,
|
||||
pair_count: 27,
|
||||
pair_gap_counter_semantics: "blocking_actionable_pairs_only".to_string(),
|
||||
literal_pair_without_trade_count: 4,
|
||||
literal_pair_without_candle_count: 3,
|
||||
trade_materialized_pair_count: 23,
|
||||
candle_materialized_pair_count: 24,
|
||||
actionable_pair_count: 23,
|
||||
candle_bucket_timeframe_count: 4,
|
||||
candles_are_bucketed: true,
|
||||
blocking_pair_without_trade_count: 0,
|
||||
blocking_pair_without_candle_count: 0,
|
||||
non_actionable_pair_count: 0,
|
||||
pair_without_trade_count: 0,
|
||||
pair_without_candle_count: 0,
|
||||
@@ -609,6 +706,28 @@ mod tests {
|
||||
},
|
||||
],
|
||||
pair_summaries: vec![],
|
||||
pair_actionability_summaries: vec![
|
||||
crate::LocalPairActionabilityDiagnosticSummaryDto {
|
||||
pair_actionability: "trade_materialized".to_string(),
|
||||
pair_count: 23,
|
||||
decoded_event_count: 210,
|
||||
decoded_trade_candidate_count: 210,
|
||||
actionable_trade_candidate_count: 210,
|
||||
failed_trade_candidate_count: 0,
|
||||
trade_event_count: 210,
|
||||
pair_candle_count: 230,
|
||||
},
|
||||
crate::LocalPairActionabilityDiagnosticSummaryDto {
|
||||
pair_actionability: "catalog_only".to_string(),
|
||||
pair_count: 4,
|
||||
decoded_event_count: 0,
|
||||
decoded_trade_candidate_count: 0,
|
||||
actionable_trade_candidate_count: 0,
|
||||
failed_trade_candidate_count: 0,
|
||||
trade_event_count: 0,
|
||||
pair_candle_count: 0,
|
||||
},
|
||||
],
|
||||
decoded_event_summaries: vec![],
|
||||
event_classification_summaries: vec![],
|
||||
missing_trade_event_reason_summaries: vec![],
|
||||
@@ -746,6 +865,24 @@ mod tests {
|
||||
assert_eq!(report.validation_profile_code, "0.7.31_trade_event_actionability_policy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_accepts_0_7_32_validation_report_semantics_summary() {
|
||||
let mut summary = make_0_7_28_summary_with_meteora();
|
||||
summary.literal_pair_without_trade_count = 12;
|
||||
summary.literal_pair_without_candle_count = 12;
|
||||
summary.blocking_pair_without_trade_count = 0;
|
||||
summary.blocking_pair_without_candle_count = 0;
|
||||
summary.pair_without_trade_count = 0;
|
||||
summary.pair_without_candle_count = 0;
|
||||
summary.pair_gap_counter_semantics = "blocking_actionable_pairs_only".to_string();
|
||||
let config = crate::LocalPipelineValidationConfig::v0_7_32_validation_report_semantics();
|
||||
let report = crate::validate_local_pipeline_diagnostics_summary(&summary, &config);
|
||||
assert!(report.validation_passed);
|
||||
assert_eq!(report.validation_profile_code, "0.7.32_validation_report_semantics");
|
||||
assert_eq!(summary.literal_pair_without_trade_count, 12);
|
||||
assert_eq!(summary.blocking_pair_without_trade_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_0_7_31_failed_transaction_trade_events() {
|
||||
let mut summary = make_0_7_28_summary_with_meteora();
|
||||
@@ -790,6 +927,42 @@ mod tests {
|
||||
assert!(found_pump_swap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_dex_matrix_entries_have_skip_reasons() {
|
||||
for entry in crate::dex_support_matrix_entry_dtos() {
|
||||
if entry.status == "partial" {
|
||||
let has_skip_reason = match entry.skip_reason {
|
||||
Some(skip_reason) => !skip_reason.trim().is_empty(),
|
||||
None => false,
|
||||
};
|
||||
assert!(has_skip_reason, "{}", entry.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supported_dex_matrix_entries_have_materialization_flags() {
|
||||
for entry in crate::dex_support_matrix_entry_dtos() {
|
||||
if entry.status == "supported" {
|
||||
assert!(entry.decoded, "{}", entry.code);
|
||||
assert!(entry.materialized, "{}", entry.code);
|
||||
assert!(entry.trade_candidate, "{}", entry.code);
|
||||
assert!(entry.candle_candidate, "{}", entry.code);
|
||||
assert!(entry.pair_candidate, "{}", entry.code);
|
||||
assert!(entry.pool_candidate, "{}", entry.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planned_dex_matrix_entries_are_not_catalog_enabled() {
|
||||
for entry in crate::dex_support_matrix_entry_dtos() {
|
||||
if entry.status == "planned" || entry.status == "to_verify" {
|
||||
assert!(!entry.catalog_enabled, "{}", entry.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_rejects_missing_expected_dex() {
|
||||
let mut summary = make_clean_summary();
|
||||
|
||||
Reference in New Issue
Block a user