email_notifications #7

Merged
jonas merged 18 commits from email_notifications into main 2025-10-16 13:20:27 +00:00
2 changed files with 120 additions and 2 deletions
Showing only changes of commit a8c441ea6f - Show all commits

View File

@@ -32,6 +32,7 @@ from .customer_service import CustomerService
from .db import Base, get_database_url from .db import Base, get_database_url
from .db import Customer as DBCustomer from .db import Customer as DBCustomer
from .db import Reservation as DBReservation from .db import Reservation as DBReservation
from .email_monitoring import ReservationStatsCollector
from .email_service import create_email_service from .email_service import create_email_service
from .logging_config import get_logger, setup_logging from .logging_config import get_logger, setup_logging
from .rate_limit import ( from .rate_limit import (
@@ -245,8 +246,17 @@ async def lifespan(app: FastAPI):
else: else:
_LOGGER.info("All existing customers already have hashed data") _LOGGER.info("All existing customers already have hashed data")
# Start daily report scheduler if enabled # Initialize and hook up stats collector for daily reports
if report_scheduler: if report_scheduler:
stats_collector = ReservationStatsCollector(
async_sessionmaker=AsyncSessionLocal,
config=config,
)
# Hook up the stats collector to the report scheduler
report_scheduler.set_stats_collector(stats_collector.collect_stats)
_LOGGER.info("Stats collector initialized and hooked up to report scheduler")
# Start daily report scheduler
report_scheduler.start() report_scheduler.start()
_LOGGER.info("Daily report scheduler started") _LOGGER.info("Daily report scheduler started")

View File

@@ -7,10 +7,14 @@ email alerts based on configurable thresholds and time windows.
import asyncio import asyncio
import logging import logging
import threading import threading
from collections import deque from collections import defaultdict, deque
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import async_sessionmaker
from .db import Reservation
from .email_service import EmailService from .email_service import EmailService
from .logging_config import get_logger from .logging_config import get_logger
@@ -449,3 +453,107 @@ class DailyReportScheduler:
""" """
self._stats_collector = collector self._stats_collector = collector
class ReservationStatsCollector:
"""Collects reservation statistics per hotel for daily reports.
This collector queries the database for reservations created since the last
report and aggregates them by hotel. It includes hotel_code and hotel_name
from the configuration.
"""
def __init__(
self,
async_sessionmaker: async_sessionmaker,
config: dict[str, Any],
):
"""Initialize the stats collector.
Args:
async_sessionmaker: SQLAlchemy async session maker
config: Application configuration containing hotel information
"""
self.async_sessionmaker = async_sessionmaker
self.config = config
self._last_report_time = datetime.now()
# Build hotel mapping from config
self._hotel_map = {}
for hotel in config.get("alpine_bits_auth", []):
hotel_id = hotel.get("hotel_id")
hotel_name = hotel.get("hotel_name")
if hotel_id:
self._hotel_map[hotel_id] = hotel_name or "Unknown Hotel"
_LOGGER.info(
"ReservationStatsCollector initialized with %d hotels",
len(self._hotel_map),
)
async def collect_stats(self) -> dict[str, Any]:
"""Collect reservation statistics for the reporting period.
Returns:
Dictionary with statistics including reservations per hotel
"""
now = datetime.now()
period_start = self._last_report_time
period_end = now
_LOGGER.info(
"Collecting reservation stats from %s to %s",
period_start.strftime("%Y-%m-%d %H:%M:%S"),
period_end.strftime("%Y-%m-%d %H:%M:%S"),
)
async with self.async_sessionmaker() as session:
# Query reservations created in the reporting period
result = await session.execute(
select(Reservation.hotel_code, func.count(Reservation.id))
.where(Reservation.created_at >= period_start)
.where(Reservation.created_at < period_end)
.group_by(Reservation.hotel_code)
)
hotel_counts = dict(result.all())
# Build stats with hotel names from config
hotels_stats = []
total_reservations = 0
for hotel_code, count in hotel_counts.items():
hotel_name = self._hotel_map.get(hotel_code, "Unknown Hotel")
hotels_stats.append(
{
"hotel_code": hotel_code,
"hotel_name": hotel_name,
"reservations": count,
}
)
total_reservations += count
# Sort by reservation count descending
hotels_stats.sort(key=lambda x: x["reservations"], reverse=True)
# Update last report time
self._last_report_time = now
stats = {
"reporting_period": {
"start": period_start.strftime("%Y-%m-%d %H:%M:%S"),
"end": period_end.strftime("%Y-%m-%d %H:%M:%S"),
},
"total_reservations": total_reservations,
"hotels": hotels_stats,
}
_LOGGER.info(
"Collected stats: %d total reservations across %d hotels",
total_reservations,
len(hotels_stats),
)
return stats