Beliefs gain a temporal scope (persistent, transient or unknown, classified deterministically from the proposition), an observation time and, for transient states, a validity interval. When a newer belief conflicts with an older transient one observed at least a minute earlier, the older belief is closed (valid_until set, status superseded, a supersedes link, a version and an event saying 'state changed') with its score untouched: 'the door was open at 10:00' stays true of 10:00 when it is closed at 10:05. Simultaneous conflicting reports and persistent propositions contradict as before. Introspection lists superseded states with their validity. Migration 0020 is additive.
56 lines
2.6 KiB
Go
56 lines
2.6 KiB
Go
package beliefs
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Temporal scopes. Persistent propositions are true or false without an
|
|
// interval ("atlas is the primary host", "Andy prefers Go"). Transient ones
|
|
// describe a state that can change ("the door is open", "server A is down")
|
|
// and carry a validity interval. Unknown is the honest default when the
|
|
// classifier cannot tell.
|
|
const (
|
|
ScopePersistent = "persistent"
|
|
ScopeTransient = "transient"
|
|
ScopeUnknown = "unknown"
|
|
)
|
|
|
|
// MinStateChangeGap is the shortest interval between two observations for a
|
|
// later, conflicting observation of a transient state to count as a state
|
|
// change rather than a contradiction. Two reports of the same moment that
|
|
// disagree are a contradiction; reports minutes apart are a change.
|
|
const MinStateChangeGap = time.Minute
|
|
|
|
var (
|
|
reTransient = regexp.MustCompile(`(?i)\b(?:is|are|was|were|remains|seems|looks|appears|has been|have been|is currently|is now)\s+(?:not\s+|still\s+|no longer\s+)?(?:down|up|open|closed|offline|online|running|stopped|stuck|failing|degraded|full|empty|busy|idle|locked|unlocked|available|unavailable|slow|broken|healthy|unhealthy|overloaded|unreachable|reachable|responding|paused|active|inactive|pending|in progress|at capacity|on call|away|present|absent|late|early|hot|cold|warm|crossed)\b|\b(?:right now|currently|at the moment|at \d{1,2}:\d{2}|between \d|since \d|until \d|today|tonight|this morning|this afternoon|for the last|in the last)\b`)
|
|
rePersistent = regexp.MustCompile(`(?i)\b(?:is (?:a|an|the)\b|are (?:a|an|the)\b|was born|prefers?|likes?|dislikes?|always|never|is named|is called|is the name|belongs to|owns|is made of|is located in|is based in|works at|works for|is part of|means|stands for|is written in|uses|runs on|depends on)\b`)
|
|
)
|
|
|
|
// ClassifyScope decides, deterministically, whether a proposition names a
|
|
// transient state, a persistent fact, or neither recognisably.
|
|
func ClassifyScope(proposition string) string {
|
|
p := strings.TrimSpace(proposition)
|
|
switch {
|
|
case reTransient.MatchString(p):
|
|
return ScopeTransient
|
|
case rePersistent.MatchString(p):
|
|
return ScopePersistent
|
|
}
|
|
return ScopeUnknown
|
|
}
|
|
|
|
// StateChange reports whether a newer belief should close an older,
|
|
// conflicting one as a change of state rather than contest it: both must be
|
|
// transient, and the newer observation must be later by at least the gap.
|
|
func StateChange(older, newer Belief) bool {
|
|
if older.TemporalScope != ScopeTransient || newer.TemporalScope != ScopeTransient {
|
|
return false
|
|
}
|
|
if older.ObservedAt == nil || newer.ObservedAt == nil {
|
|
return false
|
|
}
|
|
return newer.ObservedAt.Sub(*older.ObservedAt) >= MinStateChangeGap
|
|
}
|