summaryrefslogtreecommitdiff
path: root/internal/calendar/oracle_test.go
blob: 8ced2820f8533636e773bc853bb7c8a833f4ac34 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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)
	}
}