use napi_derive::napi;
use serde_json::json;
use crate::search::{tokenize as rs_tokenize, Bm25Index};
use crate::similarity::{lcs_similarity as rs_lcs, canonical_hash as rs_canonical};
use crate::curation::{calculate_score as rs_score, assess_text_quality as rs_quality};
use crate::pack::{validate_pack, HivePack};
use crate::memory::Mem;
#[napi]
pub fn tokenize(text: String) -> Vec<String> {
rs_tokenize(&text)
}
#[napi]
pub fn lcs_similarity(a: String, b: String) -> f64 {
rs_lcs(&a, &b)
}
#[napi]
pub fn canonical_hash(content: String) -> String {
rs_canonical(&content)
}
#[napi]
pub fn assess_text_quality(content: String) -> f64 {
rs_quality(&content)
}
#[napi]
pub fn calculate_curation_score(params_json: String) -> napi::Result<serde_json::Value> {
let params: serde_json::Value = serde_json::from_str(¶ms_json)
.map_err(|e| napi::Error::from_reason(format!("Invalid JSON: {}", e)))?;
let weight = params["weight"].as_f64().unwrap_or(0.5);
let confidence_factor = params["confidenceFactor"].as_f64().unwrap_or(0.4);
let days_since_update = params["daysSinceUpdate"].as_u64().unwrap_or(0) as u32;
let loaded_count = params["loadedCount"].as_u64().unwrap_or(0) as u32;
let referenced_count = params["referencedCount"].as_u64().unwrap_or(0) as u32;
let dispute_decay = params["disputeDecay"].as_f64().unwrap_or(0.0);
let level = params["level"].as_str().unwrap_or("fact");
let freshness = (1.0 - days_since_update as f64 / 180.0).max(0.0);
let text_quality = rs_quality(params["content"].as_str().unwrap_or(""));
let usage_bonus = {
let loaded = loaded_count as f64;
let referenced = referenced_count as f64;
(loaded * 0.01 + referenced * 0.02).min(0.5)
};
let level_bonus = match level {
"cornerstone" => 0.2,
"fact" => 0.0,
_ => -0.1,
};
let score = weight * confidence_factor * freshness * text_quality * (1.0 + usage_bonus)
- dispute_decay + level_bonus;
Ok(json!({
"score": score,
"textQuality": text_quality,
"freshness": freshness,
"usageBonus": usage_bonus,
}))
}
#[napi]
pub fn validate_pack_json(json_str: String) -> napi::Result<serde_json::Value> {
let pack: HivePack = serde_json::from_str(&json_str)
.map_err(|e| napi::Error::from_reason(format!("Invalid pack JSON: {}", e)))?;
let result = validate_pack(&pack);
Ok(json!({
"valid": result.valid,
"errors": result.errors,
"warnings": result.warnings,
"memoryCount": result.memory_count,
}))
}
#[napi]
pub fn bm25_search_json(query: String, memories_json: String) -> napi::Result<serde_json::Value> {
use crate::search::bm25_search;
let raw_mems: Vec<serde_json::Value> = serde_json::from_str(&memories_json)
.map_err(|e| napi::Error::from_reason(format!("Invalid memories JSON: {}", e)))?;
let mems: Vec<Mem> = raw_mems.iter().map(|v| {
Mem {
id: v["id"].as_str().unwrap_or("?").to_string(),
level: crate::memory::MemLevel::Fact,
zone: crate::memory::MemoryZone::General,
content: v["content"].as_str().unwrap_or("").to_string(),
weight: v["weight"].as_f64().unwrap_or(0.5),
tags: vec![],
source: None,
metadata: None,
last_matched: None,
match_count: 0,
loaded_count: v["loadedCount"].as_u64().unwrap_or(0) as u32,
referenced_count: v["referencedCount"].as_u64().unwrap_or(0) as u32,
last_loaded: None,
last_referenced: None,
supersedes: None,
superseded_at: None,
superseded_by: None,
trust: None,
created_at: "".to_string(),
updated_at: "".to_string(),
}
}).collect();
let mem_refs: Vec<&Mem> = mems.iter().collect();
let index = Bm25Index::build(&mem_refs);
let results = bm25_search(&query, &index, &mem_refs);
let hits: Vec<serde_json::Value> = results.iter().map(|r| {
let mem = &mems.iter().find(|m| m.id == r.id).unwrap();
json!({
"id": r.id,
"score": r.score,
"content": mem.content,
})
}).collect();
Ok(json!({ "hits": hits, "total": hits.len() }))
}