• בלוג
  • שבתות וחגים - מה קורה כשהמודל מנסה יותר מדי

שבתות וחגים - מה קורה כשהמודל מנסה יותר מדי

11/10/2026

נתתי ל Sol את הפרומפט הבא כדי לבנות תוכנית פייתון שמדלגת על שבתות וחגים, למשל בשביל לא לשלוח מייל או לחייב לקוחות בשבת:

create a new python project with uv
  the app is a cron scheduler we should start it via a CLI
  cron.py cronfile

  cronfile is in the same format as any regular crontab EXCEPT it has a twist
  each line also has a boolean indicating if the request should execute in Jewish holidays and Saturdays
  (called "skip_shabatot_vehagim")

  If skip_shabatot_vehagim is set that line shouldn't execute on Jewish holidays and Saturdays with religious date
  boundaries

התוצאה היתה מרשימה. זה קובץ ה calendar.py שהוא יצר:

"""Protected intervals, starting the evening before each sacred civil date."""

import math
from dataclasses import dataclass
from datetime import UTC, date, datetime, timedelta
from functools import lru_cache
from zoneinfo import ZoneInfo

from astral import Observer, SunDirection
from astral.sun import sunset, time_at_elevation
from pyluach.dates import HebrewDate


class BoundaryError(ValueError):
    """The location has no calculable sunset or nightfall for a required date."""


@dataclass(frozen=True)
class ReligiousCalendar:
    timezone: ZoneInfo = ZoneInfo("Asia/Jerusalem")
    latitude: float = 31.778
    longitude: float = 35.235
    israel: bool = True
    start_offset_minutes: float = 0
    nightfall_degrees: float = 8.5
    nightfall_minutes: float | None = None

    def __post_init__(self) -> None:
        for name, value, lower, upper in (
            ("latitude", self.latitude, -90, 90),
            ("longitude", self.longitude, -180, 180),
            ("start offset", self.start_offset_minutes, 0, 180),
            ("nightfall degrees", self.nightfall_degrees, 1, 30),
        ):
            if not math.isfinite(value) or not lower <= value <= upper:
                raise ValueError(f"{name} must be between {lower} and {upper}")
        if self.nightfall_minutes is not None and (
            not math.isfinite(self.nightfall_minutes)
            or not 0 < self.nightfall_minutes <= 240
        ):
            raise ValueError("nightfall minutes must be greater than 0 and at most 240")

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def protected_date(self, day: date) -> bool:
        if day.weekday() == 5:
            return True
        hebrew = HebrewDate.from_pydate(day)
        # Pyluach numbers Nissan as 1 and Tishrei as 7. Only holidays on
        # which work is prohibited are included, rather than minor holidays.
        sacred = {
            (7, 1),
            (7, 2),  # Rosh Hashanah (two days also in Israel)
            (7, 10),  # Yom Kippur
            (7, 15),
            (7, 22),  # Sukkot and Shemini Atzeret / Simchat Torah
            (1, 15),
            (1, 21),  # First and last days of Pesach
            (3, 6),  # Shavuot
        }
        if not self.israel:
            sacred |= {(7, 16), (7, 23), (1, 16), (1, 22), (3, 7)}
        return (hebrew.month, hebrew.day) in sacred

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def interval(self, day: date) -> tuple[datetime, datetime]:
        observer = Observer(self.latitude, self.longitude)
        try:
            start = sunset(observer, day - timedelta(days=1), self.timezone)
            start = start.astimezone(UTC) - timedelta(minutes=self.start_offset_minutes)
            if self.nightfall_minutes is not None:
                end = sunset(observer, day, self.timezone).astimezone(UTC)
                end += timedelta(minutes=self.nightfall_minutes)
            else:
                end = time_at_elevation(
                    observer,
                    -self.nightfall_degrees,
                    day,
                    SunDirection.SETTING,
                    self.timezone,
                    with_refraction=False,
                ).astimezone(UTC)
        except ValueError as error:
            raise BoundaryError(
                f"cannot calculate religious boundaries for {day}"
            ) from error
        if end <= start:
            raise BoundaryError(f"invalid religious boundaries for {day}")
        return start, end

    def is_protected(self, when: datetime) -> bool:
        if when.tzinfo is None or when.utcoffset() is None:
            raise ValueError(
                "religious boundary checks require a timezone-aware datetime"
            )
        local_day = when.astimezone(self.timezone).date()
        instant = when.astimezone(UTC)
        # Yesterday covers a nightfall after midnight at high latitudes.
        # Tomorrow covers Friday and holiday-eve evenings. Intersecting
        # intervals naturally keep consecutive holy days protected.
        for delta in (-1, 0, 1):
            day = local_day + timedelta(days=delta)
            if self.protected_date(day):
                start, end = self.interval(day)
                if start <= instant < end:
                    return True
        return False

הקובץ כולל את רשימת החגים מובנית, המרה לתאריך עברי ואפילו בדיקת שעות כדי לא לבצע משימות בערב חג אבל כן לאפשר משימות אחרי צאת שבת.

אם הייתי נותן ל AI להריץ איטרציית שיפור על הקוד הוא כנראה היה מבין שאין צורך להגדיר את רשימת החגים בתוך הפונקציה protected_date ואפשר להוציא אותה למשתנה של המודול כדי לשפר ביצועים ולקצר קצת את הקוד.

ובכל זאת הייתי נזהר מלהשתמש בקוד כזה במערכת שלי. הקוד גבולי מדי, הוא חושב שהוא יודע הכל ועושה מאמץ למצוא בדיוק את שעת השקיעה כדי לזהות מתי נכנסת שבת. המאמץ לעשות את הדבר הנכון בא על חשבון מרווחי ביטחון בריאים.

תיקון טוב במקרה הזה היה הפרומפט:

replace sunset calculation with a healthy safe buffer

זה הקובץ אחרי התיקון:

"""Conservative local-clock buffers around Shabbat and Jewish holidays."""

from dataclasses import dataclass
from datetime import UTC, date, datetime, time, timedelta
from functools import lru_cache
from zoneinfo import ZoneInfo

from pyluach.dates import HebrewDate


@dataclass(frozen=True)
class ReligiousCalendar:
    timezone: ZoneInfo = ZoneInfo("Asia/Jerusalem")
    israel: bool = True
    protection_start: time = time(15, 0)
    protection_end: time = time(23, 0)

    def __post_init__(self) -> None:
        for boundary in (self.protection_start, self.protection_end):
            if boundary.tzinfo is not None or boundary.second or boundary.microsecond:
                raise ValueError("protection times must be local HH:MM times")
        if self.protection_end < self.protection_start:
            raise ValueError("protection end must not be earlier than protection start")

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def protected_date(self, day: date) -> bool:
        if day.weekday() == 5:
            return True
        hebrew = HebrewDate.from_pydate(day)
        # Pyluach numbers Nissan as 1 and Tishrei as 7. Only holidays on
        # which work is prohibited are included, rather than minor holidays.
        sacred = {
            (7, 1),
            (7, 2),  # Rosh Hashanah (two days also in Israel)
            (7, 10),  # Yom Kippur
            (7, 15),
            (7, 22),  # Sukkot and Shemini Atzeret / Simchat Torah
            (1, 15),
            (1, 21),  # First and last days of Pesach
            (3, 6),  # Shavuot
        }
        if not self.israel:
            sacred |= {(7, 16), (7, 23), (1, 16), (1, 22), (3, 7)}
        return (hebrew.month, hebrew.day) in sacred

    @lru_cache(maxsize=1024)  # noqa: B019 - bounded cache of immutable configurations
    def interval(self, day: date) -> tuple[datetime, datetime]:
        start = datetime.combine(
            day - timedelta(days=1), self.protection_start, self.timezone
        )
        end = datetime.combine(day, self.protection_end, self.timezone)
        return start.astimezone(UTC), end.astimezone(UTC)

    def is_protected(self, when: datetime) -> bool:
        if when.tzinfo is None or when.utcoffset() is None:
            raise ValueError(
                "religious boundary checks require a timezone-aware datetime"
            )
        local_day = when.astimezone(self.timezone).date()
        instant = when.astimezone(UTC)
        # Tomorrow covers the buffered eve. Overlapping intervals keep
        # consecutive holy days continuously protected.
        for delta in (0, 1):
            day = local_day + timedelta(days=delta)
            if self.protected_date(day):
                start, end = self.interval(day)
                if start <= instant < end:
                    return True
        return False

גם קוד פשוט יותר וגם עונה טוב יותר למטרה האמיתית של הפרומפט הראשון.

אז מה זה אומר שמפתחים עוברים להיות אנשי Product ומאפיינים? סוג של. מה שחשוב היום בעבודה של מפתחים הוא ראייה מערכתית. להבין איך המערכת צריכה להיראות, איזה מנגנונים בקוד אנחנו רוצים לבנות ואיפה אנחנו רוצים דווקא לוותר על המנגנון המתוחכם בשביל משהו פשוט שיהיה יותר יציב. להבין איך הקוד מתחבר לדרישות ואיך לבחור את המימוש שיענה טוב יותר גם על הדרישות שלא נמצאות בפרומפט.