aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/oracle_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 12:33:47 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 12:33:47 +0200
commitf5afb407beab795e371c36a3229e991e48c366fe (patch)
treeb9378cca00e46a1b063d53f3cfa52546ae782f9f /internal/calendar/oracle_test.go
parent56f3b8161f73c33fe6e25f6461d3f061af427e9e (diff)
downloadlectio-f5afb407beab795e371c36a3229e991e48c366fe.tar.gz
lectio-f5afb407beab795e371c36a3229e991e48c366fe.zip
test(calendar): regression oracle vs calapi 2020-2040 (0 season mismatches)
Caught + fixed an Advent-start bug: anchor on Christmas Eve, not Dec 25, so years where Dec 25 is a Sunday don't lose the first week of Advent. Rank-class diffs (324) are informational — the initial sanctoral dataset is partial.
Diffstat (limited to 'internal/calendar/oracle_test.go')
-rw-r--r--internal/calendar/oracle_test.go92
1 files changed, 92 insertions, 0 deletions
diff --git a/internal/calendar/oracle_test.go b/internal/calendar/oracle_test.go
new file mode 100644
index 0000000..8ced282
--- /dev/null
+++ b/internal/calendar/oracle_test.go
@@ -0,0 +1,92 @@
+package calendar_test
+
+import (
+ "encoding/json"
+ "math"
+ "os"
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+type oracleDay struct {
+ Season string `json:"season"`
+ RankNum float64 `json:"rank_num"`
+}
+
+// classOf maps a Table band (1-13) to its calapi class (1, 2, or 3).
+func classOf(band int) int {
+ switch {
+ case band <= 4:
+ return 1
+ case band <= 9:
+ return 2
+ default:
+ return 3
+ }
+}
+
+// mySeason maps the engine's season onto calapi's five-season vocabulary
+// (calapi buckets the Triduum's Thu/Fri/Sat under "lent").
+func mySeason(s calendar.Season) string {
+ if s == calendar.Triduum {
+ return "lent"
+ }
+ return string(s)
+}
+
+// TestOracle2020to2040 diffs the engine against calapi (calendarium-romanum).
+// Season is asserted strictly (data-independent — it validates the whole
+// temporal engine). Rank class is reported for information only, since a class
+// mismatch usually means the initial embedded sanctoral simply lacks that saint.
+func TestOracle2020to2040(t *testing.T) {
+ raw, err := os.ReadFile("testdata/oracle-2020-2040.json")
+ if err != nil {
+ t.Skip("oracle snapshot missing; run scripts/build-oracle.sh")
+ }
+ var oracle map[string]oracleDay
+ if err := json.Unmarshal(raw, &oracle); err != nil {
+ t.Fatal(err)
+ }
+ sel := calendar.DefaultSelection()
+ layers := []calendar.Layer{caldata.Universal()}
+
+ dates := make([]string, 0, len(oracle))
+ for date := range oracle {
+ dates = append(dates, date)
+ }
+ sort.Strings(dates)
+
+ var seasonMiss, classMiss, total int
+ shownS, shownC := 0, 0
+ for _, date := range dates {
+ want := oracle[date]
+ day, _ := time.Parse("2006-01-02", date)
+ got := calendar.Compute(day.UTC(), sel, layers)
+ total++
+
+ if mySeason(got.Season) != want.Season {
+ seasonMiss++
+ if shownS < 20 {
+ t.Errorf("%s: season got %q want %q", date, mySeason(got.Season), want.Season)
+ shownS++
+ }
+ }
+ wantClass := int(math.Floor(want.RankNum))
+ if classOf(got.ObservedBand) != wantClass && shownC < 15 {
+ classMiss++
+ t.Logf("[info] %s: rank class got %d (band %d) want %d — likely missing sanctoral data",
+ date, classOf(got.ObservedBand), got.ObservedBand, wantClass)
+ shownC++
+ } else if classOf(got.ObservedBand) != wantClass {
+ classMiss++
+ }
+ }
+ t.Logf("oracle: %d days, %d season mismatches, %d rank-class mismatches (info)", total, seasonMiss, classMiss)
+ if seasonMiss > 0 {
+ t.Fatalf("%d/%d season mismatches vs calapi — temporal engine is wrong", seasonMiss, total)
+ }
+}