validation.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """节日结果过滤:仅校验查询日期是否在 LLM 返回的预热/节日期区间内。"""
  2. from __future__ import annotations
  3. from datetime import date, timedelta
  4. from typing import Any
  5. def _parse_date(value: Any) -> date | None:
  6. text = str(value or "").strip()
  7. if not text:
  8. return None
  9. try:
  10. return date.fromisoformat(text)
  11. except ValueError:
  12. return None
  13. def is_festival_active_on_date(
  14. query_date: date,
  15. festival_start: date,
  16. festival_end: date,
  17. prewarm_days: int,
  18. ) -> tuple[str, int] | None:
  19. """判断查询日是否命中;命中返回 (phase, days_to_festival),否则 None。"""
  20. if festival_start <= query_date <= festival_end:
  21. return ("festival", 0)
  22. if prewarm_days > 0:
  23. prewarm_start = festival_start - timedelta(days=prewarm_days)
  24. if prewarm_start <= query_date < festival_start:
  25. return ("prewarm", (festival_start - query_date).days)
  26. return None
  27. def filter_festivals_by_query_date(
  28. festivals: list[dict[str, Any]],
  29. query_date: date,
  30. prewarm_by_name: dict[str, int],
  31. ) -> list[dict[str, Any]]:
  32. """保留符合「目标日期 + 提前量」的节日,移除其余项。"""
  33. kept: list[dict[str, Any]] = []
  34. for item in festivals:
  35. name = str(item.get("name") or "").strip()
  36. if not name or name not in prewarm_by_name:
  37. continue
  38. festival_start = _parse_date(item.get("festival_start"))
  39. festival_end = _parse_date(item.get("festival_end"))
  40. if not festival_start or not festival_end:
  41. continue
  42. if festival_end < festival_start:
  43. festival_start, festival_end = festival_end, festival_start
  44. prewarm_days = prewarm_by_name[name]
  45. active = is_festival_active_on_date(
  46. query_date,
  47. festival_start,
  48. festival_end,
  49. prewarm_days,
  50. )
  51. if not active:
  52. continue
  53. phase, days_to_festival = active
  54. kept.append(
  55. {
  56. **item,
  57. "prewarm_days": prewarm_days,
  58. "festival_start": festival_start.isoformat(),
  59. "festival_end": festival_end.isoformat(),
  60. "phase": phase,
  61. "days_to_festival": days_to_festival,
  62. }
  63. )
  64. return kept