query by NodeType instead of key prefix
Replace key prefix matching (journal#j-, daily-, weekly-, monthly-) with NodeType filters (EpisodicSession, EpisodicDaily, EpisodicWeekly, EpisodicMonthly) for all queries: journal-tail, digest gathering, digest auto-detection, experience mining dedup, and find_journal_node. Add EpisodicMonthly to NodeType enum and capnp schema. Key naming conventions (journal#j-TIMESTAMP-slug, daily-DATE, etc.) are retained for key generation — the fix is about how we find nodes, not how we name them. Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
parent
fd5591653d
commit
804578b977
8 changed files with 43 additions and 47 deletions
|
|
@ -45,6 +45,7 @@ enum NodeType {
|
||||||
episodicDaily @1;
|
episodicDaily @1;
|
||||||
episodicWeekly @2;
|
episodicWeekly @2;
|
||||||
semantic @3;
|
semantic @3;
|
||||||
|
episodicMonthly @4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Provenance {
|
enum Provenance {
|
||||||
|
|
|
||||||
|
|
@ -145,15 +145,13 @@ fn gather(level: &DigestLevel, store: &Store, arg: &str) -> Result<(String, Vec<
|
||||||
.collect();
|
.collect();
|
||||||
load_child_digests(store, child_name, &child_labels)
|
load_child_digests(store, child_name, &child_labels)
|
||||||
} else {
|
} else {
|
||||||
// Leaf level: scan store for journal entries matching label
|
// Leaf level: scan store for episodic entries matching date
|
||||||
let date_re = Regex::new(&format!(
|
|
||||||
r"^journal#j-{}", regex::escape(&label)
|
|
||||||
)).unwrap();
|
|
||||||
let mut entries: Vec<_> = store.nodes.values()
|
let mut entries: Vec<_> = store.nodes.values()
|
||||||
.filter(|n| date_re.is_match(&n.key))
|
.filter(|n| n.node_type == store::NodeType::EpisodicSession
|
||||||
|
&& n.timestamp > 0
|
||||||
|
&& store::format_date(n.timestamp) == label)
|
||||||
.map(|n| {
|
.map(|n| {
|
||||||
let ts = n.key.strip_prefix("journal#j-").unwrap_or(&n.key);
|
(store::format_datetime(n.timestamp), n.content.clone())
|
||||||
(ts.to_string(), n.content.clone())
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||||
|
|
@ -252,14 +250,10 @@ pub fn generate(store: &mut Store, level_name: &str, arg: &str) -> Result<(), St
|
||||||
pub fn digest_auto(store: &mut Store) -> Result<(), String> {
|
pub fn digest_auto(store: &mut Store) -> Result<(), String> {
|
||||||
let today = Local::now().format("%Y-%m-%d").to_string();
|
let today = Local::now().format("%Y-%m-%d").to_string();
|
||||||
|
|
||||||
// Collect all dates with journal entries
|
// Collect all dates with episodic entries
|
||||||
let date_re = Regex::new(r"^\d{4}-\d{2}-\d{2}").unwrap();
|
let dates: Vec<String> = store.nodes.values()
|
||||||
let dates: Vec<String> = store.nodes.keys()
|
.filter(|n| n.node_type == store::NodeType::EpisodicSession && n.timestamp > 0)
|
||||||
.filter_map(|key| {
|
.map(|n| store::format_date(n.timestamp))
|
||||||
key.strip_prefix("journal#j-")
|
|
||||||
.filter(|rest| rest.len() >= 10 && date_re.is_match(rest))
|
|
||||||
.map(|rest| rest[..10].to_string())
|
|
||||||
})
|
|
||||||
.collect::<BTreeSet<_>>()
|
.collect::<BTreeSet<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -408,10 +402,12 @@ fn parse_digest_node_links(key: &str, content: &str) -> Vec<DigestLink> {
|
||||||
pub fn parse_all_digest_links(store: &Store) -> Vec<DigestLink> {
|
pub fn parse_all_digest_links(store: &Store) -> Vec<DigestLink> {
|
||||||
let mut all_links = Vec::new();
|
let mut all_links = Vec::new();
|
||||||
|
|
||||||
let mut digest_keys: Vec<&String> = store.nodes.keys()
|
let mut digest_keys: Vec<&String> = store.nodes.iter()
|
||||||
.filter(|k| k.starts_with("daily-")
|
.filter(|(_, n)| matches!(n.node_type,
|
||||||
|| k.starts_with("weekly-")
|
store::NodeType::EpisodicDaily
|
||||||
|| k.starts_with("monthly-"))
|
| store::NodeType::EpisodicWeekly
|
||||||
|
| store::NodeType::EpisodicMonthly))
|
||||||
|
.map(|(k, _)| k)
|
||||||
.collect();
|
.collect();
|
||||||
digest_keys.sort();
|
digest_keys.sort();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -336,21 +336,11 @@ pub fn experience_mine(
|
||||||
.map(|n| n.content.clone())
|
.map(|n| n.content.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Get recent journal entries to avoid duplication
|
// Get recent episodic entries to avoid duplication
|
||||||
let key_date_re = Regex::new(r"^journal#j-(\d{4}-\d{2}-\d{2}[t-]\d{2}-\d{2})").unwrap();
|
|
||||||
let date_re = Regex::new(r"(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2})").unwrap();
|
|
||||||
let mut journal: Vec<_> = store.nodes.values()
|
let mut journal: Vec<_> = store.nodes.values()
|
||||||
.filter(|node| node.key.starts_with("journal#j-"))
|
.filter(|node| matches!(node.node_type, store::NodeType::EpisodicSession))
|
||||||
.collect();
|
.collect();
|
||||||
journal.sort_by(|a, b| {
|
journal.sort_by_key(|n| n.timestamp);
|
||||||
let ak = key_date_re.captures(&a.key).map(|c| c[1].to_string())
|
|
||||||
.or_else(|| date_re.captures(&a.content).map(|c| c[1].to_string()))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let bk = key_date_re.captures(&b.key).map(|c| c[1].to_string())
|
|
||||||
.or_else(|| date_re.captures(&b.content).map(|c| c[1].to_string()))
|
|
||||||
.unwrap_or_default();
|
|
||||||
ak.cmp(&bk)
|
|
||||||
});
|
|
||||||
let recent: String = journal.iter().rev().take(10)
|
let recent: String = journal.iter().rev().take(10)
|
||||||
.map(|n| format!("---\n{}\n", n.content))
|
.map(|n| format!("---\n{}\n", n.content))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
30
src/main.rs
30
src/main.rs
|
|
@ -1122,7 +1122,8 @@ fn cmd_trace(args: &[String]) -> Result<(), String> {
|
||||||
episodic_session.push(entry),
|
episodic_session.push(entry),
|
||||||
store::NodeType::EpisodicDaily =>
|
store::NodeType::EpisodicDaily =>
|
||||||
episodic_daily.push(entry),
|
episodic_daily.push(entry),
|
||||||
store::NodeType::EpisodicWeekly =>
|
store::NodeType::EpisodicWeekly
|
||||||
|
| store::NodeType::EpisodicMonthly =>
|
||||||
episodic_weekly.push(entry),
|
episodic_weekly.push(entry),
|
||||||
store::NodeType::Semantic =>
|
store::NodeType::Semantic =>
|
||||||
semantic.push(entry),
|
semantic.push(entry),
|
||||||
|
|
@ -1855,12 +1856,12 @@ fn cmd_journal_tail(args: &[String]) -> Result<(), String> {
|
||||||
// Original journal-tail behavior
|
// Original journal-tail behavior
|
||||||
journal_tail_entries(&store, n, full)
|
journal_tail_entries(&store, n, full)
|
||||||
} else {
|
} else {
|
||||||
let prefix = match level {
|
let node_type = match level {
|
||||||
1 => "daily-",
|
1 => store::NodeType::EpisodicDaily,
|
||||||
2 => "weekly-",
|
2 => store::NodeType::EpisodicWeekly,
|
||||||
_ => "monthly-",
|
_ => store::NodeType::EpisodicMonthly,
|
||||||
};
|
};
|
||||||
journal_tail_digests(&store, prefix, n, full)
|
journal_tail_digests(&store, node_type, n, full)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1916,17 +1917,22 @@ fn journal_tail_entries(store: &store::Store, n: usize, full: bool) -> Result<()
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn journal_tail_digests(store: &store::Store, prefix: &str, n: usize, full: bool) -> Result<(), String> {
|
fn journal_tail_digests(store: &store::Store, node_type: store::NodeType, n: usize, full: bool) -> Result<(), String> {
|
||||||
let mut digests: Vec<_> = store.nodes.values()
|
let mut digests: Vec<_> = store.nodes.values()
|
||||||
.filter(|node| node.key.starts_with(prefix))
|
.filter(|node| node.node_type == node_type)
|
||||||
.collect();
|
.collect();
|
||||||
// Sort by key — the date/week label sorts lexicographically
|
// Sort by timestamp, fall back to key for lexicographic ordering
|
||||||
digests.sort_by(|a, b| a.key.cmp(&b.key));
|
digests.sort_by(|a, b| {
|
||||||
|
if a.timestamp > 0 && b.timestamp > 0 {
|
||||||
|
a.timestamp.cmp(&b.timestamp)
|
||||||
|
} else {
|
||||||
|
a.key.cmp(&b.key)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let skip = if digests.len() > n { digests.len() - n } else { 0 };
|
let skip = if digests.len() > n { digests.len() - n } else { 0 };
|
||||||
for node in digests.iter().skip(skip) {
|
for node in digests.iter().skip(skip) {
|
||||||
let label = node.key.strip_prefix(prefix)
|
let label = &node.key;
|
||||||
.unwrap_or(&node.key);
|
|
||||||
let title = extract_title(&node.content);
|
let title = extract_title(&node.content);
|
||||||
if full {
|
if full {
|
||||||
println!("--- [{}] {} ---\n{}\n", label, title, node.content);
|
println!("--- [{}] {} ---\n{}\n", label, title, node.content);
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,7 @@ fn node_type_label(nt: NodeType) -> &'static str {
|
||||||
NodeType::EpisodicSession => "episodic_session",
|
NodeType::EpisodicSession => "episodic_session",
|
||||||
NodeType::EpisodicDaily => "episodic_daily",
|
NodeType::EpisodicDaily => "episodic_daily",
|
||||||
NodeType::EpisodicWeekly => "episodic_weekly",
|
NodeType::EpisodicWeekly => "episodic_weekly",
|
||||||
|
NodeType::EpisodicMonthly => "episodic_monthly",
|
||||||
NodeType::Semantic => "semantic",
|
NodeType::Semantic => "semantic",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ impl Store {
|
||||||
Some(output.trim_end().to_string())
|
Some(output.trim_end().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the journal node that best matches the given entry text.
|
/// Find the episodic node that best matches the given entry text.
|
||||||
pub fn find_journal_node(&self, entry_text: &str) -> Option<String> {
|
pub fn find_journal_node(&self, entry_text: &str) -> Option<String> {
|
||||||
if entry_text.is_empty() {
|
if entry_text.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -308,7 +308,7 @@ impl Store {
|
||||||
let mut best_score = 0;
|
let mut best_score = 0;
|
||||||
|
|
||||||
for (key, node) in &self.nodes {
|
for (key, node) in &self.nodes {
|
||||||
if !key.starts_with("journal#") {
|
if node.node_type != NodeType::EpisodicSession {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let content_lower = node.content.to_lowercase();
|
let content_lower = node.content.to_lowercase();
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ pub fn classify_filename(filename: &str) -> NodeType {
|
||||||
let bare = filename.strip_suffix(".md").unwrap_or(filename);
|
let bare = filename.strip_suffix(".md").unwrap_or(filename);
|
||||||
if bare.starts_with("daily-") { NodeType::EpisodicDaily }
|
if bare.starts_with("daily-") { NodeType::EpisodicDaily }
|
||||||
else if bare.starts_with("weekly-") { NodeType::EpisodicWeekly }
|
else if bare.starts_with("weekly-") { NodeType::EpisodicWeekly }
|
||||||
|
else if bare.starts_with("monthly-") { NodeType::EpisodicMonthly }
|
||||||
else if bare == "journal" { NodeType::EpisodicSession }
|
else if bare == "journal" { NodeType::EpisodicSession }
|
||||||
else { NodeType::Semantic }
|
else { NodeType::Semantic }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ pub enum NodeType {
|
||||||
EpisodicDaily,
|
EpisodicDaily,
|
||||||
EpisodicWeekly,
|
EpisodicWeekly,
|
||||||
Semantic,
|
Semantic,
|
||||||
|
EpisodicMonthly,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||||
|
|
@ -345,7 +346,7 @@ pub enum RelationType {
|
||||||
}
|
}
|
||||||
|
|
||||||
capnp_enum!(NodeType, memory_capnp::NodeType,
|
capnp_enum!(NodeType, memory_capnp::NodeType,
|
||||||
[EpisodicSession, EpisodicDaily, EpisodicWeekly, Semantic]);
|
[EpisodicSession, EpisodicDaily, EpisodicWeekly, Semantic, EpisodicMonthly]);
|
||||||
|
|
||||||
capnp_enum!(Provenance, memory_capnp::Provenance,
|
capnp_enum!(Provenance, memory_capnp::Provenance,
|
||||||
[Manual, Journal, Agent, Dream, Derived,
|
[Manual, Journal, Agent, Dream, Derived,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue