tokmd-analysis 1.13.1

Analysis logic and enrichers for tokmd receipts.
Documentation
use std::collections::BTreeMap;

use tokmd_analysis_types::{LangPurityReport, LangPurityRow, PolyglotReport};
use tokmd_scan::{round_f64, safe_ratio};
use tokmd_types::FileRow;

pub(super) fn build_lang_purity_report(rows: &[&FileRow]) -> LangPurityReport {
    let mut by_module: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();

    for row in rows {
        let entry = if let Some(existing) = by_module.get_mut(row.module.as_str()) {
            existing
        } else {
            by_module.insert(row.module.as_str(), BTreeMap::new());
            by_module.get_mut(row.module.as_str()).unwrap()
        };

        if let Some(val) = entry.get_mut(row.lang.as_str()) {
            *val += row.lines;
        } else {
            entry.insert(row.lang.as_str(), row.lines);
        }
    }

    let mut out = Vec::new();
    for (module, langs) in by_module {
        let mut total = 0usize;
        let mut dominant_lang: Option<&str> = None;
        let mut dominant_lines = 0usize;
        for (&lang, lines) in &langs {
            total += *lines;
            if *lines > dominant_lines
                || (*lines == dominant_lines && dominant_lang.is_some_and(|d| lang < d))
            {
                dominant_lines = *lines;
                dominant_lang = Some(lang);
            }
        }
        let pct = if total == 0 {
            0.0
        } else {
            safe_ratio(dominant_lines, total)
        };
        out.push(LangPurityRow {
            module: module.to_string(),
            lang_count: langs.len(),
            dominant_lang: dominant_lang.unwrap_or_default().to_string(),
            dominant_lines,
            dominant_pct: pct,
        });
    }

    out.sort_by(|a, b| a.module.cmp(&b.module));
    LangPurityReport { rows: out }
}

pub(super) fn build_polyglot_report(rows: &[&FileRow]) -> PolyglotReport {
    let mut by_lang: BTreeMap<&str, usize> = BTreeMap::new();
    let mut total = 0usize;

    for row in rows {
        if let Some(val) = by_lang.get_mut(row.lang.as_str()) {
            *val += row.code;
        } else {
            by_lang.insert(row.lang.as_str(), row.code);
        }
        total += row.code;
    }

    let mut entropy = 0.0;
    let mut dominant_lang: Option<&str> = None;
    let mut dominant_lines = 0usize;

    for (&lang, lines) in &by_lang {
        if *lines > dominant_lines
            || (*lines == dominant_lines && dominant_lang.is_some_and(|d| lang < d))
        {
            dominant_lines = *lines;
            dominant_lang = Some(lang);
        }
        if total > 0 && *lines > 0 {
            let p = *lines as f64 / total as f64;
            entropy -= p * p.log2();
        }
    }

    let dominant_pct = if total == 0 {
        0.0
    } else {
        safe_ratio(dominant_lines, total)
    };

    PolyglotReport {
        lang_count: by_lang.len(),
        entropy: round_f64(entropy, 4),
        dominant_lang: dominant_lang.unwrap_or_default().to_string(),
        dominant_lines,
        dominant_pct,
    }
}