mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
global: snapshot
This commit is contained in:
822
crates/brk_bindgen/tests/catalog_test.rs
Normal file
822
crates/brk_bindgen/tests/catalog_test.rs
Normal file
@@ -0,0 +1,822 @@
|
||||
//! Tests that verify pattern analysis using the real catalog.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
|
||||
use brk_bindgen::ClientMetadata;
|
||||
use brk_types::TreeNode;
|
||||
|
||||
/// Load the catalog from the JSON file.
|
||||
fn load_catalog() -> TreeNode {
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/catalog.json");
|
||||
let catalog_json = std::fs::read_to_string(path).expect("Failed to read catalog.json");
|
||||
serde_json::from_str(&catalog_json).expect("Failed to parse catalog.json")
|
||||
}
|
||||
|
||||
/// Load OpenAPI spec from api.json.
|
||||
fn load_openapi_json() -> String {
|
||||
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/api.json");
|
||||
std::fs::read_to_string(path).expect("Failed to read api.json")
|
||||
}
|
||||
|
||||
/// Load metadata from the catalog.
|
||||
#[allow(unused)]
|
||||
fn load_metadata() -> ClientMetadata {
|
||||
ClientMetadata::from_catalog(load_catalog())
|
||||
}
|
||||
|
||||
/// Collect all leaf metric names from a tree.
|
||||
fn collect_leaf_names(node: &TreeNode, names: &mut HashSet<String>) {
|
||||
match node {
|
||||
TreeNode::Leaf(leaf) => {
|
||||
names.insert(leaf.name().to_string());
|
||||
}
|
||||
TreeNode::Branch(children) => {
|
||||
for child in children.values() {
|
||||
collect_leaf_names(child, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_catalog_loads() {
|
||||
let catalog = load_catalog();
|
||||
|
||||
// Should be a branch with top-level categories
|
||||
let TreeNode::Branch(categories) = &catalog else {
|
||||
panic!("Expected catalog to be a branch");
|
||||
};
|
||||
|
||||
// Check some expected top-level categories exist
|
||||
assert!(
|
||||
categories.contains_key("addresses"),
|
||||
"Missing addresses category"
|
||||
);
|
||||
assert!(categories.contains_key("blocks"), "Missing blocks category");
|
||||
assert!(categories.contains_key("market"), "Missing market category");
|
||||
assert!(categories.contains_key("supply"), "Missing supply category");
|
||||
|
||||
println!("Catalog has {} top-level categories", categories.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_leaves_have_names() {
|
||||
let catalog = load_catalog();
|
||||
let mut names = HashSet::new();
|
||||
collect_leaf_names(&catalog, &mut names);
|
||||
|
||||
println!("Catalog has {} unique metric names", names.len());
|
||||
assert!(!names.is_empty(), "Should have at least some metrics");
|
||||
|
||||
// All names should be non-empty
|
||||
for name in &names {
|
||||
assert!(!name.is_empty(), "Found empty metric name");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_detection() {
|
||||
let catalog = load_catalog();
|
||||
|
||||
let (patterns, concrete_to_pattern, concrete_to_type_param) =
|
||||
brk_bindgen::detect_structural_patterns(&catalog);
|
||||
|
||||
println!("Detected {} structural patterns", patterns.len());
|
||||
println!(
|
||||
"Concrete to pattern mappings: {}",
|
||||
concrete_to_pattern.len()
|
||||
);
|
||||
println!("Type parameter mappings: {}", concrete_to_type_param.len());
|
||||
|
||||
// Print pattern details
|
||||
for pattern in &patterns {
|
||||
let mode_str = match &pattern.mode {
|
||||
Some(brk_bindgen::PatternMode::Suffix { relatives }) => {
|
||||
format!("Suffix({})", relatives.len())
|
||||
}
|
||||
Some(brk_bindgen::PatternMode::Prefix { prefixes }) => {
|
||||
format!("Prefix({})", prefixes.len())
|
||||
}
|
||||
None => "None".to_string(),
|
||||
};
|
||||
println!(
|
||||
" {} (fields: {}, generic: {}, mode: {})",
|
||||
pattern.name,
|
||||
pattern.fields.len(),
|
||||
pattern.is_generic,
|
||||
mode_str
|
||||
);
|
||||
}
|
||||
|
||||
// Should have detected some patterns
|
||||
assert!(!patterns.is_empty(), "Should detect at least some patterns");
|
||||
|
||||
// Check that parameterizable patterns have valid modes
|
||||
for pattern in &patterns {
|
||||
if pattern.is_parameterizable() {
|
||||
let mode = pattern.mode.as_ref().unwrap();
|
||||
match mode {
|
||||
brk_bindgen::PatternMode::Suffix { relatives } => {
|
||||
assert_eq!(
|
||||
relatives.len(),
|
||||
pattern.fields.len(),
|
||||
"Pattern {} should have relative for each field",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
brk_bindgen::PatternMode::Prefix { prefixes } => {
|
||||
assert_eq!(
|
||||
prefixes.len(),
|
||||
pattern.fields.len(),
|
||||
"Pattern {} should have prefix for each field",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cost_basis_pattern() {
|
||||
let catalog = load_catalog();
|
||||
|
||||
let (patterns, _, _) = brk_bindgen::detect_structural_patterns(&catalog);
|
||||
|
||||
// Find CostBasisPattern2 and inspect it
|
||||
let cost_basis = patterns
|
||||
.iter()
|
||||
.find(|p| p.name == "CostBasisPattern2")
|
||||
.expect("CostBasisPattern2 should exist");
|
||||
|
||||
println!("CostBasisPattern2:");
|
||||
println!(
|
||||
" Fields: {:?}",
|
||||
cost_basis
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| &f.name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
println!(" Mode: {:?}", cost_basis.mode);
|
||||
println!(" Is generic: {}", cost_basis.is_generic);
|
||||
|
||||
// With suffix naming convention (cost_basis_max, cost_basis_min, cost_basis):
|
||||
//
|
||||
// At root level: common prefix is "cost_basis_" -> suffix mode
|
||||
// max -> "max"
|
||||
// min -> "min"
|
||||
// percentiles -> "" (identity)
|
||||
//
|
||||
// At lth_ level: common prefix is "lth_cost_basis_" -> suffix mode
|
||||
// max -> "max"
|
||||
// min -> "min"
|
||||
// percentiles -> "" (identity)
|
||||
//
|
||||
// Both use suffix mode with same relatives, so pattern IS parameterizable!
|
||||
assert!(
|
||||
cost_basis.is_parameterizable(),
|
||||
"CostBasisPattern2 should be parameterizable with consistent suffix mode"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_realized_pattern3_fields() {
|
||||
let catalog = load_catalog();
|
||||
let metadata = ClientMetadata::from_catalog(catalog);
|
||||
|
||||
let pattern = metadata
|
||||
.find_pattern("RealizedPattern3")
|
||||
.expect("RealizedPattern3 should exist");
|
||||
|
||||
println!("RealizedPattern3 fields:");
|
||||
for field in &pattern.fields {
|
||||
let is_branch = field.is_branch();
|
||||
let is_pattern = metadata.find_pattern(&field.rust_type).is_some();
|
||||
let is_param = metadata.is_parameterizable(&field.rust_type);
|
||||
println!(
|
||||
" {} -> {} (branch={}, pattern={}, param={})",
|
||||
field.name, field.rust_type, is_branch, is_pattern, is_param
|
||||
);
|
||||
}
|
||||
|
||||
// Check if RealizedPattern3 is considered parameterizable
|
||||
println!(
|
||||
"\nRealizedPattern3 is_parameterizable (metadata): {}",
|
||||
metadata.is_parameterizable("RealizedPattern3")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parameterizable_patterns_have_mode() {
|
||||
let catalog = load_catalog();
|
||||
let (patterns, _, _) = brk_bindgen::detect_structural_patterns(&catalog);
|
||||
|
||||
// All patterns that appear 2+ times should either:
|
||||
// 1. Be parameterizable (have a mode)
|
||||
// 2. Or have inconsistent instances (mode = None)
|
||||
//
|
||||
// Patterns with mode = None should be inlined, not generate factories
|
||||
|
||||
let parameterizable: Vec<_> = patterns.iter().filter(|p| p.is_parameterizable()).collect();
|
||||
let non_parameterizable: Vec<_> = patterns
|
||||
.iter()
|
||||
.filter(|p| !p.is_parameterizable())
|
||||
.collect();
|
||||
|
||||
println!("\nParameterizable patterns ({}):", parameterizable.len());
|
||||
for p in ¶meterizable {
|
||||
let mode = p.mode.as_ref().unwrap();
|
||||
let mode_type = match mode {
|
||||
brk_bindgen::PatternMode::Suffix { .. } => "Suffix",
|
||||
brk_bindgen::PatternMode::Prefix { .. } => "Prefix",
|
||||
};
|
||||
println!(" {} ({} fields, {})", p.name, p.fields.len(), mode_type);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\nNon-parameterizable patterns ({}):",
|
||||
non_parameterizable.len()
|
||||
);
|
||||
for p in &non_parameterizable {
|
||||
println!(" {} ({} fields)", p.name, p.fields.len());
|
||||
}
|
||||
|
||||
// Verify all parameterizable patterns have valid modes with all fields
|
||||
for pattern in ¶meterizable {
|
||||
let mode = pattern.mode.as_ref().unwrap();
|
||||
let field_names: HashSet<_> = pattern.fields.iter().map(|f| f.name.clone()).collect();
|
||||
|
||||
match mode {
|
||||
brk_bindgen::PatternMode::Suffix { relatives } => {
|
||||
let mode_fields: HashSet<_> = relatives.keys().cloned().collect();
|
||||
assert_eq!(
|
||||
field_names, mode_fields,
|
||||
"Pattern {} suffix mode should have all fields",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
brk_bindgen::PatternMode::Prefix { prefixes } => {
|
||||
let mode_fields: HashSet<_> = prefixes.keys().cloned().collect();
|
||||
assert_eq!(
|
||||
field_names, mode_fields,
|
||||
"Pattern {} prefix mode should have all fields",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_patterns() {
|
||||
let catalog = load_catalog();
|
||||
|
||||
let (used_indexes, index_patterns) = brk_bindgen::detect_index_patterns(&catalog);
|
||||
|
||||
println!("Used indexes: {:?}", used_indexes);
|
||||
println!("Index set patterns: {}", index_patterns.len());
|
||||
|
||||
for pattern in &index_patterns {
|
||||
println!(" {} -> {:?}", pattern.name, pattern.indexes);
|
||||
}
|
||||
|
||||
// Should have detected some index patterns
|
||||
assert!(!index_patterns.is_empty(), "Should detect index patterns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_rust_output() {
|
||||
let catalog = load_catalog();
|
||||
let metadata = ClientMetadata::from_catalog(catalog.clone());
|
||||
|
||||
// Collect all metric names from the catalog
|
||||
let mut all_metrics = HashSet::new();
|
||||
collect_leaf_names(&catalog, &mut all_metrics);
|
||||
|
||||
// Generate Rust client output
|
||||
let mut rust_output = String::new();
|
||||
brk_bindgen::rust::client::generate_imports(&mut rust_output);
|
||||
brk_bindgen::rust::client::generate_base_client(&mut rust_output);
|
||||
brk_bindgen::rust::client::generate_metric_pattern_trait(&mut rust_output);
|
||||
brk_bindgen::rust::client::generate_endpoint(&mut rust_output);
|
||||
brk_bindgen::rust::client::generate_index_accessors(
|
||||
&mut rust_output,
|
||||
&metadata.index_set_patterns,
|
||||
);
|
||||
brk_bindgen::rust::client::generate_pattern_structs(
|
||||
&mut rust_output,
|
||||
&metadata.structural_patterns,
|
||||
&metadata,
|
||||
);
|
||||
brk_bindgen::rust::tree::generate_tree(&mut rust_output, &metadata.catalog, &metadata);
|
||||
brk_bindgen::rust::api::generate_main_client(&mut rust_output, &[]);
|
||||
|
||||
// Count metrics that appear as direct string literals
|
||||
let mut direct_metrics = 0;
|
||||
for metric in &all_metrics {
|
||||
if rust_output.contains(&format!("\"{}\"", metric)) {
|
||||
direct_metrics += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nGenerated Rust output stats:");
|
||||
println!(" Total metrics in catalog: {}", all_metrics.len());
|
||||
println!(" Direct string literals: {}", direct_metrics);
|
||||
println!(
|
||||
" Via pattern factories: {}",
|
||||
all_metrics.len() - direct_metrics
|
||||
);
|
||||
println!(" Output size: {} bytes", rust_output.len());
|
||||
|
||||
// Write output to actual client location
|
||||
let output_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../brk_client/src/lib.rs");
|
||||
std::fs::write(output_path, &rust_output).expect("Failed to write client output");
|
||||
println!(" Wrote output to: {}", output_path);
|
||||
|
||||
// Verify the output contains the key components
|
||||
assert!(rust_output.contains("fn _m("), "Should define _m helper");
|
||||
assert!(
|
||||
rust_output.contains("pub struct MetricsTree"),
|
||||
"Should have MetricsTree"
|
||||
);
|
||||
assert!(
|
||||
rust_output.contains("impl MetricsTree"),
|
||||
"Should have MetricsTree impl"
|
||||
);
|
||||
|
||||
// Count parameterizable patterns (these use _m for dynamic metric names)
|
||||
// Use metadata.is_parameterizable() for full recursive check
|
||||
let parameterizable_count = metadata
|
||||
.structural_patterns
|
||||
.iter()
|
||||
.filter(|p| metadata.is_parameterizable(&p.name))
|
||||
.count();
|
||||
println!(" Parameterizable patterns: {}", parameterizable_count);
|
||||
|
||||
// Verify all pattern structs are generated (parameterizable and non)
|
||||
for pattern in &metadata.structural_patterns {
|
||||
assert!(
|
||||
rust_output.contains(&format!("pub struct {}", pattern.name)),
|
||||
"Missing pattern struct: {}",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
|
||||
println!("\nGenerated Rust client is complete!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_javascript_output() {
|
||||
let catalog = load_catalog();
|
||||
let metadata = ClientMetadata::from_catalog(catalog.clone());
|
||||
|
||||
// Collect all metric names from the catalog
|
||||
let mut all_metrics = HashSet::new();
|
||||
collect_leaf_names(&catalog, &mut all_metrics);
|
||||
|
||||
// Load schemas from OpenAPI spec only (catalog schemas require runtime data)
|
||||
let openapi_json = load_openapi_json();
|
||||
let schemas = brk_bindgen::extract_schemas(&openapi_json);
|
||||
|
||||
// Generate JavaScript client output
|
||||
let mut js_output = String::new();
|
||||
writeln!(js_output, "// Auto-generated BRK JavaScript client").unwrap();
|
||||
writeln!(js_output, "// Do not edit manually\n").unwrap();
|
||||
brk_bindgen::javascript::types::generate_type_definitions(&mut js_output, &schemas);
|
||||
brk_bindgen::javascript::client::generate_base_client(&mut js_output);
|
||||
brk_bindgen::javascript::client::generate_index_accessors(
|
||||
&mut js_output,
|
||||
&metadata.index_set_patterns,
|
||||
);
|
||||
brk_bindgen::javascript::client::generate_structural_patterns(
|
||||
&mut js_output,
|
||||
&metadata.structural_patterns,
|
||||
&metadata,
|
||||
);
|
||||
brk_bindgen::javascript::tree::generate_tree_typedefs(
|
||||
&mut js_output,
|
||||
&metadata.catalog,
|
||||
&metadata,
|
||||
);
|
||||
brk_bindgen::javascript::tree::generate_main_client(
|
||||
&mut js_output,
|
||||
&metadata.catalog,
|
||||
&metadata,
|
||||
&[],
|
||||
);
|
||||
|
||||
// Count metrics that appear as direct string literals
|
||||
let mut direct_metrics = 0;
|
||||
for metric in &all_metrics {
|
||||
if js_output.contains(&format!("'{}'", metric))
|
||||
|| js_output.contains(&format!("\"{}\"", metric))
|
||||
{
|
||||
direct_metrics += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nGenerated JavaScript output stats:");
|
||||
println!(" Total metrics in catalog: {}", all_metrics.len());
|
||||
println!(" Direct string literals: {}", direct_metrics);
|
||||
println!(
|
||||
" Via pattern factories: {}",
|
||||
all_metrics.len() - direct_metrics
|
||||
);
|
||||
println!(" Output size: {} bytes", js_output.len());
|
||||
println!(" Output lines: {}", js_output.lines().count());
|
||||
|
||||
// Write output to actual client location
|
||||
let output_path = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../modules/brk-client/index.js"
|
||||
);
|
||||
std::fs::write(output_path, &js_output).expect("Failed to write JS client output");
|
||||
println!(" Wrote output to: {}", output_path);
|
||||
|
||||
// Verify the output contains key components
|
||||
assert!(js_output.contains("const _m ="), "Should define _m helper");
|
||||
assert!(js_output.contains("const _p ="), "Should define _p helper");
|
||||
assert!(
|
||||
js_output.contains("@typedef {Object} MetricsTree"),
|
||||
"Should have MetricsTree typedef"
|
||||
);
|
||||
assert!(
|
||||
js_output.contains("class BrkClient"),
|
||||
"Should have BrkClient class"
|
||||
);
|
||||
|
||||
// Verify all pattern factories are generated
|
||||
for pattern in &metadata.structural_patterns {
|
||||
assert!(
|
||||
js_output.contains(&format!("function create{}(", pattern.name)),
|
||||
"Missing pattern factory: {}",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
|
||||
println!("\nGenerated JavaScript client is complete!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generated_python_output() {
|
||||
let catalog = load_catalog();
|
||||
let metadata = ClientMetadata::from_catalog(catalog.clone());
|
||||
|
||||
// Collect all metric names from the catalog
|
||||
let mut all_metrics = HashSet::new();
|
||||
collect_leaf_names(&catalog, &mut all_metrics);
|
||||
|
||||
// Load schemas from OpenAPI spec only (catalog schemas require runtime data)
|
||||
let openapi_json = load_openapi_json();
|
||||
let schemas = brk_bindgen::extract_schemas(&openapi_json);
|
||||
|
||||
// Generate Python client output
|
||||
let mut py_output = String::new();
|
||||
writeln!(py_output, "# Auto-generated BRK Python client").unwrap();
|
||||
writeln!(py_output, "# Do not edit manually\n").unwrap();
|
||||
writeln!(py_output, "from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload").unwrap();
|
||||
writeln!(
|
||||
py_output,
|
||||
"from http.client import HTTPSConnection, HTTPConnection"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(py_output, "from urllib.parse import urlparse").unwrap();
|
||||
writeln!(py_output, "import json\n").unwrap();
|
||||
writeln!(py_output, "T = TypeVar('T')\n").unwrap();
|
||||
|
||||
brk_bindgen::python::types::generate_type_definitions(&mut py_output, &schemas);
|
||||
brk_bindgen::python::client::generate_base_client(&mut py_output);
|
||||
brk_bindgen::python::client::generate_endpoint_class(&mut py_output);
|
||||
brk_bindgen::python::client::generate_index_accessors(
|
||||
&mut py_output,
|
||||
&metadata.index_set_patterns,
|
||||
);
|
||||
brk_bindgen::python::client::generate_structural_patterns(
|
||||
&mut py_output,
|
||||
&metadata.structural_patterns,
|
||||
&metadata,
|
||||
);
|
||||
brk_bindgen::python::tree::generate_tree_classes(&mut py_output, &metadata.catalog, &metadata);
|
||||
brk_bindgen::python::api::generate_main_client(&mut py_output, &[]);
|
||||
|
||||
// Count metrics that appear as direct string literals
|
||||
let mut direct_metrics = 0;
|
||||
for metric in &all_metrics {
|
||||
if py_output.contains(&format!("'{}'", metric))
|
||||
|| py_output.contains(&format!("\"{}\"", metric))
|
||||
{
|
||||
direct_metrics += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nGenerated Python output stats:");
|
||||
println!(" Total metrics in catalog: {}", all_metrics.len());
|
||||
println!(" Direct string literals: {}", direct_metrics);
|
||||
println!(
|
||||
" Via pattern factories: {}",
|
||||
all_metrics.len() - direct_metrics
|
||||
);
|
||||
println!(" Output size: {} bytes", py_output.len());
|
||||
println!(" Output lines: {}", py_output.lines().count());
|
||||
|
||||
// Write output to actual client location
|
||||
let output_path = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../packages/brk_client/brk_client/__init__.py"
|
||||
);
|
||||
std::fs::write(output_path, &py_output).expect("Failed to write Python client output");
|
||||
println!(" Wrote output to: {}", output_path);
|
||||
|
||||
// Verify the output contains key components
|
||||
assert!(py_output.contains("def _m("), "Should define _m helper");
|
||||
assert!(py_output.contains("def _p("), "Should define _p helper");
|
||||
assert!(
|
||||
py_output.contains("class MetricsTree:"),
|
||||
"Should have MetricsTree class"
|
||||
);
|
||||
assert!(
|
||||
py_output.contains("class BrkClient"),
|
||||
"Should have BrkClient class"
|
||||
);
|
||||
|
||||
// Verify all pattern classes have constructors
|
||||
for pattern in &metadata.structural_patterns {
|
||||
assert!(
|
||||
py_output.contains(&format!("class {}:", pattern.name))
|
||||
|| py_output.contains(&format!("class {}(", pattern.name)),
|
||||
"Missing pattern class: {}",
|
||||
pattern.name
|
||||
);
|
||||
}
|
||||
|
||||
println!("\nGenerated Python client is complete!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cost_basis_relatives() {
|
||||
let catalog = load_catalog();
|
||||
|
||||
// Find cost_basis branches that have 3 direct children (max, min, percentiles)
|
||||
fn find_cost_basis_with_percentiles(
|
||||
node: &TreeNode,
|
||||
path: &str,
|
||||
) -> Vec<(String, Vec<(String, String)>)> {
|
||||
let mut results = Vec::new();
|
||||
if let TreeNode::Branch(children) = node {
|
||||
for (name, child) in children {
|
||||
let child_path = if path.is_empty() {
|
||||
name.clone()
|
||||
} else {
|
||||
format!("{}.{}", path, name)
|
||||
};
|
||||
|
||||
if name == "cost_basis"
|
||||
&& let TreeNode::Branch(cb_children) = child
|
||||
&& cb_children.contains_key("percentiles")
|
||||
{
|
||||
// Found a cost_basis with percentiles
|
||||
let mut metrics = Vec::new();
|
||||
for (field_name, field_node) in cb_children {
|
||||
match field_node {
|
||||
TreeNode::Leaf(leaf) => {
|
||||
metrics.push((field_name.clone(), leaf.name().to_string()));
|
||||
}
|
||||
TreeNode::Branch(pct_children) => {
|
||||
// Get first percentile as example
|
||||
if let Some((_, TreeNode::Leaf(first))) = pct_children.iter().next()
|
||||
{
|
||||
metrics.push((
|
||||
format!("{}.first", field_name),
|
||||
first.name().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results.push((child_path.clone(), metrics));
|
||||
}
|
||||
results.extend(find_cost_basis_with_percentiles(child, &child_path));
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
let instances = find_cost_basis_with_percentiles(&catalog, "");
|
||||
|
||||
println!("\nCostBasisPattern2 instances (with percentiles):");
|
||||
for (path, metrics) in instances.iter().take(10) {
|
||||
println!(" {}:", path);
|
||||
for (field, metric) in metrics {
|
||||
println!(" {} -> {}", field, metric);
|
||||
}
|
||||
}
|
||||
|
||||
// Now compute what relatives the pattern detection would see
|
||||
// The key is: percentiles returns its BASE (common prefix of pct05, pct10, etc.)
|
||||
// not the individual percentile metrics
|
||||
use brk_bindgen::find_common_prefix;
|
||||
|
||||
println!("\nComputing relatives (simulating branch base returns):");
|
||||
for (path, metrics) in instances.iter().take(5) {
|
||||
println!(" Instance: {}", path);
|
||||
|
||||
// For leaves (max, min), the base is the metric name
|
||||
// For branches (percentiles), the base is the common prefix of its children
|
||||
let mut child_bases: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
for (field, metric) in metrics {
|
||||
if field.starts_with("percentiles.") {
|
||||
// This is a percentile metric - compute what the percentiles branch would return
|
||||
// The base is the metric name with the pct suffix stripped
|
||||
let base = metric
|
||||
.strip_suffix("_pct05")
|
||||
.or_else(|| metric.strip_suffix("_pct10"))
|
||||
.unwrap_or(metric)
|
||||
.to_string();
|
||||
child_bases.insert("percentiles".to_string(), base);
|
||||
} else {
|
||||
child_bases.insert(field.clone(), metric.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect();
|
||||
println!(" Child bases:");
|
||||
for (field, base) in &child_bases {
|
||||
println!(" {} -> {}", field, base);
|
||||
}
|
||||
|
||||
if let Some(prefix) = find_common_prefix(&bases) {
|
||||
println!(" Common prefix: '{}'", prefix);
|
||||
for (field, base) in &child_bases {
|
||||
let relative = base.strip_prefix(&prefix).unwrap_or(base);
|
||||
println!(" {} -> relative '{}'", field, relative);
|
||||
}
|
||||
} else {
|
||||
println!(" No common prefix found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_cost_basis_pattern2_mode() {
|
||||
// Debug why CostBasisPattern2 has mode=None
|
||||
let catalog = load_catalog();
|
||||
let metadata = brk_bindgen::ClientMetadata::from_catalog(catalog.clone());
|
||||
let pattern_lookup = metadata.pattern_lookup();
|
||||
|
||||
let pattern = metadata
|
||||
.find_pattern("CostBasisPattern2")
|
||||
.expect("CostBasisPattern2 should exist");
|
||||
|
||||
println!("\nCostBasisPattern2 fields:");
|
||||
for field in &pattern.fields {
|
||||
println!(" {} (type: {})", field.name, field.rust_type);
|
||||
}
|
||||
println!("Mode: {:?}", pattern.mode);
|
||||
|
||||
// Now debug the instance collection
|
||||
#[derive(Debug, Clone)]
|
||||
struct DebugInstanceAnalysis {
|
||||
base: String,
|
||||
field_parts: std::collections::HashMap<String, String>,
|
||||
is_suffix_mode: bool,
|
||||
}
|
||||
|
||||
fn collect_debug(
|
||||
node: &TreeNode,
|
||||
pattern_lookup: &std::collections::HashMap<Vec<brk_bindgen::PatternField>, String>,
|
||||
all_analyses: &mut std::collections::HashMap<String, Vec<DebugInstanceAnalysis>>,
|
||||
) -> Option<String> {
|
||||
match node {
|
||||
TreeNode::Leaf(leaf) => Some(leaf.name().to_string()),
|
||||
TreeNode::Branch(children) => {
|
||||
let mut child_bases: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
for (field_name, child_node) in children {
|
||||
if let Some(base) = collect_debug(child_node, pattern_lookup, all_analyses) {
|
||||
child_bases.insert(field_name.clone(), base);
|
||||
}
|
||||
}
|
||||
|
||||
if child_bases.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Analyze this instance
|
||||
let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect();
|
||||
let (base, field_parts, is_suffix_mode) =
|
||||
if let Some(common_prefix) = brk_bindgen::find_common_prefix(&bases) {
|
||||
let base = common_prefix.trim_end_matches('_').to_string();
|
||||
let mut parts = std::collections::HashMap::new();
|
||||
for (field_name, child_base) in &child_bases {
|
||||
let relative = if *child_base == base {
|
||||
String::new()
|
||||
} else {
|
||||
child_base
|
||||
.strip_prefix(&common_prefix)
|
||||
.unwrap_or(child_base)
|
||||
.to_string()
|
||||
};
|
||||
parts.insert(field_name.clone(), relative);
|
||||
}
|
||||
(base, parts, true)
|
||||
} else {
|
||||
let base = child_bases.values().next().cloned().unwrap_or_default();
|
||||
let parts = child_bases
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
(base, parts, true)
|
||||
};
|
||||
|
||||
let analysis = DebugInstanceAnalysis {
|
||||
base: base.clone(),
|
||||
field_parts,
|
||||
is_suffix_mode,
|
||||
};
|
||||
|
||||
// Get the pattern name for this node
|
||||
let fields = brk_bindgen::get_node_fields(children, pattern_lookup);
|
||||
if let Some(pattern_name) = pattern_lookup.get(&fields) {
|
||||
all_analyses
|
||||
.entry(pattern_name.clone())
|
||||
.or_default()
|
||||
.push(analysis);
|
||||
}
|
||||
|
||||
Some(base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut all_analyses: std::collections::HashMap<String, Vec<DebugInstanceAnalysis>> =
|
||||
std::collections::HashMap::new();
|
||||
collect_debug(&catalog, &pattern_lookup, &mut all_analyses);
|
||||
|
||||
if let Some(analyses) = all_analyses.get("CostBasisPattern2") {
|
||||
println!(
|
||||
"\nCollected {} instances of CostBasisPattern2:",
|
||||
analyses.len()
|
||||
);
|
||||
for (i, a) in analyses.iter().enumerate() {
|
||||
println!(" Instance {}:", i);
|
||||
println!(" base: {}", a.base);
|
||||
println!(" is_suffix: {}", a.is_suffix_mode);
|
||||
println!(" field_parts:");
|
||||
for (f, p) in &a.field_parts {
|
||||
println!(" {} -> '{}'", f, p);
|
||||
}
|
||||
}
|
||||
|
||||
// Check consistency
|
||||
if analyses.len() >= 2 {
|
||||
let first = &analyses[0];
|
||||
for (i, a) in analyses.iter().enumerate().skip(1) {
|
||||
if a.is_suffix_mode != first.is_suffix_mode {
|
||||
println!(" INCONSISTENT: Instance {} has different mode", i);
|
||||
}
|
||||
for (field, part) in &a.field_parts {
|
||||
if first.field_parts.get(field) != Some(part) {
|
||||
println!(
|
||||
" INCONSISTENT: Instance {} field '{}' has part '{}' vs '{}'",
|
||||
i,
|
||||
field,
|
||||
part,
|
||||
first
|
||||
.field_parts
|
||||
.get(field)
|
||||
.unwrap_or(&"<missing>".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("\nNo instances collected for CostBasisPattern2!");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_cost_basis_prefix() {
|
||||
use brk_bindgen::find_common_prefix;
|
||||
|
||||
// Root-level cost_basis has:
|
||||
// max -> "max_cost_basis"
|
||||
// min -> "min_cost_basis"
|
||||
// percentiles -> "cost_basis" (base of pct05, pct10, etc.)
|
||||
|
||||
let bases = vec!["max_cost_basis", "min_cost_basis", "cost_basis"];
|
||||
let prefix = find_common_prefix(&bases);
|
||||
println!("Root cost_basis prefix: {:?}", prefix);
|
||||
|
||||
// Compare with nested cost_basis
|
||||
let nested_bases = vec![
|
||||
"utxos_at_least_15y_old_max_cost_basis",
|
||||
"utxos_at_least_15y_old_min_cost_basis",
|
||||
"utxos_at_least_15y_old_cost_basis",
|
||||
];
|
||||
let nested_prefix = find_common_prefix(&nested_bases);
|
||||
println!("Nested cost_basis prefix: {:?}", nested_prefix);
|
||||
}
|
||||
Reference in New Issue
Block a user