use anyhow::Result;
use rusqlite::Connection;
use crate::db;
use crate::models::{Memory, Tier};
use super::{CuratorConfig, CuratorReport, MIN_CONTENT_LEN};
pub(crate) struct CandidateBatch {
pub memories: Vec<Memory>,
pub truncated: bool,
}
pub(super) fn record_truncation(report: &mut CuratorReport, truncated: bool, cfg: &CuratorConfig) {
if truncated {
report.errors.push(format!(
"collect_candidates truncated at cap={} per tier; consider raising max_ops_per_cycle or paginating across cycles",
cfg.max_ops_per_cycle.saturating_mul(4)
));
}
}
pub(super) fn collect_candidates(conn: &Connection, cfg: &CuratorConfig) -> Result<CandidateBatch> {
let cap = cfg.max_ops_per_cycle.saturating_mul(4);
let mut out = Vec::new();
let mut truncated = false;
for tier in [Tier::Mid, Tier::Long] {
let batch = db::list(
conn,
None,
Some(&tier),
cap,
0,
None,
None,
None,
None,
None,
)?;
if batch.len() >= cap {
truncated = true;
}
out.extend(batch);
}
Ok(CandidateBatch {
memories: out,
truncated,
})
}
pub(super) fn needs_curation(mem: &Memory, cfg: &CuratorConfig) -> bool {
if mem.namespace.starts_with('_') {
return false;
}
if !cfg.include_namespaces.is_empty() && !cfg.include_namespaces.contains(&mem.namespace) {
return false;
}
if cfg.exclude_namespaces.contains(&mem.namespace) {
return false;
}
if mem.content.len() < MIN_CONTENT_LEN {
return false;
}
let has_auto_tags = mem
.metadata
.get("auto_tags")
.is_some_and(|v| v.as_array().is_some_and(|a| !a.is_empty()));
!has_auto_tags
}
pub(super) fn adjacent_memory(conn: &Connection, mem: &Memory) -> Result<Option<Memory>> {
let batch = db::list(
conn,
Some(&mem.namespace),
None,
8,
0,
None,
None,
None,
None,
None,
)?;
Ok(batch
.into_iter()
.find(|m| m.id != mem.id && m.content.len() >= MIN_CONTENT_LEN))
}