provozni zaloha
This commit is contained in:
@@ -12,20 +12,25 @@ same result, so no state needs to be persisted.
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from datetime import date, datetime, time
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
MIN_GAP_MIN = 15 # minimum gap between fire times in minutes; tune here
|
||||
DAYS_PER_WEEK = 7
|
||||
|
||||
|
||||
def compute_fire_times(target_date: date, text: str, cfg: dict) -> list[datetime]:
|
||||
"""Deterministic fire times for one day.
|
||||
|
||||
Returns [] when the day falls outside the days/from/until filters. Raises
|
||||
ValueError on a malformed config (bad window, days, dates, or when the
|
||||
requested count cannot fit the window with MIN_GAP_MIN spacing) — these are
|
||||
structural and validated before any date filter, so the same call validates
|
||||
a config regardless of the date passed in.
|
||||
With period 'day' (default) the count is per day; with 'week' it is per week,
|
||||
spread across distinct days. Returns [] when the day falls outside the
|
||||
days/from/until filters. Raises ValueError on a malformed config (bad window,
|
||||
days, dates, or an infeasible count) — these are structural and validated
|
||||
before any date filter, so the same call validates a config regardless of the
|
||||
date passed in.
|
||||
"""
|
||||
if cfg.get("period", "day") == "week":
|
||||
return _weekly_fire_times(target_date, text, cfg)
|
||||
|
||||
count = _parse_count(cfg.get("times_per_day"))
|
||||
start, end = parse_window(cfg.get("window"))
|
||||
day_set = _parse_days(cfg["days"]) if cfg.get("days") is not None else None
|
||||
@@ -54,6 +59,44 @@ def compute_fire_times(target_date: date, text: str, cfg: dict) -> list[datetime
|
||||
return [datetime.combine(target_date, _minute_to_time(m)) for m in minutes]
|
||||
|
||||
|
||||
def _weekly_fire_times(target_date: date, text: str, cfg: dict) -> list[datetime]:
|
||||
"""Deterministic fire times for target_date within a weekly schedule.
|
||||
|
||||
Picks `count` distinct days (Mon–Sun week) eligible under the days/from/until
|
||||
filters, one random time per chosen day inside the window. Seeded by the
|
||||
week, not the day, so every day of the same week computes the identical plan
|
||||
and this returns only the slice landing on target_date.
|
||||
"""
|
||||
count = _parse_count(cfg.get("times_per_day"))
|
||||
start, end = parse_window(cfg.get("window"))
|
||||
day_set = _parse_days(cfg["days"]) if cfg.get("days") is not None else None
|
||||
from_date = _parse_date(cfg["from"]) if cfg.get("from") is not None else None
|
||||
until_date = _parse_date(cfg["until"]) if cfg.get("until") is not None else None
|
||||
|
||||
week_capacity = len(day_set) if day_set is not None else DAYS_PER_WEEK
|
||||
if count > week_capacity:
|
||||
raise ValueError(
|
||||
f"{count} times per week need {count} eligible days, but only {week_capacity} match the filter"
|
||||
)
|
||||
|
||||
week_start = target_date - timedelta(days=target_date.weekday())
|
||||
eligible = [
|
||||
day
|
||||
for offset in range(DAYS_PER_WEEK)
|
||||
for day in [week_start + timedelta(days=offset)]
|
||||
if (from_date is None or day >= from_date)
|
||||
and (until_date is None or day <= until_date)
|
||||
and (day_set is None or _cron_weekday(day) in day_set)
|
||||
]
|
||||
if not eligible:
|
||||
return []
|
||||
|
||||
rnd = random.Random(f"{week_start.isoformat()}|{text}|week")
|
||||
chosen = sorted(rnd.sample(eligible, min(count, len(eligible))))
|
||||
fires = [datetime.combine(day, _minute_to_time(start + rnd.randint(0, end - start))) for day in chosen]
|
||||
return [fire for fire in fires if fire.date() == target_date]
|
||||
|
||||
|
||||
def _parse_count(raw: object) -> int:
|
||||
if not isinstance(raw, int) or isinstance(raw, bool) or raw < 1:
|
||||
raise ValueError(f"times_per_day must be an int >= 1, got {raw!r}")
|
||||
@@ -91,6 +134,7 @@ def random_cfg_from_row(row) -> dict:
|
||||
cfg = {
|
||||
"times_per_day": row["times_per_day"],
|
||||
"window": f"{minutes_to_hhmm(row['window_start'])}-{minutes_to_hhmm(row['window_end'])}",
|
||||
"period": row["period"],
|
||||
}
|
||||
if row["days_filter"]:
|
||||
cfg["days"] = row["days_filter"]
|
||||
|
||||
Reference in New Issue
Block a user