mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-30 03:53:34 +00:00
fix(brain): improve daily digest email — filter noise, better formatting
The daily digest was showing 10 identical "Self-reflection: training cycle" debug entries. Now: 1. Filters out debug category memories entirely 2. Filters known noise patterns (training cycles, IEEE events, DailyMed) 3. Skips content < 50 chars (scraping artifacts) 4. Category emojis for visual scanning 5. Cleaner layout with sentence-boundary truncation 6. Better subject line: "[pi brain] 5 new discoveries today" 7. Updated header: "What the Brain Learned Today" 8. Filters auto-generated tags from display Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
784e898fe5
commit
5d93bf44a0
1 changed files with 83 additions and 28 deletions
|
|
@ -5980,20 +5980,45 @@ async fn notify_digest(
|
|||
let topic = body["topic"].as_str();
|
||||
let hours = body["hours"].as_u64().unwrap_or(24);
|
||||
|
||||
// Gather recent discoveries from the store
|
||||
// Gather recent discoveries from the store — excluding debug/training noise
|
||||
let cutoff = chrono::Utc::now() - chrono::Duration::hours(hours as i64);
|
||||
let mut all = state.store.all_memories();
|
||||
all.sort_by(|a, b| b.created_at.cmp(&a.created_at));
|
||||
|
||||
// Filter by recency and optionally by topic
|
||||
// Filter out noise: training cycles, self-reflections, debug entries,
|
||||
// and low-signal web scraping results
|
||||
let noise_patterns: &[&str] = &[
|
||||
"Self-reflection: training cycle",
|
||||
"Fact Check: Self-reflection",
|
||||
"vTools Events",
|
||||
"Executive Committee Meeting",
|
||||
"DailyMed",
|
||||
"AccessGUDID",
|
||||
"Site en construction",
|
||||
];
|
||||
|
||||
let filtered: Vec<_> = all.iter()
|
||||
.filter(|m| {
|
||||
if m.created_at < cutoff {
|
||||
return false;
|
||||
}
|
||||
// Skip debug/auto-generated training noise
|
||||
if matches!(m.category, crate::types::BrainCategory::Debug) {
|
||||
return false;
|
||||
}
|
||||
// Skip known noise patterns in titles
|
||||
let title_lower = m.title.to_lowercase();
|
||||
if noise_patterns.iter().any(|p| title_lower.contains(&p.to_lowercase())) {
|
||||
return false;
|
||||
}
|
||||
// Skip very short content (likely scraping artifacts)
|
||||
if m.content.len() < 50 {
|
||||
return false;
|
||||
}
|
||||
// Apply optional topic filter
|
||||
topic.map_or(true, |t| {
|
||||
let t_lower = t.to_lowercase();
|
||||
m.title.to_lowercase().contains(&t_lower)
|
||||
title_lower.contains(&t_lower)
|
||||
|| m.content.to_lowercase().contains(&t_lower)
|
||||
|| m.tags.iter().any(|tag| tag.to_lowercase().contains(&t_lower))
|
||||
})
|
||||
|
|
@ -6009,27 +6034,52 @@ async fn notify_digest(
|
|||
})));
|
||||
}
|
||||
|
||||
// Build HTML rows
|
||||
// Build HTML rows — human-readable format
|
||||
let mut rows = String::new();
|
||||
let category_emoji = |cat: &crate::types::BrainCategory| -> &str {
|
||||
use crate::types::BrainCategory::*;
|
||||
match cat {
|
||||
Architecture => "🏗️",
|
||||
Pattern => "🔄",
|
||||
Solution => "💡",
|
||||
Security => "🔒",
|
||||
Convention => "📐",
|
||||
Performance => "⚡",
|
||||
Tooling => "🔧",
|
||||
Debug => "🐛",
|
||||
_ => "📝",
|
||||
}
|
||||
};
|
||||
|
||||
for (i, m) in filtered.iter().enumerate() {
|
||||
let title = if m.title.len() > 100 { &m.title[..100] } else { &m.title };
|
||||
let content = if m.content.len() > 200 { &m.content[..200] } else { &m.content };
|
||||
let quality = m.quality_score.mean();
|
||||
let tags_html: Vec<_> = m.tags.iter().take(4).map(|t| {
|
||||
format!("<span style=\"display:inline-block;background:#1a1a3a;color:#4fc3f7;padding:1px 6px;border-radius:3px;font-size:10px;margin:1px;\">{}</span>", t)
|
||||
}).collect();
|
||||
let title = if m.title.len() > 120 { &m.title[..120] } else { &m.title };
|
||||
// Take first ~250 chars but break at sentence boundary
|
||||
let content_raw = if m.content.len() > 250 { &m.content[..250] } else { &m.content };
|
||||
let content = match content_raw.rfind(". ") {
|
||||
Some(pos) if pos > 80 => &content_raw[..pos + 1],
|
||||
_ => content_raw,
|
||||
};
|
||||
let emoji = category_emoji(&m.category);
|
||||
let tags_html: Vec<_> = m.tags.iter()
|
||||
.filter(|t| !t.contains("auto-generated") && !t.contains("training-cycle"))
|
||||
.take(3)
|
||||
.map(|t| {
|
||||
format!("<span style=\"display:inline-block;background:#1a1a3a;color:#4fc3f7;padding:2px 8px;border-radius:4px;font-size:11px;margin:2px;\">{}</span>", t)
|
||||
}).collect();
|
||||
rows.push_str(&format!(
|
||||
r#"<tr style="border-bottom:1px solid #222;">
|
||||
<td style="padding:10px 0;">
|
||||
<strong style="color:#4fc3f7;">{num}. {title}</strong><br>
|
||||
<span style="color:#888;font-size:11px;">{cat} | quality: {quality:.2}</span> {tags}<br>
|
||||
<span style="color:#999;font-size:12px;">{content}...</span>
|
||||
r#"<tr style="border-bottom:1px solid #1a1a3a;">
|
||||
<td style="padding:14px 0;">
|
||||
<div style="margin-bottom:4px;">{emoji} <strong style="color:#e0e0ff;font-size:14px;">{title}</strong></div>
|
||||
<div style="margin-bottom:6px;">{tags}</div>
|
||||
<div style="color:#aaa;font-size:12px;line-height:1.5;">{content}</div>
|
||||
</td></tr>"#,
|
||||
num = i + 1,
|
||||
emoji = emoji,
|
||||
title = title,
|
||||
cat = m.category,
|
||||
quality = quality,
|
||||
tags = tags_html.join(""),
|
||||
tags = if tags_html.is_empty() {
|
||||
format!("<span style=\"color:#666;font-size:11px;\">{:?}</span>", m.category)
|
||||
} else {
|
||||
tags_html.join("")
|
||||
},
|
||||
content = content,
|
||||
));
|
||||
}
|
||||
|
|
@ -6042,16 +6092,21 @@ async fn notify_digest(
|
|||
let edges = state.graph.read().edge_count();
|
||||
|
||||
let html = format!(
|
||||
r#"<div style="font-family:'SF Mono',monospace;background:#0a0a23;color:#e0e0ff;padding:24px;border-radius:12px;max-width:600px;">
|
||||
<h2 style="color:#4fc3f7;margin:0 0 4px 0;">Daily Discovery Digest</h2>
|
||||
<p style="color:#888;font-size:12px;margin:0 0 4px 0;">Last {hours}h | {count} discoveries | {total} total memories | {edges} edges</p>
|
||||
r#"<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0a23;color:#e0e0ff;padding:24px;border-radius:12px;max-width:600px;">
|
||||
<h2 style="color:#4fc3f7;margin:0 0 8px 0;">What the Brain Learned Today</h2>
|
||||
<p style="color:#aaa;font-size:13px;margin:0 0 16px 0;">
|
||||
{count} new discoveries in the last {hours} hours.
|
||||
The brain now holds {total} memories connected by {edges} relationships.
|
||||
</p>
|
||||
{topic_line}
|
||||
<table style="color:#e0e0ff;font-size:13px;width:100%;">{rows}</table>
|
||||
<div style="background:#1a1a3a;padding:12px 16px;border-radius:8px;margin-top:16px;">
|
||||
<p style="color:#7fdbca;font-size:13px;margin:0;">Reply with <code style="color:#7fdbca;">search <query></code> to explore | <code style="color:#7fdbca;">help</code> for commands</p>
|
||||
<table style="color:#e0e0ff;font-size:13px;width:100%;border-spacing:0;">{rows}</table>
|
||||
<div style="background:#1a1a3a;padding:14px 18px;border-radius:8px;margin-top:20px;">
|
||||
<p style="color:#7fdbca;font-size:13px;margin:0 0 4px 0;">Explore the brain</p>
|
||||
<p style="color:#999;font-size:12px;margin:0;">Reply <code style="color:#7fdbca;background:#0a0a23;padding:2px 5px;border-radius:3px;">search seizure prediction</code> to find related knowledge,
|
||||
or <code style="color:#7fdbca;background:#0a0a23;padding:2px 5px;border-radius:3px;">help</code> for all commands.</p>
|
||||
</div>
|
||||
<div style="color:#666;margin-top:20px;font-size:11px;border-top:1px solid #222;padding-top:12px;">
|
||||
<a href="https://pi.ruv.io" style="color:#4fc3f7;">pi.ruv.io</a> | Powered by Resend
|
||||
<a href="https://pi.ruv.io" style="color:#4fc3f7;text-decoration:none;">pi.ruv.io</a> — the shared brain for collective intelligence
|
||||
</div></div>"#,
|
||||
hours = hours,
|
||||
count = filtered.len(),
|
||||
|
|
@ -6062,8 +6117,8 @@ async fn notify_digest(
|
|||
);
|
||||
|
||||
let subject = match topic {
|
||||
Some(t) => format!("[pi.ruv.io/discovery] Daily Digest: {}", t),
|
||||
None => "[pi.ruv.io/discovery] Daily Discovery Digest".into(),
|
||||
Some(t) => format!("[pi brain] {} — {} new discoveries", t, filtered.len()),
|
||||
None => format!("[pi brain] {} new discoveries today", filtered.len()),
|
||||
};
|
||||
|
||||
match notifier.send("discovery", &subject, &html).await {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue