1use sim_kernel::{Expr, NumberLiteral, Symbol};
10
11pub fn text_part(text: &str) -> Expr {
13 Expr::Map(vec![
14 (
15 Expr::Symbol(Symbol::new("type")),
16 Expr::Symbol(Symbol::new("text")),
17 ),
18 (
19 Expr::Symbol(Symbol::new("text")),
20 Expr::String(text.to_owned()),
21 ),
22 ])
23}
24
25pub fn number_field(name: &str, value: u64) -> (Expr, Expr) {
28 (
29 Expr::Symbol(Symbol::new(name)),
30 Expr::Number(NumberLiteral {
31 domain: Symbol::qualified("numbers", "f64"),
32 canonical: value.to_string(),
33 }),
34 )
35}
36
37pub fn usage_record(
43 input: Option<u64>,
44 output: Option<u64>,
45 total: Option<u64>,
46) -> Vec<(Expr, Expr)> {
47 let mut fields = Vec::new();
48 if let Some(value) = input {
49 fields.push(number_field("input-tokens", value));
50 }
51 if let Some(value) = output {
52 fields.push(number_field("output-tokens", value));
53 }
54 if let Some(value) = total {
55 fields.push(number_field("total-tokens", value));
56 }
57 fields
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn text_part_shape_is_stable() {
66 assert_eq!(
67 text_part("hi"),
68 Expr::Map(vec![
69 (
70 Expr::Symbol(Symbol::new("type")),
71 Expr::Symbol(Symbol::new("text")),
72 ),
73 (
74 Expr::Symbol(Symbol::new("text")),
75 Expr::String("hi".to_owned()),
76 ),
77 ])
78 );
79 }
80
81 #[test]
82 fn number_field_uses_qualified_f64_domain() {
83 let (key, value) = number_field("input-tokens", 7);
84 assert_eq!(key, Expr::Symbol(Symbol::new("input-tokens")));
85 assert_eq!(
86 value,
87 Expr::Number(NumberLiteral {
88 domain: Symbol::qualified("numbers", "f64"),
89 canonical: "7".to_owned(),
90 })
91 );
92 }
93
94 #[test]
95 fn usage_record_skips_absent_counts_in_order() {
96 let fields = usage_record(Some(3), None, Some(5));
97 assert_eq!(fields.len(), 2);
98 assert_eq!(fields[0].0, Expr::Symbol(Symbol::new("input-tokens")));
99 assert_eq!(fields[1].0, Expr::Symbol(Symbol::new("total-tokens")));
100 assert!(usage_record(None, None, None).is_empty());
101 }
102}