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    {
66        "cost_sensitive"
67    } else {
68        "default"
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn resolve_roundtrip() {
78        let p = resolve_builtin_profile("DEFAULT").expect("default");
79        assert_eq!(p.id, "default");
80        assert_eq!(p.default_mode, EfficientMode::Balanced);
81    }
82
83    #[test]
84    fn suggest_prod_leans_quality() {
85        assert_eq!(
86            suggest_profile_id_for_project("acme-customer-prod"),
87            "quality_preserve"
88        );
89    }
90
91    #[test]
92    fn suggest_ci_leans_cost() {
93        assert_eq!(
94            suggest_profile_id_for_project("nightly-ci-batch"),
95            "cost_sensitive"
96        );
97    }
98}