///|
/// A user-facing catalog entry for a waveform artifact.
pub struct TraceCatalogItem {
name : String
category : String
description : String
signal_count : Int
change_count : Int
duration : Int64
tags : Array[String]
quality : Int
} derive(Debug)
///|
/// A catalog assembled from one or more analyzed traces.
pub struct TraceCatalogV2 {
items : Array[TraceCatalogItem]
total_signals : Int
total_changes : Int
total_duration : Int64
} derive(Debug)
///|
/// Build a catalog entry from a trace and descriptive metadata.
pub fn trace_catalog_item(
name : String,
category : String,
description : String,
trace : Trace,
tags : Array[String],
) -> TraceCatalogItem {
let quality = if analysis_safe(trace) {
trace_quality_score(trace)
} else {
0
}
{
name,
category,
description,
signal_count: trace.signals.length(),
change_count: trace.changes.length(),
duration: trace.end_time,
tags,
quality,
}
}
///|
/// Create a catalog from named traces.
pub fn trace_catalog(items : Array[TraceCatalogItem]) -> TraceCatalogV2 {
{
items,
total_signals: items.fold(init=0, fn(sum, item) { sum + item.signal_count }),
total_changes: items.fold(init=0, fn(sum, item) { sum + item.change_count }),
total_duration: items.fold(init=0L, fn(sum, item) { sum + item.duration }),
}
}
///|
/// Add an item to a catalog.
pub fn TraceCatalogV2::add(
self : TraceCatalogV2,
item : TraceCatalogItem,
) -> TraceCatalogV2 {
let items = self.items.copy()
items.push(item)
trace_catalog(items)
}
///|
/// Find catalog items by category.
pub fn TraceCatalogV2::category(
self : TraceCatalogV2,
category : String,
) -> Array[TraceCatalogItem] {
self.items.filter(fn(item) { item.category == category })
}
///|
/// Find catalog items containing a tag.
pub fn TraceCatalogV2::tagged(
self : TraceCatalogV2,
tag : String,
) -> Array[TraceCatalogItem] {
self.items.filter(fn(item) { item.tags.contains(tag) })
}
///|
/// Return the highest-quality artifact.
pub fn TraceCatalogV2::best(self : TraceCatalogV2) -> TraceCatalogItem? {
self.items.fold(init=None, fn(current, item) {
match current {
Some(previous) if item.quality > previous.quality => Some(item)
Some(previous) => Some(previous)
None => Some(item)
}
})
}
///|
/// Export catalog entries as CSV.
pub fn trace_catalog_csv(items : Array[TraceCatalogItem]) -> String {
let out = StringBuilder()
out.write_string(
"name,category,signals,changes,duration,quality,tags,description\n",
)
for item in items {
out.write_string(
escape_text(item.name) +
"," +
escape_text(item.category) +
"," +
item.signal_count.to_string() +
"," +
item.change_count.to_string() +
"," +
item.duration.to_string() +
"," +
item.quality.to_string() +
"," +
item.tags.join("|") +
"," +
escape_text(item.description) +
"\n",
)
}
out.to_string()
}
///|
/// Render catalog metadata as JSON.
pub fn TraceCatalogV2::to_json(self : TraceCatalogV2) -> String {
"{\"items\":[" +
self.items.map(trace_catalog_item_json).join(",") +
"],\"total_signals\":" +
self.total_signals.to_string() +
",\"total_changes\":" +
self.total_changes.to_string() +
",\"total_duration\":" +
self.total_duration.to_string() +
"}"
}
///|
/// Render catalog totals for terminal output.
pub fn TraceCatalogV2::to_text(self : TraceCatalogV2) -> String {
"items=" +
self.items.length().to_string() +
" signals=" +
self.total_signals.to_string() +
" changes=" +
self.total_changes.to_string() +
" duration=" +
self.total_duration.to_string()
}
///|
/// Encode one catalog item as JSON.
fn trace_catalog_item_json(item : TraceCatalogItem) -> String {
"{\"name\":\"" +
escape_text(item.name) +
"\",\"category\":\"" +
escape_text(item.category) +
"\",\"description\":\"" +
escape_text(item.description) +
"\",\"signals\":" +
item.signal_count.to_string() +
",\"changes\":" +
item.change_count.to_string() +
",\"duration\":" +
item.duration.to_string() +
",\"quality\":" +
item.quality.to_string() +
",\"tags\":[" +
item.tags.map(fn(tag) { "\"" + escape_text(tag) + "\"" }).join(",") +
"]}"
}