feat: D24 fact intervals (--as-of) and bin/stack assistant helpers. (#37)
Tests / Test (push) Skipped
Tests / OCR (tesseract fixture) (push) Skipped
Tests / Release (semver) (push) Skipped

Store valid_from/valid_to on leafs and filter search by calendar day without
overloading D16 source staleness; stack start/start-assistant wires brain + PicoClaw.
This commit is contained in:
2026-08-14 15:48:26 +01:00
committed by GitHub
co-authored by GitHub
parent 0c4bb87001
commit fc2723c39f
34 changed files with 991 additions and 47 deletions
+44
View File
@@ -0,0 +1,44 @@
package facts
// Interval of truth for a fact leaf (D24 / OQ5). Not D16 source staleness.
//
// Empty valid_from and valid_to means "always" (legacy leafs). Empty asOf
// means "do not filter". Dates compare as YYYY-MM-DD (lexicographic).
// NormalizeDay keeps the calendar day from ISO-8601 or bare dates.
func NormalizeDay(s string) string {
s = trimSpace(s)
if len(s) >= 10 && s[4] == '-' && s[7] == '-' {
return s[:10]
}
return s
}
func trimSpace(s string) string {
i, j := 0, len(s)
for i < j && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') {
i++
}
for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n' || s[j-1] == '\r') {
j--
}
return s[i:j]
}
// ActiveAt reports whether a fact with [validFrom, validTo] holds at asOf.
// validTo empty = open-ended. Both ends inclusive.
func ActiveAt(validFrom, validTo, asOf string) bool {
asOf = NormalizeDay(asOf)
if asOf == "" {
return true
}
from := NormalizeDay(validFrom)
to := NormalizeDay(validTo)
if from != "" && asOf < from {
return false
}
if to != "" && asOf > to {
return false
}
return true
}
+73
View File
@@ -0,0 +1,73 @@
package facts
import "testing"
func TestActiveAtOpenEnded(t *testing.T) {
// works at Y from 2025-07-16, no end
if !ActiveAt("2025-07-16", "", "2025-07-16") {
t.Fatal("inclusive valid_from")
}
if !ActiveAt("2025-07-16", "", "2026-01-01") {
t.Fatal("open-ended valid_to")
}
if ActiveAt("2025-07-16", "", "2025-07-15") {
t.Fatal("before valid_from must be inactive")
}
}
func TestActiveAtClosedInterval(t *testing.T) {
// works at X 2024-03-01 .. 2025-07-15
if !ActiveAt("2024-03-01", "2025-07-15", "2025-01-01") {
t.Fatal("mid interval")
}
if !ActiveAt("2024-03-01", "2025-07-15", "2024-03-01") {
t.Fatal("inclusive start")
}
if !ActiveAt("2024-03-01", "2025-07-15", "2025-07-15") {
t.Fatal("inclusive end")
}
if ActiveAt("2024-03-01", "2025-07-15", "2025-07-16") {
t.Fatal("day after end")
}
if ActiveAt("2024-03-01", "2025-07-15", "2024-02-28") {
t.Fatal("day before start")
}
}
func TestActiveAtEmptyIntervalAlwaysTrue(t *testing.T) {
// legacy leafs without intervals stay visible for any as-of
if !ActiveAt("", "", "2025-01-01") {
t.Fatal("empty interval must remain active")
}
if !ActiveAt("", "", "") {
t.Fatal("no as-of means all active")
}
}
func TestActiveAtEmptyAsOfKeepsAll(t *testing.T) {
if !ActiveAt("2099-01-01", "2099-12-31", "") {
t.Fatal("empty as-of must not filter")
}
}
func TestAsOfPickXNotY(t *testing.T) {
// Acceptance from #36: as of 2025-01-01 → X, not Y
xFrom, xTo := "2024-03-01", "2025-07-15"
yFrom, yTo := "2025-07-16", ""
asOf := "2025-01-01"
if !ActiveAt(xFrom, xTo, asOf) {
t.Fatal("X must be active as of 2025-01-01")
}
if ActiveAt(yFrom, yTo, asOf) {
t.Fatal("Y must be inactive as of 2025-01-01")
}
}
func TestNormalizeDayTrimsTime(t *testing.T) {
if NormalizeDay("2025-01-01T12:00:00Z") != "2025-01-01" {
t.Fatalf("got %q", NormalizeDay("2025-01-01T12:00:00Z"))
}
if NormalizeDay("2025-01-01") != "2025-01-01" {
t.Fatalf("got %q", NormalizeDay("2025-01-01"))
}
}