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.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.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.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] [workspace.package]
version = "0.7.31" version = "0.7.32"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" 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. `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 ## 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. 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 ### 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.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 | | Code cible | Type | Statut `0.7.29` | Prochaine action |
|---|---:|---|---| |---|---:|---|---|
| `pump_fun` | Launch + bonding curve | partiel | verrouiller le rattachement mint initial -> pools migrés | | `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 : 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 ; 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. utiliser la matrice `0.7.29` comme source commune pour le catalogue, la classification et les protocol candidates ; 2. conserver la non-régression `0.7.31` : transactions failed traçables mais exclues des `trade_events`, metrics et candles ;
3. relier progressivement les événements non-trade aux tables existantes : lifecycle, liquidité, fees, rewards, admin ; 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` ; 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 ; 5. ajouter les launch surfaces manquantes comme origines de mint : LaunchLab/Launchpad, LetsBonk/Bonk.fun, Boop.fun, Moonshot/Moonit, Believe, Bags ;
6. traiter Heaven ; 6. traiter Heaven ;

View File

@@ -846,18 +846,20 @@ Réalisé :
- exposer `resetMarketMaterializationDeletedCount` dans le résultat de replay UI ; - 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. - 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 ### 6.064. Version `0.7.32` — Sémantique des diagnostics et compteurs de validation
Objectif : ne plus perdre les transactions utiles qui ne correspondent pas encore à un DEX connu. 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, Repoussé après cette clarification : consolider les transactions inconnues et protocol candidates sans polluer les trades/candles.
- 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.
### 6.065. Version `0.7.33` — Événements non-trade v1 : liquidité et cycle de vie pool ### 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. 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 : 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é, 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, 2. conserver la clarification `0.7.32` entre gaps littéraux de catalogue et gaps bloquants/actionnables,
3. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline, 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. 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, 4. garder les clients HTTP/WS et managers réseau hors du refactor DEX tant quils ne bloquent pas le pipeline,
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, 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. consolider Meteora avec corpus fiable : `meteora_dlmm`, `meteora_damm_v1`, `meteora_damm_v2`, `meteora_dbc` et `meteora_dlc` si le programme est confirmé, 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 Orca, FluxBeam et DexLab sur corpus, 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. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`, 8. consolider Orca, FluxBeam et DexLab sur corpus,
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, 9. traiter `raydium_amm_v4` legacy seulement après les autres Raydium, avec corpus dédié prouvant le programme `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`,
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, 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. ajouter ensuite les overlays des signaux analytiques sur les 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. consolider les vues métier `token / pair / pool` dans `kb_demo_app`, y compris les événements liquidité, lifecycle, fees, rewards et admin, 12. ajouter ensuite les overlays des signaux analytiques sur les candles,
13. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection, 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. préparer ensuite louverture de `0.8.x` pour lanalyse, les filtres, les patterns et les projections graphiques, 14. stabiliser lergonomie, les filtres, la pagination et la navigation de lUI dinspection,
15. préparer enfin Yellowstone gRPC comme extension de capacité, et non comme remplacement du socle HTTP / WS existant. 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"> <div class="mb-3">
<label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label> <label for="demoPipeline2ValidationProfileSelect" class="form-label">Validation profile</label>
<select id="demoPipeline2ValidationProfileSelect" class="form-select"> <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.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.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> <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 { DemoPipeline2LocalMissingTradeEventReasonSummary } from "./DemoPipeline2LocalMissingTradeEventReasonSummary";
import type { DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample } from "./DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample"; import type { DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample } from "./DemoPipeline2LocalMultiTradeSignaturePairDiagnosticSample";
import type { DemoPipeline2LocalNonActionablePairDiagnosticSummary } from "./DemoPipeline2LocalNonActionablePairDiagnosticSummary"; import type { DemoPipeline2LocalNonActionablePairDiagnosticSummary } from "./DemoPipeline2LocalNonActionablePairDiagnosticSummary";
import type { DemoPipeline2LocalPairActionabilityDiagnosticSummary } from "./DemoPipeline2LocalPairActionabilityDiagnosticSummary";
import type { DemoPipeline2LocalPairDiagnosticSummary } from "./DemoPipeline2LocalPairDiagnosticSummary"; import type { DemoPipeline2LocalPairDiagnosticSummary } from "./DemoPipeline2LocalPairDiagnosticSummary";
import type { DemoPipeline2LocalPairGapDiagnosticSample } from "./DemoPipeline2LocalPairGapDiagnosticSample"; import type { DemoPipeline2LocalPairGapDiagnosticSample } from "./DemoPipeline2LocalPairGapDiagnosticSample";
@@ -124,11 +125,51 @@ poolCount: number,
*/ */
pairCount: 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, pairWithoutTradeCount: number,
/** /**
* Total pairs without candle. * Total pairs without candle. Legacy alias for blocking/actionable gaps.
*/ */
pairWithoutCandleCount: number, pairWithoutCandleCount: number,
/** /**
@@ -139,6 +180,10 @@ dexSummaries: Array<DemoPipeline2LocalDexDiagnosticSummary>,
* Diagnostics grouped by pair. * Diagnostics grouped by pair.
*/ */
pairSummaries: Array<DemoPipeline2LocalPairDiagnosticSummary>, pairSummaries: Array<DemoPipeline2LocalPairDiagnosticSummary>,
/**
* Diagnostics grouped by pair materialization/actionability class.
*/
pairActionabilitySummaries: Array<DemoPipeline2LocalPairActionabilityDiagnosticSummary>,
/** /**
* Diagnostics grouped by decoded event kind. * Diagnostics grouped by decoded event kind.
*/ */

View File

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

View File

@@ -327,16 +327,47 @@ pub(crate) struct DemoPipeline2LocalPipelineDiagnosticSummary {
/// Total known pairs. /// Total known pairs.
#[ts(type = "number")] #[ts(type = "number")]
pub pair_count: i64, 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")] #[ts(type = "number")]
pub pair_without_trade_count: i64, pub pair_without_trade_count: i64,
/// Total pairs without candle. /// Total pairs without candle. Legacy alias for blocking/actionable gaps.
#[ts(type = "number")] #[ts(type = "number")]
pub pair_without_candle_count: i64, pub pair_without_candle_count: i64,
/// Diagnostics grouped by DEX. /// Diagnostics grouped by DEX.
pub dex_summaries: std::vec::Vec<DemoPipeline2LocalDexDiagnosticSummary>, pub dex_summaries: std::vec::Vec<DemoPipeline2LocalDexDiagnosticSummary>,
/// Diagnostics grouped by pair. /// Diagnostics grouped by pair.
pub pair_summaries: std::vec::Vec<DemoPipeline2LocalPairDiagnosticSummary>, 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. /// Diagnostics grouped by decoded event kind.
pub decoded_event_summaries: std::vec::Vec<DemoPipeline2LocalDecodedEventDiagnosticSummary>, pub decoded_event_summaries: std::vec::Vec<DemoPipeline2LocalDecodedEventDiagnosticSummary>,
/// Diagnostics grouped by decoded event classification. /// Diagnostics grouped by decoded event classification.
@@ -447,6 +478,39 @@ pub(crate) struct DemoPipeline2LocalPairDiagnosticSummary {
pub last_price_quote_per_base: std::option::Option<f64>, 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. /// Local decoded-event diagnostics summary for the UI.
#[derive(Clone, Debug, serde::Serialize, TS)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[ts( #[ts(
@@ -971,7 +1035,7 @@ pub(crate) async fn demo_pipeline2_validate_local_pipeline(
let service = kb_lib::LocalPipelineValidationService::new(database.clone()); let service = kb_lib::LocalPipelineValidationService::new(database.clone());
let profile_code = match request { let profile_code = match request {
Some(request) => request.profile_code, 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() { let run_result = match profile_code.as_str() {
"0.7.27" | "0.7.27_dexes_non_regression" => { "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" => { "0.7.31" | "0.7.31_trade_event_actionability_policy" => {
service.validate_v0_7_31_current_database().await 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!( other => Err(kb_lib::Error::InvalidState(format!(
"unsupported local pipeline validation profile: {other}" "unsupported local pipeline validation profile: {other}"
))), ))),
@@ -1472,6 +1539,14 @@ fn demo_pipeline2_map_local_diagnostics_summary(
for pair_summary in summary.pair_summaries { for pair_summary in summary.pair_summaries {
pair_summaries.push(demo_pipeline2_map_local_pair_diagnostic_summary(pair_summary)); 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(); let mut decoded_event_summaries = std::vec::Vec::new();
for decoded_event_summary in summary.decoded_event_summaries { for decoded_event_summary in summary.decoded_event_summaries {
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, token_metadata_missing_count: summary.token_metadata_missing_count,
pool_count: summary.pool_count, pool_count: summary.pool_count,
pair_count: summary.pair_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_trade_count: summary.pair_without_trade_count,
pair_without_candle_count: summary.pair_without_candle_count, pair_without_candle_count: summary.pair_without_candle_count,
dex_summaries, dex_summaries,
pair_summaries, pair_summaries,
pair_actionability_summaries,
decoded_event_summaries, decoded_event_summaries,
event_classification_summaries, event_classification_summaries,
missing_trade_event_reason_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( fn demo_pipeline2_map_local_decoded_event_diagnostic_summary(
summary: kb_lib::LocalDecodedEventDiagnosticSummaryDto, summary: kb_lib::LocalDecodedEventDiagnosticSummaryDto,
) -> DemoPipeline2LocalDecodedEventDiagnosticSummary { ) -> DemoPipeline2LocalDecodedEventDiagnosticSummary {

View File

@@ -436,8 +436,7 @@ async fn execute_demo_ws_subscribe(
let result = client.account_subscribe_typed(target, config).await; let result = client.account_subscribe_typed(target, config).await;
return result.map_err(|error| format!("account typed subscribe failed: {error}")); return result.map_err(|error| format!("account typed subscribe failed: {error}"));
} }
let config_result = let config_result = parse_optional_json_value(&request.config_json, "account raw config");
parse_optional_json_value(&request.config_json, "account raw config");
let config = match config_result { let config = match config_result {
Ok(config) => config, Ok(config) => config,
Err(error) => return Err(error), Err(error) => return Err(error),
@@ -526,8 +525,7 @@ async fn execute_demo_ws_subscribe(
let result = client.program_subscribe_typed(target, config).await; let result = client.program_subscribe_typed(target, config).await;
return result.map_err(|error| format!("program typed subscribe failed: {error}")); return result.map_err(|error| format!("program typed subscribe failed: {error}"));
} }
let config_result = let config_result = parse_optional_json_value(&request.config_json, "program raw config");
parse_optional_json_value(&request.config_json, "program raw config");
let config = match config_result { let config = match config_result {
Ok(config) => config, Ok(config) => config,
Err(error) => return Err(error), Err(error) => return Err(error),
@@ -556,8 +554,7 @@ async fn execute_demo_ws_subscribe(
let result = client.signature_subscribe_typed(target, config).await; let result = client.signature_subscribe_typed(target, config).await;
return result.map_err(|error| format!("signature typed subscribe failed: {error}")); return result.map_err(|error| format!("signature typed subscribe failed: {error}"));
} }
let config_result = let config_result = parse_optional_json_value(&request.config_json, "signature raw config");
parse_optional_json_value(&request.config_json, "signature raw config");
let config = match config_result { let config = match config_result {
Ok(config) => config, Ok(config) => config,
Err(error) => return Err(error), 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, Ok(changed_count) => changed_count,
Err(error) => return Err(error.to_string()), Err(error) => return Err(error.to_string()),
}; };
let action_result = let action_result = build_action_result("start", role.as_str(), matched_count, changed_count);
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_log(&app_handle, format_action_result_for_log(&action_result).as_str());
emit_demo_ws_manager_snapshot(&app_handle, &state).await; emit_demo_ws_manager_snapshot(&app_handle, &state).await;
build_demo_ws_manager_snapshot(&state).await build_demo_ws_manager_snapshot(&state).await

View File

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

View File

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

View File

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

View File

@@ -65,14 +65,46 @@ pub struct LocalPipelineDiagnosticSummaryDto {
pub pool_count: i64, pub pool_count: i64,
/// Total known pairs. /// Total known pairs.
pub pair_count: i64, 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. /// 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, pub pair_without_trade_count: i64,
/// Total pairs without candle. /// 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, pub pair_without_candle_count: i64,
/// Diagnostics grouped by DEX. /// Diagnostics grouped by DEX.
pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>, pub dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
/// Diagnostics grouped by pair. /// Diagnostics grouped by pair.
pub pair_summaries: std::vec::Vec<crate::LocalPairDiagnosticSummaryDto>, 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. /// Diagnostics grouped by decoded event kind.
pub decoded_event_summaries: std::vec::Vec<crate::LocalDecodedEventDiagnosticSummaryDto>, pub decoded_event_summaries: std::vec::Vec<crate::LocalDecodedEventDiagnosticSummaryDto>,
/// Diagnostics grouped by decoded event category, lifecycle kind and actionability. /// 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>, 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. /// Local decoded-event diagnostics summary.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LocalDecodedEventDiagnosticSummaryDto { pub struct LocalDecodedEventDiagnosticSummaryDto {
@@ -314,8 +367,24 @@ pub struct LocalPipelineDiagnosticCountersDto {
pub pool_count: i64, pub pool_count: i64,
/// Total known pairs. /// Total known pairs.
pub pair_count: i64, 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. /// Total pairs with only non-actionable missing trade events.
pub non_actionable_pair_count: i64, 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. /// Total pairs without trade.
pub pair_without_trade_count: i64, pub pair_without_trade_count: i64,
/// Total pairs without candle. /// Total pairs without candle.
@@ -351,7 +420,15 @@ pub(crate) struct LocalPipelineDiagnosticCountersRow {
pub(crate) token_metadata_missing_count: i64, pub(crate) token_metadata_missing_count: i64,
pub(crate) pool_count: i64, pub(crate) pool_count: i64,
pub(crate) pair_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) 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_trade_count: i64,
pub(crate) pair_without_candle_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>, 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. /// SQL row for local decoded-event diagnostics.
#[derive(Debug, Clone, sqlx::FromRow)] #[derive(Debug, Clone, sqlx::FromRow)]
pub(crate) struct LocalDecodedEventDiagnosticSummaryRow { pub(crate) struct LocalDecodedEventDiagnosticSummaryRow {

View File

@@ -1,4 +1,3 @@
// file: kb_lib/src/db/entities/program_instruction_discriminator_row.rs // file: kb_lib/src/db/entities/program_instruction_discriminator_row.rs
//! Program instruction discriminator diagnostic row entity. //! 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_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_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_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_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_candle_diagnostic_list_samples;
pub use local_pipeline_diagnostics::query_local_pair_without_trade_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, ) AS token_metadata_missing_count,
(SELECT COUNT(*) FROM k_sol_pools) AS pool_count, (SELECT COUNT(*) FROM k_sol_pools) AS pool_count,
(SELECT COUNT(*) FROM k_sol_pairs) AS pair_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(*) SELECT COUNT(*)
FROM ( FROM (
@@ -216,6 +261,24 @@ SELECT
AND COUNT(DISTINCT dde.id) > 0 AND COUNT(DISTINCT dde.id) > 0
) )
) AS non_actionable_pair_count, ) 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(*) SELECT COUNT(*)
FROM ( FROM (
@@ -234,6 +297,24 @@ SELECT
AND COUNT(DISTINCT te.id) = 0 AND COUNT(DISTINCT te.id) = 0
) )
) AS pair_without_trade_count, ) 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(*) SELECT COUNT(*)
FROM ( FROM (
@@ -295,7 +376,15 @@ SELECT
token_metadata_missing_count: row.token_metadata_missing_count, token_metadata_missing_count: row.token_metadata_missing_count,
pool_count: row.pool_count, pool_count: row.pool_count,
pair_count: row.pair_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, 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_trade_count: row.pair_without_trade_count,
pair_without_candle_count: row.pair_without_candle_count, pair_without_candle_count: row.pair_without_candle_count,
actionable_missing_trade_event_count: row.actionable_missing_trade_event_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. /// Lists local decoded-event diagnostic summaries.
pub async fn query_local_decoded_event_diagnostic_list_summaries( pub async fn query_local_decoded_event_diagnostic_list_summaries(
database: &crate::Database, database: &crate::Database,

View File

@@ -848,19 +848,18 @@ mod tests {
#[test] #[test]
fn matrix_lookup_by_program_id_returns_expected_protocol() { 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, Some(entry) => entry,
None => panic!("expected meteora_dlmm program id lookup"), None => panic!("expected meteora_dlmm program id lookup"),
}; };
assert_eq!(entry.code, "meteora_dlmm"); assert_eq!(entry.code, "meteora_dlmm");
let raydium_entry = match crate::dex_support_matrix_entry_by_program_id( let raydium_entry =
crate::RAYDIUM_AMM_V4_PROGRAM_ID, match crate::dex_support_matrix_entry_by_program_id(crate::RAYDIUM_AMM_V4_PROGRAM_ID) {
) { Some(entry) => entry,
Some(entry) => entry, None => panic!("expected raydium AMM v4 program id lookup"),
None => panic!("expected raydium AMM v4 program id lookup"), };
};
assert_eq!(raydium_entry.code, "raydium_amm_v4"); assert_eq!(raydium_entry.code, "raydium_amm_v4");
} }

View File

@@ -357,6 +357,8 @@ pub use db::LocalMissingTradeEventReasonSummaryDto;
pub use db::LocalMultiTradeSignaturePairDiagnosticSampleDto; pub use db::LocalMultiTradeSignaturePairDiagnosticSampleDto;
/// Local pair diagnostics for pairs whose missing trade events are non-actionable. /// Local pair diagnostics for pairs whose missing trade events are non-actionable.
pub use db::LocalNonActionablePairDiagnosticSummaryDto; pub use db::LocalNonActionablePairDiagnosticSummaryDto;
/// Local pair diagnostics grouped by materialization/actionability class.
pub use db::LocalPairActionabilityDiagnosticSummaryDto;
/// Local pair diagnostics summary. /// Local pair diagnostics summary.
pub use db::LocalPairDiagnosticSummaryDto; pub use db::LocalPairDiagnosticSummaryDto;
/// Sample of a pair gap. /// 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; pub use db::query_local_multi_trade_signature_pair_diagnostic_list_samples;
/// Lists pair summaries for non-actionable missing trade events. /// Lists pair summaries for non-actionable missing trade events.
pub use db::query_local_non_actionable_pair_diagnostic_list_summaries; 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. /// Lists local pair diagnostic summaries.
pub use db::query_local_pair_diagnostic_list_summaries; pub use db::query_local_pair_diagnostic_list_summaries;
/// Lists samples of pairs without candles. /// Lists samples of pairs without candles.

View File

@@ -35,6 +35,13 @@ impl LocalPipelineDiagnosticsService {
Ok(pair_summaries) => pair_summaries, Ok(pair_summaries) => pair_summaries,
Err(error) => return Err(error), 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 = let decoded_event_summaries_result =
crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref()) crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref())
.await; .await;
@@ -160,10 +167,21 @@ impl LocalPipelineDiagnosticsService {
token_metadata_missing_count: counters.token_metadata_missing_count, token_metadata_missing_count: counters.token_metadata_missing_count,
pool_count: counters.pool_count, pool_count: counters.pool_count,
pair_count: counters.pair_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_trade_count: counters.pair_without_trade_count,
pair_without_candle_count: counters.pair_without_candle_count, pair_without_candle_count: counters.pair_without_candle_count,
dex_summaries, dex_summaries,
pair_summaries, pair_summaries,
pair_actionability_summaries,
decoded_event_summaries, decoded_event_summaries,
event_classification_summaries, event_classification_summaries,
missing_trade_event_reason_summaries, missing_trade_event_reason_summaries,

View File

@@ -40,6 +40,8 @@ pub struct LocalPipelineValidationConfig {
pub require_candles_per_dex: bool, pub require_candles_per_dex: bool,
/// Whether non-actionable classification groups must have no linked trade events. /// Whether non-actionable classification groups must have no linked trade events.
pub require_no_non_actionable_trade_events_materialized: bool, 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 { impl Default for LocalPipelineValidationConfig {
@@ -59,6 +61,7 @@ impl Default for LocalPipelineValidationConfig {
require_trade_events_per_dex: true, require_trade_events_per_dex: true,
require_candles_per_dex: true, require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: 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_trade_events_per_dex: true,
require_candles_per_dex: true, require_candles_per_dex: true,
require_no_non_actionable_trade_events_materialized: 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_trade_events_per_dex: false,
require_candles_per_dex: false, require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true, 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_trade_events_per_dex: false,
require_candles_per_dex: false, require_candles_per_dex: false,
require_no_non_actionable_trade_events_materialized: true, 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; config.require_no_non_actionable_trade_events_materialized = true;
return config; 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. /// A single local pipeline validation issue.
@@ -318,6 +335,14 @@ impl LocalPipelineValidationService {
crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy(); crate::LocalPipelineValidationConfig::v0_7_31_trade_event_actionability_policy();
return self.validate_current_database(&config).await; 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. /// 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 { if config.require_all_expected_dexes {
for expected_dex_code in &expected_dex_codes { for expected_dex_code in &expected_dex_codes {
if !observed_dex_codes.contains(expected_dex_code) { 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( fn build_local_pipeline_validation_run(
summary: crate::LocalPipelineDiagnosticSummaryDto, summary: crate::LocalPipelineDiagnosticSummaryDto,
config: &crate::LocalPipelineValidationConfig, config: &crate::LocalPipelineValidationConfig,
@@ -563,6 +650,16 @@ mod tests {
token_metadata_missing_count: 0, token_metadata_missing_count: 0,
pool_count: 27, pool_count: 27,
pair_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, non_actionable_pair_count: 0,
pair_without_trade_count: 0, pair_without_trade_count: 0,
pair_without_candle_count: 0, pair_without_candle_count: 0,
@@ -609,6 +706,28 @@ mod tests {
}, },
], ],
pair_summaries: vec![], 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![], decoded_event_summaries: vec![],
event_classification_summaries: vec![], event_classification_summaries: vec![],
missing_trade_event_reason_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"); 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] #[test]
fn validation_rejects_0_7_31_failed_transaction_trade_events() { fn validation_rejects_0_7_31_failed_transaction_trade_events() {
let mut summary = make_0_7_28_summary_with_meteora(); let mut summary = make_0_7_28_summary_with_meteora();
@@ -790,6 +927,42 @@ mod tests {
assert!(found_pump_swap); 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] #[test]
fn validation_rejects_missing_expected_dex() { fn validation_rejects_missing_expected_dex() {
let mut summary = make_clean_summary(); let mut summary = make_clean_summary();