Skip to main content

ainl_compression/
profiles.rs

1//! Built-in compression profiles (policy vocabulary for hosts).
2//!
3//! Persistence and per-project overrides live in embedding hosts (`openfang-runtime`, etc.);
4//! this module defines **portable defaults** and **project → profile** heuristics.
5
6use crate::EfficientMode;
7
8/// One named profile: stable id, human label, and default eco mode.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct CompressionProfile {
11    pub id: &'static str,
12    pub display_name: &'static str,
13    pub default_mode: EfficientMode,
14    pub description: &'static str,
15}
16
17/// Registry of profiles shipped with `ainl-compression` (no disk I/O).
18pub const BUILTIN_PROFILES: &[CompressionProfile] = &[
19    CompressionProfile {
20        id: "default",
21        display_name: "Default",
22        default_mode: EfficientMode::Balanced,
23        description: "General prompts; balanced retention (~55%).",
24    },
25    CompressionProfile {
26        id: "cost_sensitive",
27        display_name: "Cost sensitive",
28        default_mode: EfficientMode::Aggressive,
29        description: "High-volume / batch paths; prefer stronger reduction when safe.",
30    },
31    CompressionProfile {
32        id: "quality_preserve",
33        display_name: "Quality preserve",
34        default_mode: EfficientMode::Balanced,
35        description: "Customer- or prod-adjacent contexts; conservative defaults (future: stricter preserve gates).",
36    },
37];
38
39#[must_use]
40pub fn list_builtin_profiles() -> &'static [CompressionProfile] {
41    BUILTIN_PROFILES
42}
43
44#[must_use]
45pub fn resolve_builtin_profile(id: &str) -> Option<&'static CompressionProfile> {
46    let k = id.trim();
47    BUILTIN_PROFILES
48        .iter()
49        .find(|p| p.id.eq_ignore_ascii_case(k))
50}
51
52/// Heuristic mapping from a **project identifier** (repo slug, cwd basename, MCP `project_id`) to a built-in profile id.
53///
54/// Hosts may override with on-disk config later; this stays dependency-free.
55#[must_use]
56pub fn suggest_profile_id_for_project(project_id: &str) -> &'static str {
57    let p = project_id.to_ascii_lowercase();
58    if p.contains("prod")
59        || p.contains("customer")
60        || p.contains("staging")
61        || p.contains("release")
62    {
63        "quality_preserve"
64    } else if p.contains("batch") || p.contains("ci") || p.contains("cron") || p.contains("scaled") {
65        "cost_sensitive"
66    } else {
67        "default"
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn resolve_roundtrip() {
77        let p = resolve_builtin_profile("DEFAULT").expect("default");
78        assert_eq!(p.id, "default");
79        assert_eq!(p.default_mode, EfficientMode::Balanced);
80    }
81
82    #[test]
83    fn suggest_prod_leans_quality() {
84        assert_eq!(
85            suggest_profile_id_for_project("acme-customer-prod"),
86            "quality_preserve"
87        );
88    }
89
90    #[test]
91    fn suggest_ci_leans_cost() {
92        assert_eq!(suggest_profile_id_for_project("nightly-ci-batch"), "cost_sensitive");
93    }
94}