1use serde_json::{Value, json};
7use typesafe::{Answer, ModelMetadata};
8
9pub fn answer(state: &Value, name: &str, question: &Value) -> Option<Answer> {
11 let state = state.to_string();
12 let kind = question.get("type")?.as_str()?;
13 let instructions = question
14 .get("instructions")
15 .map(ToString::to_string)
16 .unwrap_or_default();
17 let seed = format!("{state}\u{1}{name}\u{1}{instructions}");
18 match kind {
19 "noul" => {
20 let p = round(0.04 + 0.92 * unit(&[&seed, "noul"]));
21 from_value(json!({ "noul": p }))
22 }
23 "choice" => {
24 let labels: Vec<String> = question
25 .get("criteria")?
26 .as_object()?
27 .keys()
28 .cloned()
29 .collect();
30 let probs = distribution(&seed, &state, &labels);
31 let (choice, _) = probs
32 .iter()
33 .cloned()
34 .max_by(|a, b| a.1.total_cmp(&b.1))
35 .unwrap_or_default();
36 let map: serde_json::Map<String, Value> =
37 probs.iter().map(|(l, p)| (l.clone(), json!(p))).collect();
38 let conf = confidence(probs.iter().map(|(_, p)| *p));
39 from_value(json!({"choice": choice, "probabilities": map, "confidence": conf}))
40 }
41 "score" => {
42 let levels = question.get("criteria")?.as_array()?;
43 let keys: Vec<String> = (0..levels.len()).map(|i| i.to_string()).collect();
44 let probs = distribution(&seed, &state, &keys);
45 let score = round(
46 probs
47 .iter()
48 .enumerate()
49 .map(|(i, (_, p))| i as f64 * p)
50 .sum(),
51 );
52 let legend: serde_json::Map<String, Value> = levels
53 .iter()
54 .enumerate()
55 .map(|(i, v)| (i.to_string(), v.clone()))
56 .collect();
57 let map: serde_json::Map<String, Value> =
58 probs.iter().map(|(k, p)| (k.clone(), json!(p))).collect();
59 let conf = confidence(probs.iter().map(|(_, p)| *p));
60 from_value(
61 json!({"score": score, "confidence": conf, "legend": legend, "probabilities": map}),
62 )
63 }
64 _ => None,
65 }
66}
67
68pub fn answer_json(answer: &Answer) -> Value {
71 match answer {
72 Answer::Noul(a) => json!({"type": "noul", "noul": number(a.noul)}),
73 Answer::Choice(a) => json!({
74 "type": "choice", "choice": a.choice,
75 "probabilities": a.probabilities.iter()
76 .map(|(k, v)| (k.clone(), number(*v)))
77 .collect::<serde_json::Map<String, Value>>(),
78 "confidence": number(a.confidence),
79 }),
80 Answer::Score(a) => json!({
81 "type": "score", "score": number(a.score), "confidence": number(a.confidence),
82 "legend": a.legend.iter()
83 .map(|(k, v)| (k.to_string(), v.clone()))
84 .collect::<serde_json::Map<String, Value>>(),
85 "probabilities": a.probabilities.iter()
86 .map(|(k, v)| (k.to_string(), number(*v)))
87 .collect::<serde_json::Map<String, Value>>(),
88 }),
89 _ => Value::Null,
91 }
92}
93
94fn number(x: f64) -> Value {
98 if x.fract() == 0.0 && x.abs() < 9e15 {
99 return json!(x as i64);
100 }
101 json!(x)
102}
103
104pub fn models() -> Vec<ModelMetadata> {
106 [
107 (
108 "jev-latest",
109 "Alias for the newest jev release",
110 "2026-05-01",
111 ),
112 ("jev-2", "Previous generation", "2025-11-12"),
113 ]
114 .into_iter()
115 .filter_map(|(name, description, release_date)| {
116 serde_json::from_value(json!({
117 "name": name, "description": description, "release_date": release_date
118 }))
119 .ok()
120 })
121 .collect()
122}
123
124fn from_value(v: Value) -> Option<Answer> {
125 if v.get("noul").is_some() {
128 return serde_json::from_value(v).ok().map(Answer::Noul);
129 }
130 if v.get("choice").is_some() {
131 return serde_json::from_value(v).ok().map(Answer::Choice);
132 }
133 serde_json::from_value(v).ok().map(Answer::Score)
134}
135
136fn distribution(seed: &str, state: &str, labels: &[String]) -> Vec<(String, f64)> {
138 let lower = state.to_lowercase();
139 let mut raw: Vec<(String, f64)> = labels
140 .iter()
141 .map(|label| {
142 let u = unit(&[seed, label]);
143 let mut w = 0.02 + u * u * u;
144 if label.len() > 3 && lower.contains(&label.to_lowercase()) {
145 w *= 4.0;
146 }
147 (label.clone(), w)
148 })
149 .collect();
150 let total: f64 = raw.iter().map(|(_, w)| w).sum();
151 if total <= 0.0 {
152 let even = round(1.0 / raw.len().max(1) as f64);
153 return raw.into_iter().map(|(l, _)| (l, even)).collect();
154 }
155 for (_, w) in &mut raw {
156 *w = round(*w / total);
157 }
158 let drift = 1.0 - raw.iter().map(|(_, w)| w).sum::<f64>();
160 if let Some(top) = raw
161 .iter_mut()
162 .max_by(|a, b| a.1.total_cmp(&b.1))
163 .filter(|_| drift.abs() > f64::EPSILON)
164 {
165 top.1 = round(top.1 + drift);
166 }
167 raw
168}
169
170fn confidence(probs: impl Iterator<Item = f64>) -> f64 {
172 let probs: Vec<f64> = probs.collect();
173 let n = probs.len();
174 if n < 2 {
175 return 1.0;
176 }
177 let max = probs.iter().copied().fold(0.0, f64::max);
178 let floor = 1.0 / n as f64;
179 round(((max - floor) / (1.0 - floor)).clamp(0.0, 1.0))
180}
181
182fn unit(parts: &[&str]) -> f64 {
183 (fnv(parts) % 100_000) as f64 / 100_000.0
184}
185
186fn fnv(parts: &[&str]) -> u64 {
187 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
188 for part in parts {
189 for b in part.as_bytes() {
190 h ^= *b as u64;
191 h = h.wrapping_mul(0x0000_0100_0000_01b3);
192 }
193 h ^= 0xff;
194 h = h.wrapping_mul(0x0000_0100_0000_01b3);
195 }
196 h
197}
198
199fn round(x: f64) -> f64 {
200 (x * 1000.0).round() / 1000.0
201}
202
203pub fn body(answers: &[(String, Option<Answer>)], model: &str) -> String {
205 let mut map = serde_json::Map::new();
206 for (name, answer) in answers {
207 let value = match answer {
208 Some(answer) => answer_json(answer),
209 None => Value::Null,
210 };
211 map.insert(name.clone(), value);
212 }
213 serde_json::to_string_pretty(&serde_json::json!({
214 "model": model,
215 "answers": map,
216 "usage": {"input_tokens": null, "output_tokens": null},
217 }))
218 .unwrap_or_default()
219}