Skip to main content

somatize_tree/
findings.rs

1//! What an edit did, read rather than decided.
2//!
3//! The model lives in `somatize.foreseen` and not here — its vocabulary, its
4//! propagation and its 24 tests, one implementation rather than two that
5//! drift. This is a typed reader of what it answers.
6//!
7//! One axis is still this side's, and it is not a second model: `identity` is
8//! a class name, so `Embed(0.5)` and `Embed(0.9)` are one recipe and one AST,
9//! and `foreseen.changes` answers `{}` to that edit. The probe reads what an
10//! object was constructed with and folds it into the fingerprint before
11//! asking, so `STALE` — and the walk that turns it into `SUSPECT` — becomes
12//! true of this axis for free.
13
14use serde::Deserialize;
15use std::collections::BTreeMap;
16
17/// A name that moved because a name above it moved. Not where an edit is.
18pub const DOWNSTREAM: &str = "DOWNSTREAM";
19/// Settled at another state: retrained, or another version of a dataset.
20pub const RESETTLED: &str = "RESETTLED";
21/// The salt moved. A statement about the store, not about the code.
22pub const SALTED: &str = "SALTED";
23/// Its name did not move and its code did: the cache will hit.
24pub const STALE: &str = "STALE";
25/// Something above it is `STALE`, so what reaches it is last week's answer.
26pub const SUSPECT: &str = "SUSPECT";
27
28/// The findings that mean somebody typed something here.
29///
30/// `STALE` is one of them, and it is why this list is not just `CHANGED`: a
31/// rewritten `forward` moves no name, so leaving it out would answer *nobody
32/// edited anything* to the very edit this exists to catch.
33const AN_EDIT: [&str; 4] = ["CHANGED", "ADDED", "GONE", STALE];
34/// The findings a name moved by, that no code moved by.
35const NOT_A_VARIANT: [&str; 2] = [RESETTLED, SALTED];
36
37/// One step: what `foreseen.changes` said, and the one axis it was not given.
38#[derive(Debug, Default, Clone, Deserialize, serde::Serialize)]
39pub struct Findings {
40    /// `{node: [finding, ...]}`. A node with nothing said about it is absent.
41    pub findings: BTreeMap<String, Vec<String>>,
42    /// `{node: [before, after]}` — the readable form of a declaration that
43    /// moved, so a report can print `Embed(0.5) → Embed(0.9)` rather than the
44    /// two digests the fold turned it into.
45    pub declared: BTreeMap<String, [String; 2]>,
46}
47
48impl Findings {
49    /// Whether anything at all was said.
50    pub fn is_quiet(&self) -> bool {
51        self.findings.is_empty()
52    }
53
54    /// The nodes carrying that finding.
55    pub fn saying(&self, finding: &str) -> Vec<&str> {
56        self.findings
57            .iter()
58            .filter(|(_, said)| said.iter().any(|one| one == finding))
59            .map(|(node, _)| node.as_str())
60            .collect()
61    }
62
63    /// Where somebody typed, with what merely inherited it left out.
64    ///
65    /// Without this separation, inserting one node in a graph of forty reports
66    /// forty changes and says nothing about which edit caused them.
67    pub fn the_edit(&self) -> Vec<&str> {
68        self.findings
69            .iter()
70            .filter(|(_, said)| said.iter().any(|one| AN_EDIT.contains(&one.as_str())))
71            .map(|(node, _)| node.as_str())
72            .collect()
73    }
74
75    /// The nodes whose numbers cannot be compared with the ones from before.
76    ///
77    /// A retrained node is not one of them, and neither is what sits under it:
78    /// its results moved without it becoming another variant, which is what a
79    /// trial is.
80    pub fn not_comparable(&self) -> Vec<&str> {
81        if self.the_edit().is_empty() {
82            return Vec::new();
83        }
84        self.findings
85            .iter()
86            .filter(|(_, said)| {
87                said.iter()
88                    .any(|one| !NOT_A_VARIANT.contains(&one.as_str()))
89            })
90            .map(|(node, _)| node.as_str())
91            .collect()
92    }
93}