use serde_json::Value;
pub fn render_program(v: &Value) -> Option<String> {
match v.get("program").and_then(|p| p.as_str()) {
Some("breakdown") => Some(render_breakdown(v)),
Some("crosstab") => Some(render_crosstab(v)),
Some("rank") => Some(render_rank(v)),
Some("structure") => render_structure(v),
Some("profile") => Some(render_profile(v)),
_ => None,
}
}
fn render_profile(v: &Value) -> String {
let sections = v.get("sections").and_then(|s| s.as_array()).cloned().unwrap_or_default();
if sections.is_empty() {
return "No informative structure found in the matching rows.".to_string();
}
let scope = v.get("scope_size").and_then(|x| x.as_u64()).unwrap_or(0);
let mut out = vec![format!("Profile of {scope} matching situations:")];
for s in §ions {
if let Some(r) = s.get("result").and_then(render_program) {
out.push(format!("\n{r}"));
}
}
out.join("\n")
}
fn render_breakdown(v: &Value) -> String {
let facet = v.get("facet").and_then(|x| x.as_str()).unwrap_or("value");
let total = v.get("total").and_then(|x| x.as_u64()).unwrap_or(0);
let parts = v.get("partition").and_then(|x| x.as_array()).cloned().unwrap_or_default();
if parts.is_empty() {
return format!("No {facet} values matched ({total} situations in scope).");
}
let items: Vec<String> = parts
.iter()
.map(|p| {
let val = p.get("value").and_then(|x| x.as_str()).unwrap_or("?");
let n = p.get("count").and_then(|x| x.as_u64()).unwrap_or(0);
format!("{val} ({n})")
})
.collect();
let top = parts[0].get("value").and_then(|x| x.as_str()).unwrap_or("?");
format!("Breakdown by {facet} across {total} situations: {}. Most common: {top}.", items.join(", "))
}
fn render_crosstab(v: &Value) -> String {
let row_facet = v.get("row_facet").and_then(|x| x.as_str()).unwrap_or("row");
let col_facet = v.get("col_facet").and_then(|x| x.as_str()).unwrap_or("col");
let total = v.get("total").and_then(|x| x.as_u64()).unwrap_or(0);
let matrix = v.get("matrix").and_then(|x| x.as_array()).cloned().unwrap_or_default();
if matrix.is_empty() {
return format!("No {row_facet} × {col_facet} combinations matched ({total} situations).");
}
let mut lines = Vec::new();
for row in &matrix {
let name = row.get("row").and_then(|x| x.as_str()).unwrap_or("?");
let cells = row.get("cells").and_then(|x| x.as_array()).cloned().unwrap_or_default();
let mut best: Option<(&str, u64)> = None;
let parts: Vec<String> = cells
.iter()
.map(|c| {
let col = c.get("col").and_then(|x| x.as_str()).unwrap_or("?");
let n = c.get("count").and_then(|x| x.as_u64()).unwrap_or(0);
if best.map(|(_, bn)| n > bn).unwrap_or(true) {
best = Some((col, n));
}
format!("{col} {n}")
})
.collect();
let most = best.filter(|(_, n)| *n > 0).map(|(c, _)| format!(" → most common: {c}")).unwrap_or_default();
lines.push(format!("- {name}: {}{most}", parts.join(", ")));
}
format!("{row_facet} × {col_facet} ({total} situations):\n{}", lines.join("\n"))
}
fn render_rank(v: &Value) -> String {
let facet = v.get("facet").and_then(|x| x.as_str()).unwrap_or("value");
let ranked = v.get("ranked").and_then(|x| x.as_array()).cloned().unwrap_or_default();
if ranked.is_empty() {
return format!("No {facet} values to rank.");
}
let items: Vec<String> = ranked
.iter()
.take(8)
.map(|r| {
let tok = r.get("token").and_then(|x| x.as_str()).unwrap_or("?");
let leaf = tok.rsplit('/').next().unwrap_or(tok);
match r.get("mdus").and_then(|x| x.as_f64()) {
Some(m) => format!("{leaf} ({m:.2})"),
None => leaf.to_string(),
}
})
.collect();
format!("Top {facet} by salience: {}.", items.join(", "))
}
fn render_structure(v: &Value) -> Option<String> {
match v.get("op").and_then(|x| x.as_str()) {
Some("cooccurs") => {
let focus = v.get("focus").and_then(|x| x.as_str()).unwrap_or("?");
let co = v.get("cooccurs").and_then(|x| x.as_array()).cloned().unwrap_or_default();
if co.is_empty() {
return Some(format!("{focus} has no notable co-occurring tokens."));
}
let items: Vec<String> = co
.iter()
.take(8)
.map(|c| {
let tok = c.get("token").and_then(|x| x.as_str()).unwrap_or("?");
let leaf = tok.rsplit('/').next().unwrap_or(tok);
let n = c.get("shared").and_then(|x| x.as_u64()).unwrap_or(0);
format!("{leaf} ({n})")
})
.collect();
Some(format!("{focus} most co-occurs with: {}.", items.join(", ")))
}
Some("s_path") => {
let (a, b) = (v.get("a").and_then(|x| x.as_str()).unwrap_or("?"), v.get("b").and_then(|x| x.as_str()).unwrap_or("?"));
match v.get("path").and_then(|p| p.as_array()) {
Some(p) if !p.is_empty() => {
let hops: Vec<&str> = p.iter().filter_map(|x| x.as_str()).map(|t| t.rsplit('/').next().unwrap_or(t)).collect();
Some(format!("Path {a} → {b}: {}.", hops.join(" → ")))
}
_ => Some(format!("No connecting path between {a} and {b} at this overlap threshold.")),
}
}
Some("s_clusters") => {
let cl = v.get("clusters").and_then(|x| x.as_array()).cloned().unwrap_or_default();
if cl.is_empty() {
return Some("No multi-token clusters at this overlap threshold.".into());
}
let lines: Vec<String> = cl
.iter()
.take(6)
.map(|c| {
let size = c.get("size").and_then(|x| x.as_u64()).unwrap_or(0);
let toks: Vec<&str> = c
.get("tokens")
.and_then(|t| t.as_array())
.map(|a| a.iter().filter_map(|x| x.as_str()).map(|t| t.rsplit('/').next().unwrap_or(t)).take(8).collect())
.unwrap_or_default();
format!("- [{size}] {}", toks.join(", "))
})
.collect();
Some(format!("{} concept clusters:\n{}", cl.len(), lines.join("\n")))
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn crosstab_reports_argmax_per_row() {
let v = json!({
"program": "crosstab", "row_facet": "country", "col_facet": "powertrain", "total": 20,
"matrix": [
{"row": "japan", "cells": [{"col":"electric","count":2},{"col":"diesel","count":1},{"col":"hybrid","count":2}]},
{"row": "germany", "cells": [{"col":"electric","count":3},{"col":"diesel","count":2},{"col":"hybrid","count":0}]}
]
});
let out = render_program(&v).unwrap();
assert!(out.contains("country × powertrain"));
assert!(out.contains("germany: electric 3, diesel 2, hybrid 0 → most common: electric"));
assert!(out.contains("japan:"));
}
#[test]
fn breakdown_and_rank_render() {
let b = json!({"program":"breakdown","facet":"country","total":20,"partition":[{"value":"usa","count":6},{"value":"japan","count":5}]});
assert!(render_program(&b).unwrap().contains("Most common: usa"));
let r = json!({"program":"rank","facet":"ent","ranked":[{"token":"ent/connect","mdus":1.0},{"token":"ent/agent","mdus":0.76}]});
assert!(render_program(&r).unwrap().contains("connect (1.00)"));
}
#[test]
fn non_program_returns_none() {
assert!(render_program(&json!({"foo": "bar"})).is_none());
}
}