Small builders for the documents the laws merge.

3use std::collections::BTreeSet;
5use whiskers_core::{ChatState, Fact, Household, Kind, MemorySnapshot};
6
7pub fn fact(gid: &str, text: &str) -> Fact {
8    Fact {
9        id: 0,
10        text: text.to_owned(),
11        learned_at_ms: 5,
12        kind: Kind::Other,
13        who: vec![],
14        place: None,
15        when: None,
16        pictures: vec![],
17        embedding: vec![],
18        gid: gid.to_owned(),
19        visibility: whiskers_core::Visibility::default(),
20        icon: None,
21    }
22}
23
24pub fn with_embedding(mut f: Fact, embedding: &[f32]) -> Fact {
25    f.embedding = embedding.to_vec();
26    f
27}
28
29pub fn snapshot(facts: Vec<Fact>, forgotten: &[&str]) -> MemorySnapshot {
30    MemorySnapshot { facts, forgotten: forgotten.iter().map(|g| (*g).to_owned()).collect() }
31}

A fact by identity: its gid, its words and its embedding (as bits, so it compares exactly).

34pub type FactGist = (String, String, Vec<u32>);

What a snapshot says once the hub's own numbering and ordering are set aside: the facts by identity (with their words and whether they carry an embedding) and the forgotten identities.

38pub fn gist(s: &MemorySnapshot) -> (BTreeSet<FactGist>, BTreeSet<String>) {
39    (
40        s.facts.iter().map(|f| (f.gid.clone(), f.text.clone(), f.embedding.iter().map(|x| x.to_bits()).collect())).collect(),
41        s.forgotten.iter().cloned().collect(),
42    )
43}

A household document, built through its JSON (its fields are private to the core, as they should be): the choices made at config_ms with a thinking allowance and a voice allowance, and the time each named device has spent on day.

48pub fn household(config_ms: u64, tokens: u64, voice_chars: u32, day: u32, used_ms: &[(&str, u64)]) -> Household {
49    let used: serde_json::Map<String, serde_json::Value> = used_ms.iter().map(|(d, ms)| ((*d).to_owned(), serde_json::json!(ms))).collect();
50    serde_json::from_value(serde_json::json!({
51        "config": {
52            "limits": { "daily_minutes": null, "quiet": null },
53            "tokens": { "per_window": tokens, "window_hours": 5 },
54            "keep_mic_open": false,
55            "voice_daily_chars": voice_chars,
56            "pin": null,
57            "child": null
58        },
59        "config_updated_ms": config_ms,
60        "today": { "day": day, "used_ms": used, "extra_minutes": {} }
61    }))
62    .expect("a household document")
63}
65pub fn chat(version: u64, last_active_ms: u64, summary: &str) -> ChatState {
66    ChatState { summary: summary.to_owned(), turns: vec![], last_active_ms, version }
67}

Every order of items.

70pub fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
71    if items.len() <= 1 {
72        return vec![items.to_vec()];
73    }
74    let mut out = Vec::new();
75    for i in 0..items.len() {
76        let mut rest = items.to_vec();
77        let head = rest.remove(i);
78        for mut tail in permutations(&rest) {
79            tail.insert(0, head.clone());
80            out.push(tail);
81        }
82    }
83    out
84}