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) } }