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

@@ -62,3 +62,4 @@
0.7.29 - Ajout dune matrice DEX commune (`dex_support_matrix`) utilisée par le catalogue DEX, la classification transactionnelle et lenregistrement des protocol candidates ; ajout du profil de validation `0.7.29_multi_dex_matrix_baseline` exposant la matrice dans le rapport de validation ; préparation explicite des surfaces planifiées sans inventer de program ids non vérifiés.
0.7.30 - Ajout dune taxonomie DEX plus fine pour les événements décodés : `eventLifecycleKind`, `eventActionability`, `nonTradeUseful`, compteurs diagnostics des événements non-trade utiles, trades non actionnables et classifications inconnues ; ajout du profil `0.7.30_non_trade_event_classification` sans modification volontaire de la matérialisation trade/candle.
0.7.31 - Application de la politique Option B : les transactions failed restent traçables dans les événements décodés mais ne peuvent plus alimenter `trade_events`, metrics ou candles ; le replay local réinitialise les tables de matérialisation marché avant reconstruction pour supprimer les anciennes lignes dérivées non actionnables.
0.7.32 - Clarification de la sémantique des diagnostics locaux : séparation des gaps littéraux de paires et des gaps bloquants/actionnables, ajout des compteurs de matérialisation par paire, résumé `pairActionabilitySummaries`, profil `0.7.32_validation_report_semantics` et garde-fous sur la matrice DEX sans modification de la matérialisation trade/candle.

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "0.7.31"
version = "0.7.32"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot"

View File

@@ -4,7 +4,7 @@
`khadhroony-bobobot` est un workspace Rust destiné à la détection, au décodage, à lanalyse et, à terme, au trading semi-automatisé de tokens Solana.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de reprise autour de `0.7.30` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale et une matrice DEX commune existent déjà.
Le README précédent décrivait surtout létat `0.3.1`. Ce fichier reflète létat de reprise autour de `0.7.32` : le socle transport HTTP/WS, la résolution transactionnelle, le modèle SQLite, plusieurs connecteurs DEX, les candles, les signaux analytiques, la validation locale et une matrice DEX commune existent déjà.
## 1. Objectif
@@ -31,7 +31,7 @@ Le workspace contient deux crates principales.
La logique métier doit rester dans `kb_lib`. `kb_demo_app` doit rester une façade UI/Tauri et ne doit pas récupérer de logique Solana ou DEX profonde.
## 3. État actuel autour de `0.7.30`
## 3. État actuel autour de `0.7.32`
### 3.1. Socle stabilisé à ne pas refactorer maintenant
@@ -99,6 +99,8 @@ Depuis `0.7.29`, la matrice de support DEX est portée par `kb_lib/src/dex_suppo
Depuis `0.7.30`, les événements décodés reçoivent aussi une classification plus fine : `eventLifecycleKind`, `eventActionability` et `nonTradeUseful`. Cette classification sert aux diagnostics et prépare la matérialisation future des événements non-trade sans alimenter directement les trades/candles.
Depuis `0.7.32`, les diagnostics distinguent explicitement les gaps littéraux de catalogue (`literalPairWithoutTradeCount`, `literalPairWithoutCandleCount`) des gaps bloquants/actionnables (`blockingPairWithoutTradeCount`, `blockingPairWithoutCandleCount`). Les anciens champs `pairWithoutTradeCount` et `pairWithoutCandleCount` restent exposés comme alias de compatibilité pour les gaps bloquants/actionnables.
| Code cible | Type | Statut `0.7.29` | Prochaine action |
|---|---:|---|---|
| `pump_fun` | Launch + bonding curve | partiel | verrouiller le rattachement mint initial -> pools migrés |
@@ -215,9 +217,10 @@ Les tests peuvent rester plus souples lorsque cela clarifie le test.
La reprise doit suivre cet ordre :
1. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
2. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
3. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ;
1. conserver la sémantique `0.7.32` : les gaps littéraux de catalogue ne doivent pas être confondus avec les gaps bloquants/actionnables utilisés par la validation ;
2. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
3. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ;
4. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ;
4. consolider Meteora, surtout `meteora_dlmm` et le cas partiel `meteora_damm_v1` ;
5. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags ;
6. traiter Heaven ;

View File

@@ -846,18 +846,20 @@ Réalisé :
- exposer `resetMarketMaterializationDeletedCount` dans le résultat de replay UI ;
- conserver la validation multi-DEX et la matrice DEX comme garde-fous avant dajouter les surfaces restantes.
### 6.064. Version `0.7.32` — Transactions inconnues et protocol candidates
Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encore à un DEX connu.
### 6.064. Version `0.7.32` — Sémantique des diagnostics et compteurs de validation
Réalisé :
À faire :
- conserver la politique `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
- clarifier que `pairWithoutTradeCount` et `pairWithoutCandleCount` sont des compteurs de gaps bloquants/actionnables, pas des compteurs littéraux sur tout le catalogue ;
- ajouter `literalPairWithoutTradeCount` et `literalPairWithoutCandleCount` pour les paires de catalogue sans trade/candle matérialisé ;
- ajouter `blockingPairWithoutTradeCount` et `blockingPairWithoutCandleCount` comme noms explicites des anciens compteurs bloquants ;
- ajouter les compteurs de matérialisation par paire : `tradeMaterializedPairCount`, `candleMaterializedPairCount`, `actionablePairCount`, `candleBucketTimeframeCount` et `candlesAreBucketed` ;
- ajouter `pairActionabilitySummaries` pour distinguer les paires matérialisées, actionnables sans matérialisation, candidates failed, non-actionables, décodées sans trade candidate et catalog-only ;
- ajouter le profil `0.7.32_validation_report_semantics` ;
- ajouter des garde-fous de validation sur la matrice DEX : entrées `supported` entièrement matérialisées, entrées `partial` avec `skipReason`, entrées `planned/to_verify` non activées au catalogue ;
- ne pas modifier la logique de replay, trade aggregation ou candle aggregation validée en `0.7.31`.
- consolider `k_sol_transaction_classifications`, déjà présente, avec les catégories utiles au suivi DEX,
- consolider `k_sol_protocol_candidates`, déjà présente, pour prioriser les programmes inconnus ou partiellement reconnus,
- classifier les transactions résolues en catégories : known supported, known partial, known non-trade, unknown program, unknown protocol candidate, unknown event kind, failed transaction, non-actionable trade,
- conserver les `program_id`, comptes, signatures, préfixes de `data`, logs et indices dinstructions utiles à lanalyse,
- créer des requêtes de diagnostic pour repérer les programmes inconnus fréquents,
- permettre de promouvoir plus tard un protocol candidate vers un vrai DEX/surface sans perdre lhistorique,
- garantir que ces tables nalimentent jamais directement les trades/candles.
Repoussé après cette clarification : consolider les transactions inconnues et protocol candidates sans polluer les trades/candles.
### 6.065. Version `0.7.33` — Événements non-trade v1 : liquidité et cycle de vie pool
Objectif : exploiter les événements utiles à lanalyse et au trading semi-automatique sans les mélanger avec les swaps/candles.
@@ -1212,17 +1214,18 @@ Le projet doit maintenir au minimum :
La priorité immédiate est désormais la suivante :
1. conserver la validation acquise `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles, aucun trade/candle candidate sans payload montant/prix exploitable, aucun diagnostic bloquant masqué,
2. utiliser la matrice `0.7.29` (`kb_lib/src/dex_support_matrix.rs`) comme source commune pour le catalogue DEX, les mappings program id -> protocole, la classification transactionnelle et les protocol candidates,
3. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
4. consolider les événements non-trade sans les confondre avec les trades/candles : lifecycle de pool, liquidité, fees, rewards, admin/config, migration et launch/mint,
5. rattacher les launch surfaces aux tokens et aux pools migrés : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags et Heaven,
6. consolider Meteora avec corpus fiable : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlc` si le programme est confirmé,
7. consolider Orca, FluxBeam et DexLab sur corpus,
8. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`,
9. ajouter une matérialisation dédiée des transactions inconnues ou partiellement décodées pour analyser les DEX manquants sans polluer les trades/candles,
10. effectuer une validation DEX v1 consolidée sur tous les connecteurs supportés avant de considérer la couche DEX `0.7.x` comme stable,
11. ajouter ensuite les overlays des signaux analytiques sur les candles,
12. consolider les vues métier `token / pair / pool` dans `kb_demo_app`, y compris les événements liquidité, lifecycle, fees, rewards et admin,
13. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection,
14. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques,
15. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant.
2. conserver la clarification `0.7.32` entre gaps littéraux de catalogue et gaps bloquants/actionnables,
3. utiliser la matrice `0.7.29` (`kb_lib/src/dex_support_matrix.rs`) comme source commune pour le catalogue DEX, les mappings program id -> protocole, la classification transactionnelle et les protocol candidates,
4. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
5. consolider les événements non-trade sans les confondre avec les trades/candles : lifecycle de pool, liquidité, fees, rewards, admin/config, migration et launch/mint,
6. rattacher les launch surfaces aux tokens et aux pools migrés : Raydium LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags et Heaven,
7. consolider Meteora avec corpus fiable : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlc` si le programme est confirmé,
8. consolider Orca, FluxBeam et DexLab sur corpus,
9. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`,
10. ajouter une matérialisation dédiée des transactions inconnues ou partiellement décodées pour analyser les DEX manquants sans polluer les trades/candles,
11. effectuer une validation DEX v1 consolidée sur tous les connecteurs supportés avant de considérer la couche DEX `0.7.x` comme stable,
12. ajouter ensuite les overlays des signaux analytiques sur les candles,
13. consolider les vues métier `token / pair / pool` dans `kb_demo_app`, y compris les événements liquidité, lifecycle, fees, rewards et admin,
14. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection,
15. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques,
16. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant.

View File

@@ -166,7 +166,8 @@
<div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select">
<option value="0.7.31_trade_event_actionability_policy" selected>0.7.31trade event actionability policy</option>
<option value="0.7.32_validation_report_semantics" selected>0.7.32validation report semantics</option>
<option value="0.7.31_trade_event_actionability_policy">0.7.31 — trade event actionability policy</option>
<option value="0.7.30_non_trade_event_classification">0.7.30 — non-trade event classification</option>
<option value="0.7.29_multi_dex_matrix_baseline">0.7.29 — DEX matrix baseline</option>
<option value="0.7.28_multi_dex_non_regression">0.7.28 — multi-DEX non-regression</option>

View File

@@ -0,0 +1,38 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Local pair actionability diagnostics summary for the UI.
*/
export type DemoPipeline2LocalPairActionabilityDiagnosticSummary = {
/**
* Pair actionability or materialization class.
*/
pairActionability: string,
/**
* Pair count.
*/
pairCount: number,
/**
* Decoded event count.
*/
decodedEventCount: number,
/**
* Decoded trade candidate count.
*/
decodedTradeCandidateCount: number,
/**
* Actionable trade candidate count.
*/
actionableTradeCandidateCount: number,
/**
* Failed trade candidate count.
*/
failedTradeCandidateCount: number,
/**
* Trade event count.
*/
tradeEventCount: number,
/**
* Pair candle count.
*/
pairCandleCount: number, };

View File

@@ -7,6 +7,7 @@ import type { DemoPipeline2LocalMissingTradeEventDiagnosticSample } from "./Demo
import type { DemoPipeline2LocalMissingTradeEventReasonSummary } from "./DemoPipeline2LocalMissingTradeEventReasonSummary";
import type { DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample } from "./DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample";
import type { DemoPipeline2LocalNonActionablePairDiagnosticSummary } from "./DemoPipeline2LocalNonActionablePairDiagnosticSummary";
import type { DemoPipeline2LocalPairActionabilityDiagnosticSummary } from "./DemoPipeline2LocalPairActionabilityDiagnosticSummary";
import type { DemoPipeline2LocalPairDiagnosticSummary } from "./DemoPipeline2LocalPairDiagnosticSummary";
import type { DemoPipeline2LocalPairGapDiagnosticSample } from "./DemoPipeline2LocalPairGapDiagnosticSample";
@@ -124,11 +125,51 @@ poolCount: number,
*/
pairCount: number,
/**
* Total pairs without trade.
* Stable explanation for legacy pair gap counters.
*/
pairGapCounterSemantics: string,
/**
* Total pairs without any persisted trade event.
*/
literalPairWithoutTradeCount: number,
/**
* Total pairs without any persisted candle.
*/
literalPairWithoutCandleCount: number,
/**
* Total pairs that have at least one persisted trade event.
*/
tradeMaterializedPairCount: number,
/**
* Total pairs that have at least one persisted candle bucket.
*/
candleMaterializedPairCount: number,
/**
* Total pairs with at least one successful decoded trade candidate.
*/
actionablePairCount: number,
/**
* Total distinct candle timeframes currently materialized.
*/
candleBucketTimeframeCount: number,
/**
* Whether candle rows are bucketed aggregates rather than one row per trade.
*/
candlesAreBucketed: boolean,
/**
* Total pairs without trade among actionable successful trade candidates.
*/
blockingPairWithoutTradeCount: number,
/**
* Total pairs without candle among actionable successful candle candidates.
*/
blockingPairWithoutCandleCount: number,
/**
* Total pairs without trade. Legacy alias for blocking/actionable gaps.
*/
pairWithoutTradeCount: number,
/**
* Total pairs without candle.
* Total pairs without candle. Legacy alias for blocking/actionable gaps.
*/
pairWithoutCandleCount: number,
/**
@@ -139,6 +180,10 @@ dexSummaries: Array<DemoPipeline2LocalDexDiagnosticSummary>,
* Diagnostics grouped by pair.
*/
pairSummaries: Array<DemoPipeline2LocalPairDiagnosticSummary>,
/**
* Diagnostics grouped by pair materialization/actionability class.
*/
pairActionabilitySummaries: Array<DemoPipeline2LocalPairActionabilityDiagnosticSummary>,
/**
* Diagnostics grouped by decoded event kind.
*/

View File

@@ -1,7 +1,7 @@
{
"name": "kb-demo-app",
"private": true,
"version": "0.7.31",
"version": "0.7.32",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -327,16 +327,47 @@ pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
/// Total known pairs.
#[ts(type = "number")]
pub pair_count: i64,
/// Total pairs without trade.
/// 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.
/// 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 decoded event kind.
pub decoded_event_summaries: std::vec::Vec<DemoPipeline2LocalDecodedEventDiagnosticSummary>,
/// Diagnostics grouped by decoded event classification.
@@ -447,6 +478,39 @@ pub(crate) struct DemoPipeline2LocalPairDiagnosticSummary {
pub last_price_quote_per_base: std::option::Option<f64>,
}
/// 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 decoded-event diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
@@ -971,7 +1035,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request {
Some(request) => request.profile_code,
None => "0.7.31_trade_event_actionability_policy".to_string(),
None => "0.7.32_validation_report_semantics".to_string(),
};
let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => {
@@ -989,6 +1053,9 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
"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
},
other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}"
))),
@@ -1472,6 +1539,14 @@ fn demo_pipeline2_map_local_diagnostics_summary(
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 decoded_event_summaries = std::vec::Vec::new();
for decoded_event_summary in summary.decoded_event_summaries {
decoded_event_summaries
@@ -1552,10 +1627,21 @@ fn demo_pipeline2_map_local_diagnostics_summary(
token_metadata_missing_count: summary.token_metadata_missing_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,
decoded_event_summaries,
event_classification_summaries,
missing_trade_event_reason_summaries,
@@ -1606,6 +1692,21 @@ fn demo_pipeline2_map_local_pair_diagnostic_summary(
}
}
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_decoded_event_diagnostic_summary(
summary: kb_lib::LocalDecodedEventDiagnosticSummaryDto,
) -> DemoPipeline2LocalDecodedEventDiagnosticSummary {

View File

@@ -436,8 +436,7 @@ async fn execute_demo_ws_subscribe(
let result = client.account_subscribe_typed(target, config).await;
return result.map_err(|error| format!("account typed subscribe failed: {error}"));
}
let config_result =
parse_optional_json_value(&request.config_json, "account raw config");
let config_result = parse_optional_json_value(&request.config_json, "account raw config");
let config = match config_result {
Ok(config) => config,
Err(error) => return Err(error),
@@ -526,8 +525,7 @@ async fn execute_demo_ws_subscribe(
let result = client.program_subscribe_typed(target, config).await;
return result.map_err(|error| format!("program typed subscribe failed: {error}"));
}
let config_result =
parse_optional_json_value(&request.config_json, "program raw config");
let config_result = parse_optional_json_value(&request.config_json, "program raw config");
let config = match config_result {
Ok(config) => config,
Err(error) => return Err(error),
@@ -556,8 +554,7 @@ async fn execute_demo_ws_subscribe(
let result = client.signature_subscribe_typed(target, config).await;
return result.map_err(|error| format!("signature typed subscribe failed: {error}"));
}
let config_result =
parse_optional_json_value(&request.config_json, "signature raw config");
let config_result = parse_optional_json_value(&request.config_json, "signature raw config");
let config = match config_result {
Ok(config) => config,
Err(error) => return Err(error),

View File

@@ -174,8 +174,7 @@ pub(crate) async fn demo_ws_manager_start_role(
Ok(changed_count) => changed_count,
Err(error) => return Err(error.to_string()),
};
let action_result =
build_action_result("start", role.as_str(), matched_count, changed_count);
let action_result = build_action_result("start", role.as_str(), matched_count, changed_count);
emit_demo_ws_manager_log(&app_handle, format_action_result_for_log(&action_result).as_str());
emit_demo_ws_manager_snapshot(&app_handle, &state).await;
build_demo_ws_manager_snapshot(&state).await

View File

@@ -22,7 +22,7 @@ async fn main() -> std::process::ExitCode {
Err(_err) => {
eprintln!("Cannot create lock!");
std::process::exit(1);
}
},
};
// trying to aquire an exclusive lock
if lock_file.try_lock_exclusive().is_err() {
@@ -32,11 +32,11 @@ async fn main() -> std::process::ExitCode {
if rustls::crypto::CryptoProvider::get_default().is_none() {
let provider_result = rustls::crypto::aws_lc_rs::default_provider().install_default();
match provider_result {
Ok(()) => {}
Ok(()) => {},
Err(error) => {
eprintln!("kb_demo_app rustls provider init error: {:?}", error);
return std::process::ExitCode::FAILURE;
}
},
}
}
let run_result = kb_demo_app_lib::run().await;

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "kb-demo-app",
"version": "0.7.31",
"version": "0.7.32",
"identifier": "com.sasedev.kb-demo-app",
"build": {
"beforeDevCommand": "npm run dev",

View File

@@ -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;

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,

View File

@@ -848,16 +848,15 @@ 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)
{
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,
) {
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"),
};

View File

@@ -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.

View File

@@ -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,

View File

@@ -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();