Merge: t-paliad-332 — UPC vacations no longer block deadlines (align with paliad t-paliad-121)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled

brunel aligned the embedded snapshot calendar with paliad-side policy from t-paliad-121: UPC vacation rows stay in the data for informational annotation but no longer trigger IsNonWorkingDay → AdjustForNonWorkingDays leaves dates intact.

Changes:
- pkg/litigationplanner/embedded/upc/holidays.go: IsNonWorkingDay now returns true only on closure (not vacation). Vacations still surface via AdjustForNonWorkingDaysWithReason for labelling.
- pkg/litigationplanner/embedded/upc/holidays.json: regenerated from live paliad.holidays. Was 5 placeholders → now 55 holidays (33 vacation + 22 closures). Includes UPC Winter Vacation rows.
- pkg/litigationplanner/embedded/upc/meta.json: snapshot version bump.
- snapshot_test.go: +42 lines covering the vacation non-blocking behavior with regression cases.

Affects youpc.org/deadlines (consumes pkg/litigationplanner via Go module replace) — picks up automatically on rebuild.
This commit is contained in:
mAi
2026-05-27 15:05:06 +02:00
4 changed files with 379 additions and 16 deletions

View File

@@ -30,18 +30,29 @@ func (h SnapshotHoliday) appliesTo(country, regime string) bool {
}
func (h SnapshotHoliday) isVacation() bool { return h.HolidayType == "vacation" }
func (h SnapshotHoliday) isClosure() bool { return h.HolidayType == "closure" }
// isClosure accepts both "public_holiday" and "closure" so the
// embedded calendar matches paliad's HolidayService.IsClosure
// reconciliation (internal/services/holidays.go ~L132). Live DB rows
// use "public_holiday"; "closure" is kept as a legacy synonym so old
// hand-crafted snapshots still parse correctly.
func (h SnapshotHoliday) isClosure() bool {
return h.HolidayType == "public_holiday" || h.HolidayType == "closure"
}
// SnapshotHolidayCalendar serves HolidayCalendar against the embedded
// holiday slice. The semantics mirror paliad's HolidayService:
//
// - IsNonWorkingDay = weekend OR a closure/vacation row matching
// the (country, regime) pair
// - IsNonWorkingDay = weekend OR a closure row matching the
// (country, regime) pair. "Vacation" rows are informational only
// and do not block — see t-paliad-121 / IsNonWorkingDay godoc.
// - AdjustForNonWorkingDays = walk forward day-by-day until
// IsNonWorkingDay returns false (bounded at 60 iters)
// - AdjustForNonWorkingDaysBackward = same but stepping -1 day
// - AdjustForNonWorkingDaysWithReason = forward walk + structured
// reason payload (vacation > public_holiday > weekend)
// reason payload (vacation > public_holiday > weekend) — vacation
// kind fires only when a vacation row overlaps a weekend or
// closure that is doing the rolling.
type SnapshotHolidayCalendar struct {
byDate map[string][]SnapshotHoliday // keyed by YYYY-MM-DD
}
@@ -60,8 +71,18 @@ func NewHolidayCalendar() (*SnapshotHolidayCalendar, error) {
return cal, nil
}
// IsNonWorkingDay returns true on weekends or closure/vacation
// holidays applicable to the given country/regime.
// IsNonWorkingDay returns true on weekends or closure-type holidays
// applicable to the given (country, regime).
//
// "Vacation" entries (today: UPC summer + winter judicial vacations
// per the UPC AC decision on judicial vacations 2023-05-26) are
// deliberately excluded — the Court continues to operate during them
// and they do not extend procedural deadlines (RoP / AC decision-on-
// judicial-vacation). They stay in holidays.json as informational
// metadata so callers can still surface "this date overlaps with UPC
// vacation" if they want. Mirrors HolidayService.IsNonWorkingDay in
// internal/services — see t-paliad-121 for the policy decision and
// t-paliad-332 for the snapshot-side alignment.
func (c *SnapshotHolidayCalendar) IsNonWorkingDay(date time.Time, country, regime string) bool {
if wd := date.Weekday(); wd == time.Saturday || wd == time.Sunday {
return true
@@ -71,7 +92,7 @@ func (c *SnapshotHolidayCalendar) IsNonWorkingDay(date time.Time, country, regim
if !h.appliesTo(country, regime) {
continue
}
if h.isClosure() || h.isVacation() {
if h.isClosure() {
return true
}
}

View File

@@ -3,30 +3,330 @@
"date": "2026-01-01",
"name": "Neujahr",
"country": "DE",
"holiday_type": "closure"
"holiday_type": "public_holiday"
},
{
"date": "2026-04-03",
"name": "Karfreitag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-04-05",
"name": "Ostersonntag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-04-06",
"name": "Ostermontag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-05-01",
"name": "Tag der Arbeit",
"country": "DE",
"holiday_type": "closure"
"holiday_type": "public_holiday"
},
{
"date": "2026-05-14",
"name": "Christi Himmelfahrt",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-05-24",
"name": "Pfingstsonntag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-05-25",
"name": "Pfingstmontag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-07-27",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-07-28",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-07-29",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-07-30",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-07-31",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-03",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-04",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-05",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-06",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-07",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-10",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-11",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-12",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-13",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-14",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-17",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-18",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-19",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-20",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-21",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-24",
"name": "UPC Sommerpause",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-25",
"name": "UPC Sommerpause",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-26",
"name": "UPC Sommerpause",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-27",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-08-28",
"name": "UPC Summer Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-10-03",
"name": "Tag der Deutschen Einheit",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-12-24",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-12-25",
"name": "1. Weihnachtstag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-12-26",
"name": "2. Weihnachtstag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2026-12-28",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-12-29",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-12-30",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2026-12-31",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2027-01-01",
"name": "Neujahr",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-01-04",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2027-01-05",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2027-01-06",
"name": "UPC Winter Vacation",
"regime": "UPC",
"holiday_type": "vacation"
},
{
"date": "2027-03-26",
"name": "Karfreitag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-03-28",
"name": "Ostersonntag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-03-29",
"name": "Ostermontag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-05-01",
"name": "Tag der Arbeit",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-05-06",
"name": "Christi Himmelfahrt",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-05-16",
"name": "Pfingstsonntag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-05-17",
"name": "Pfingstmontag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-10-03",
"name": "Tag der Deutschen Einheit",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-12-25",
"name": "1. Weihnachtstag",
"country": "DE",
"holiday_type": "public_holiday"
},
{
"date": "2027-12-26",
"name": "2. Weihnachtstag",
"country": "DE",
"holiday_type": "public_holiday"
}
]

View File

@@ -1,11 +1,11 @@
{
"version": "2026-05-26-1-placeholder",
"generated_at": "2026-05-26T15:00:00Z",
"version": "2026-05-27-1-holidays-only",
"generated_at": "2026-05-27T12:58:00Z",
"paliad_commit": "",
"source_db_label": "placeholder — operator must run `make snapshot-upc` against prod once mig 134/135 are applied",
"source_db_label": "paliad prod (100.99.98.201:11833) — holidays.json only; rules/proceedings/courts remain placeholder until cmd/gen-upc-snapshot is updated for the post-mig-140 schema (paliad.deadline_rules was dropped)",
"rule_count": 2,
"proceeding_count": 2,
"trigger_event_count": 0,
"holiday_count": 5,
"holiday_count": 55,
"court_count": 2
}

View File

@@ -177,6 +177,48 @@ func TestSnapshotHolidayCalendar(t *testing.T) {
if adj.Weekday() != time.Monday {
t.Errorf("adjusted weekday = %v, want Monday", adj.Weekday())
}
// t-paliad-332: UPC vacations are informational only — a deadline
// landing on a vacation day must NOT be rolled forward. Mirrors
// the paliad-side policy fixed in t-paliad-121 (the Court keeps
// running through judicial vacations, so vacation rows live in
// the snapshot for label payloads but don't extend deadlines).
//
// 2026-08-04 is a Tuesday inside UPC Summer Vacation — must stay
// put on the (DE, UPC) calendar.
sommerpauseDay := time.Date(2026, 8, 4, 0, 0, 0, 0, time.UTC)
if sommerpauseDay.Weekday() == time.Saturday || sommerpauseDay.Weekday() == time.Sunday {
t.Fatalf("test premise broken: 2026-08-04 should not be a weekend (got %v)",
sommerpauseDay.Weekday())
}
if hc.IsNonWorkingDay(sommerpauseDay, "DE", "UPC") {
t.Error("UPC Summer Vacation weekday must not be non-working (t-paliad-332)")
}
adjV, _, wasV := hc.AdjustForNonWorkingDays(sommerpauseDay, "DE", "UPC")
if wasV {
t.Error("expected NO adjustment for vacation-only day (t-paliad-332)")
}
if !adjV.Equal(sommerpauseDay) {
t.Errorf("adjusted = %v, want %v (vacation must not roll, t-paliad-332)",
adjV.Format("2006-01-02"), sommerpauseDay.Format("2006-01-02"))
}
// Sanity-pin: a UPC Winter Vacation date that is ALSO adjacent
// to weekend + Neujahr (the scenario m flagged on youpc.org —
// "rolled from 2027-01-02 (UPC Winter Vacation)"). 2027-01-02 is
// a Saturday; the roll must cross Sat/Sun → Mon 2027-01-04, which
// is in UPC Winter Vacation but no longer blocks → stops there.
// Pre-fix this rolled all the way to Thu 2027-01-07.
jan2 := time.Date(2027, 1, 2, 0, 0, 0, 0, time.UTC)
adjW, _, wasW := hc.AdjustForNonWorkingDays(jan2, "DE", "UPC")
if !wasW {
t.Error("Sat 2027-01-02 must roll forward (weekend)")
}
want := time.Date(2027, 1, 4, 0, 0, 0, 0, time.UTC)
if !adjW.Equal(want) {
t.Errorf("Sat 2027-01-02 adjusted to %v, want %v (vacation no longer rolls, t-paliad-332)",
adjW.Format("2006-01-02"), want.Format("2006-01-02"))
}
}
// TestSnapshotCourtRegistry pins (country, regime) resolution.