Stats collector for email monitoring
This commit is contained in:
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user