stratifykit_core/quota.rs
1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::BucketKey;
6
7/// A hand-editable quota file: observed (or desired) per-bucket target counts. `quotas`' keys are
8/// caller-defined bucket-key strings, reused verbatim (not trimmed/reparsed) so there is never a
9/// second representation of "what bucket is this" that could drift from the first. `by` makes the
10/// file self-describing: a caller reconstructs its own bucketing dimensions from this field alone,
11/// never from separately-passed flags, so an apply-time mismatch between flags and file is
12/// structurally impossible.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct QuotaSpec {
15 // Why: kept as `stratify_format_version` (not the generic `format_version`) so the on-disk
16 // JSON this crate reads/writes stays byte-for-byte compatible with shogiesa's existing
17 // `stratify --write-template`/`--quota` file format.
18 pub stratify_format_version: u32,
19 pub input: String,
20 pub by: Vec<String>,
21 pub quotas: BTreeMap<BucketKey, usize>,
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn round_trips_through_json() {
30 let mut quotas = BTreeMap::new();
31 quotas.insert("phase:opening:".to_string(), 100);
32 let spec = QuotaSpec {
33 stratify_format_version: 1,
34 input: "in.jsonl".to_string(),
35 by: vec!["phase".to_string()],
36 quotas,
37 };
38 let json = serde_json::to_string(&spec).unwrap();
39 let back: QuotaSpec = serde_json::from_str(&json).unwrap();
40 assert_eq!(back.quotas.get("phase:opening:"), Some(&100));
41 }
42}