agent-supplements-rec 0.1.0

Curated supplement recommendation engine for longevity biomarker optimization
use crate::types::{Brand, CatalogFile, Interaction, Product};

const EMBEDDED_CATALOG: &str = include_str!("../data/catalog.toml");

/// Load the embedded TOML catalog. Panics if the embedded catalog is invalid.
pub fn load_catalog() -> CatalogFile {
    toml::from_str(EMBEDDED_CATALOG).expect("embedded catalog.toml is invalid")
}

/// Find a product by ID.
pub fn find_product<'a>(catalog: &'a CatalogFile, id: &str) -> Option<&'a Product> {
    catalog.product.iter().find(|p| p.id == id)
}

/// Find a brand by ID.
pub fn find_brand<'a>(catalog: &'a CatalogFile, id: &str) -> Option<&'a Brand> {
    catalog.brand.iter().find(|b| b.id == id)
}

/// Find all products targeting a specific biomarker (standardized_name).
pub fn products_for_biomarker<'a>(catalog: &'a CatalogFile, biomarker: &str) -> Vec<&'a Product> {
    let bio_lower = biomarker.to_lowercase();
    catalog
        .product
        .iter()
        .filter(|p| {
            p.targets
                .iter()
                .any(|t| t.biomarker.to_lowercase() == bio_lower)
        })
        .collect()
}

/// Filter products by category.
pub fn products_by_category<'a>(catalog: &'a CatalogFile, category: &str) -> Vec<&'a Product> {
    let cat_lower = category.to_lowercase();
    catalog
        .product
        .iter()
        .filter(|p| p.category.to_lowercase() == cat_lower)
        .collect()
}

/// Filter products by brand ID.
pub fn products_by_brand<'a>(catalog: &'a CatalogFile, brand_id: &str) -> Vec<&'a Product> {
    let bid_lower = brand_id.to_lowercase();
    catalog
        .product
        .iter()
        .filter(|p| p.brand_id.to_lowercase() == bid_lower)
        .collect()
}

/// Search products by case-insensitive substring across name, category, brand,
/// targets, and description.
pub fn search_products<'a>(catalog: &'a CatalogFile, query: &str) -> Vec<&'a Product> {
    let q = query.to_lowercase();
    catalog
        .product
        .iter()
        .filter(|p| {
            p.name.to_lowercase().contains(&q)
                || p.category.to_lowercase().contains(&q)
                || p.brand_id.to_lowercase().contains(&q)
                || p.description.to_lowercase().contains(&q)
                || p.id.to_lowercase().contains(&q)
                || p.targets
                    .iter()
                    .any(|t| t.biomarker.to_lowercase().contains(&q) || t.mechanism.to_lowercase().contains(&q))
        })
        .collect()
}

/// Find interactions among a set of product IDs (checks all pairs).
pub fn find_interactions<'a>(catalog: &'a CatalogFile, ids: &[&str]) -> Vec<&'a Interaction> {
    catalog
        .interaction
        .iter()
        .filter(|i| {
            let a_in = ids.contains(&i.product_a.as_str());
            let b_in = ids.contains(&i.product_b.as_str());
            a_in && b_in
        })
        .collect()
}