Facts a rules engine wants, asked of Jev in one request.
A rules engine that learns its facts from Jev does not ask one question per rule. At each step it knows every fact that some rule still alive is waiting on, and all of them go to Jev in one request: N questions about the same state, one round trip. This crate is that step, on Jev's side of it. It turns "these facts are wanted" into one request, and the response back into "these facts are now known".
It knows nothing of the engine. A host says what its facts are with a
[Source]: which question teaches a fact, and what an answer makes it.
Whatever decides which facts are wanted (the rete crate's
Next::Ask(facts), or a host's own three lines) hands them to [wanted],
sends what comes back however it sends things, and gives the response to
[Prepared::judged] and [learn].
Nothing here does I/O, and nothing is async: preparing a request and reading a response are pure, so a page can show the request while Jev is still answering it, and a test needs no network.
20#![forbid(unsafe_code)]
What a host's facts are to Jev.
Two facts may be taught by one question (three readings of one Choice,
each a fact): they give the same [Source::question_id], the question is
asked once, and each reads its own value out of the one answer.
31pub trait Source {
Something that can be known. Small and copied freely, as a rules engine's facts are.
34 type Fact: Copy;
What a fact can turn out to be.
36 type Value;
The id of the question that teaches fact: its name in the request
and in the response. Facts that share a question share this.
40 fn question_id(&self, fact: Self::Fact) -> String;
The question itself. Called once per distinct id in a request.
43 fn question(&self, fact: Self::Fact) -> Result<Question, ProtocolError>;
What Jev's answer to that question makes fact. None when the
answer is not of the type the question was, which [learn] reports
and never guesses past.
Jev's answer to one question, by the type that was asked.
The probability that the answer is yes.
The answer's key, typed by the question it belongs to.
One question in a request.
69pub struct Part {
The question's id in the request, and wherever a host shows it.
71 pub id: String,
A call that is ready to send: one request, one or more questions.
78pub struct Prepared {
The questions, in the order they are asked.
80 pub parts: Vec<Part>,
The request body exactly as it will be sent.
82 pub request: String,
The most this call can cost, every retry billed
(jev_protocol::worst_case_dollars): what a budget holds before it
is sent.
One request that asks every question in asked about state. Each item
is a question's id and the question; an id used twice is the protocol's
error, not a silent overwrite.
94pub fn prepare(model: &ModelId, state: Json, asked: impl IntoIterator<Item = (String, Question)>) -> Result<Prepared, ProtocolError> { 95 let mut questions = Questions::new(); 96 let mut parts = Vec::new(); 97 for (id, question) in asked { 98 let (kind, asked) = match question { 99 Question::Noul(noul) => ("noul", Asked::Noul(questions.noul(&id, noul)?)), 100 Question::Choice(choice) => ("choice", Asked::Choice(questions.choice(&id, choice)?)), 101 Question::Score(score) => ("score", Asked::Score(questions.score(&id, score)?)), 102 }; 103 parts.push(Part { id, kind, asked }); 104 } 105 let request = String::from_utf8(request_bytes(model, &state, &questions)?).map_err(|e| ProtocolError::Invalid(e.to_string()))?; 106 let worst_case_dollars = worst_case_dollars(model, &state, &questions); 107 Ok(Prepared { parts, request, worst_case_dollars, state, questions }) 108}
One request that teaches every fact in facts about state: each
fact's question, a question that several facts share asked once, in the
order the facts first name it.
113pub fn wanted<S: Source>(source: &S, model: &ModelId, state: Json, facts: &[S::Fact]) -> Result<Prepared, ProtocolError> { 114 let mut asked: Vec<(String, Question)> = Vec::new(); 115 for &fact in facts { 116 let id = source.question_id(fact); 117 if asked.iter().all(|(known, _)| *known != id) { 118 asked.push((id, source.question(fact)?)); 119 } 120 } 121 prepare(model, state, asked) 122}
124impl Prepared {
The state and the questions, for a client that takes them and not the
body (jev-http's ask).
The answers in a response to this request, one per question, in the
order of [Prepared::parts].
133 pub fn judged(&self, response: &Response) -> Vec<Judged> { 134 self.parts 135 .iter() 136 .map(|part| match part.asked { 137 Asked::Noul(key) => Judged::Noul(response.get(key).noul), 138 Asked::Choice(key) => Judged::Choice(response.get(key).clone()), 139 Asked::Score(key) => Judged::Score(response.get(key).clone()), 140 }) 141 .collect() 142 } 143}
A fact that a response did not teach.
The request had no question with the fact's id: the facts given to
[learn] are not the ones the request was prepared for.
150 NotAsked { id: String },
The answer to the fact's question is not of the type the question was.
155impl std::fmt::Display for Unlearned { 156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 157 match self { 158 Unlearned::NotAsked { id } => write!(f, "the request did not ask {id}"), 159 Unlearned::WrongType { id } => write!(f, "Jev's answer for {id} is not the type that was asked"), 160 } 161 } 162} 163 164impl std::error::Error for Unlearned {}
What judged (from [Prepared::judged]) makes each of facts: every
fact with its value, in the order given, or the first fact that was not
taught. All or nothing, so a host never records half an answer.
169pub fn learn<S: Source>(source: &S, prepared: &Prepared, judged: &[Judged], facts: &[S::Fact]) -> Result<Vec<(S::Fact, S::Value)>, Unlearned> { 170 facts 171 .iter() 172 .map(|&fact| { 173 let id = source.question_id(fact); 174 let answer = prepared.parts.iter().position(|part| part.id == id).and_then(|index| judged.get(index)); 175 match answer { 176 None => Err(Unlearned::NotAsked { id }), 177 Some(answer) => source.learned(fact, answer).map(|value| (fact, value)).ok_or(Unlearned::WrongType { id }), 178 } 179 }) 180 .collect() 181}