Skip to main content

fib_quant/
receipt.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    codebook::CODEBOOK_SCHEMA,
5    codec::CODE_SCHEMA,
6    profile::{FibQuantProfileV1, NormFormat, PROFILE_SCHEMA},
7    rotation::ROTATION_ALGORITHM_VERSION,
8};
9
10pub const RECEIPT_SCHEMA: &str = "fib_quant_compression_receipt_v1";
11
12/// Compression receipt emitted by `encode_with_receipt`.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct FibQuantCompressionReceiptV1 {
15    /// Stable schema marker.
16    pub schema_version: String,
17    /// Profile digest.
18    pub profile_digest: String,
19    /// Codebook digest.
20    pub codebook_digest: String,
21    /// Rotation digest.
22    pub rotation_digest: String,
23    /// Digest over the canonical source vector bytes.
24    pub source_vector_digest: String,
25    /// Encoded payload digest.
26    pub encoded_digest: String,
27    /// Norm payload format used by the encoded artifact.
28    pub norm_format: NormFormat,
29    /// Encoded artifact schema marker.
30    pub code_schema_version: String,
31    /// Profile schema marker.
32    pub profile_schema_version: String,
33    /// Codebook schema marker.
34    pub codebook_schema_version: String,
35    /// Ambient dimension `d`.
36    pub ambient_dim: u32,
37    /// Block dimension `k`.
38    pub block_dim: u32,
39    /// Codebook size `N`.
40    pub codebook_size: u32,
41    /// Paper rate.
42    pub paper_rate_bits_per_coord: f64,
43    /// Wire index bits.
44    pub wire_index_bits: u8,
45    /// Wire rate.
46    pub wire_bits_per_coord: f64,
47    /// Rotation seed.
48    pub rotation_seed: u64,
49    /// Rotation algorithm identity.
50    pub rotation_algorithm_version: String,
51    /// Codebook seed.
52    pub codebook_seed: u64,
53    /// Optional reconstruction MSE if measured.
54    pub mse: Option<f64>,
55    /// Optional cosine similarity if measured.
56    pub cosine_similarity: Option<f64>,
57    /// Whether a fallback codec was available.
58    pub fallback_available: bool,
59    /// Unix timestamp seconds, supplied by caller path.
60    pub recorded_unix_seconds: i64,
61    /// Original vector size in bytes (ambient_dim * 4 for f32).
62    pub original_bytes: usize,
63    /// Encoded artifact size in bytes (compact binary form).
64    pub encoded_bytes: usize,
65    /// Achieved compression ratio (original_bytes / encoded_bytes).
66    pub compression_ratio: f64,
67    /// Bits per coordinate in the wire format.
68    pub wire_bits_per_coord_actual: f64,
69    /// Whether Lloyd-Max refinement was applied.
70    pub lloyd_refined: bool,
71}
72
73impl FibQuantCompressionReceiptV1 {
74    #[allow(clippy::too_many_arguments)]
75    pub(crate) fn new(
76        profile: &FibQuantProfileV1,
77        profile_digest: String,
78        codebook_digest: String,
79        rotation_digest: String,
80        source_vector_digest: String,
81        encoded_digest: String,
82        original_bytes: usize,
83        encoded_bytes: usize,
84        lloyd_refined: bool,
85    ) -> Self {
86        let compression_ratio = if encoded_bytes > 0 {
87            original_bytes as f64 / encoded_bytes as f64
88        } else {
89            f64::INFINITY
90        };
91        Self {
92            schema_version: RECEIPT_SCHEMA.into(),
93            profile_digest,
94            codebook_digest,
95            rotation_digest,
96            source_vector_digest,
97            encoded_digest,
98            norm_format: profile.norm_format.clone(),
99            code_schema_version: CODE_SCHEMA.into(),
100            profile_schema_version: PROFILE_SCHEMA.into(),
101            codebook_schema_version: CODEBOOK_SCHEMA.into(),
102            ambient_dim: profile.ambient_dim,
103            block_dim: profile.block_dim,
104            codebook_size: profile.codebook_size,
105            paper_rate_bits_per_coord: profile.paper_rate_bits_per_coord,
106            wire_index_bits: profile.wire_index_bits,
107            wire_bits_per_coord: profile.wire_bits_per_coord,
108            rotation_seed: profile.rotation_seed,
109            rotation_algorithm_version: ROTATION_ALGORITHM_VERSION.into(),
110            codebook_seed: profile.codebook_seed,
111            mse: None,
112            cosine_similarity: None,
113            fallback_available: false,
114            recorded_unix_seconds: current_unix_seconds(),
115            original_bytes,
116            encoded_bytes,
117            compression_ratio,
118            wire_bits_per_coord_actual: profile.wire_bits_per_coord,
119            lloyd_refined,
120        }
121    }
122}
123
124fn current_unix_seconds() -> i64 {
125    std::time::SystemTime::now()
126        .duration_since(std::time::UNIX_EPOCH)
127        .map(|duration| duration.as_secs() as i64)
128        .unwrap_or(0)
129}