Merge: t-paliad-332 — UPC vacations no longer block deadlines (align with paliad t-paliad-121)
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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user