hypersteeldb 0.1.0

A database that compiles questions instead of guessing answers: typed vocabulary discovered from your documents, queries type-checked before they run, roaring-bitmap set algebra over reified hyperedges, and Dempster-Shafer evidence with an explicit conflict guard.
Documentation
//! Relational / CSV explorer: one situation per row, a `column/value` token per non-empty cell.
//! Raw cells are kept for display so the explorer shows the original table.

use crate::projector::{slug, CorpusKind, Projector, Situation};
use std::path::PathBuf;

pub struct CsvProjector {
    path: PathBuf,
    columns: Vec<String>,
}

impl CsvProjector {
    /// Opens the file to read the header now (so `columns()` is known before projection).
    pub fn open(path: impl Into<PathBuf>) -> std::io::Result<CsvProjector> {
        let path = path.into();
        let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&path)?;
        let columns = rdr.headers()?.iter().map(slug).collect();
        Ok(CsvProjector { path, columns })
    }
}

impl Projector for CsvProjector {
    fn columns(&self) -> Vec<String> {
        self.columns.clone()
    }
    fn kind(&self) -> CorpusKind {
        CorpusKind::Csv
    }
    fn source(&self) -> String {
        self.path.display().to_string()
    }
    fn project(self: Box<Self>, sink: &mut dyn FnMut(Situation)) -> std::io::Result<()> {
        let mut rdr = csv::ReaderBuilder::new().flexible(true).from_path(&self.path)?;
        for rec in rdr.records() {
            let rec = match rec {
                Ok(r) => r,
                Err(_) => continue,
            };
            let cells: Vec<String> = rec.iter().map(|c| c.to_string()).collect();
            let mut tokens = Vec::with_capacity(cells.len());
            let mut numbers = Vec::new();
            for (i, cell) in cells.iter().enumerate() {
                let v = cell.trim();
                if v.is_empty() {
                    continue;
                }
                let col = self.columns.get(i).map(|s| s.as_str()).unwrap_or("col");
                tokens.push(format!("{col}/{}", slug(v)));
                // numeric cells also become a queryable numeric field for `(num <col> <op> <value>)`
                if let Some(n) = crate::units::parse_number(v) {
                    numbers.push((col.to_string(), n));
                }
            }
            sink(Situation { tokens, display: cells, numbers, beliefs: Vec::new() });
        }
        Ok(())
    }
}