Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EstefanoARG/FridgeRadar/llms.txt

Use this file to discover all available pages before exploring further.

FridgeRadar runs a small suite of recurring background jobs to keep pantry data fresh without requiring any user action. These jobs are powered by APScheduler (AsyncIOScheduler), which runs inside the FastAPI process and shares its async event loop. Three task modules handle distinct concerns: recalculating the freshness semáforo for every inventory item, generating expiry alert notifications, and automatically logging expired items as waste events. All tasks use CronTrigger schedules and run in the America/Lima timezone by default.

Scheduler overview

Semáforo recalculation

Runs at 00:05 daily. Recalculates estado_caducidad for every inventory item across all households.

Expiry alerts

Runs at 08:00 daily. Generates individual and bulk expiry-warning notifications for items in amarillo, rojo, and vencido states.

Auto waste logging

Runs at 02:00 daily. Automatically creates waste event records for any inventory item whose estado_caducidad is "vencido" and has not already been logged.

Task 1 — Semáforo recalculation (semaforo_task.py)

This task recalculates the estado_caducidad colour state for every product in inventory, then generates expiry alerts at 8 AM. Both jobs live in the same module and share a single AsyncIOScheduler instance:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

from app.core.database import AsyncSessionLocal
from app.services.semaforo_service import SemaforoService

scheduler = AsyncIOScheduler(timezone="America/Lima")


@scheduler.scheduled_job(CronTrigger(hour=0, minute=5))
async def actualizar_semaforos():
    """Recalcula el estado de todos los productos cada medianoche."""
    async with AsyncSessionLocal() as db:
        service = SemaforoService(db)
        total = await service.recalcular_todos()
        logger.info(f"Semáforo actualizado: {total} productos procesados")


@scheduler.scheduled_job(CronTrigger(hour=8, minute=0))
async def generar_alertas_diarias():
    """Genera alertas de vencimiento cada mañana a las 8 AM."""
    async with AsyncSessionLocal() as db:
        service = SemaforoService(db)
        alertas = await service.generar_alertas_vencimiento()
        logger.info(f"Alertas generadas: {alertas}")
JobCronWhat it does
actualizar_semaforos00:05 dailyUpdates estado_caducidad on all inventory rows
generar_alertas_diarias08:00 dailyCreates expiry-warning notifications via SemaforoService

Task 2 — Alert batch generation (alerta_task.py)

A second scheduler module uses AlertaService to perform a bulk alert sweep every morning. This complements the semáforo-based alerts with any additional notification logic encapsulated in AlertaService:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger

from app.core.database import AsyncSessionLocal
from app.services.alerta_service import AlertaService

scheduler = AsyncIOScheduler(timezone="America/Lima")


@scheduler.scheduled_job(CronTrigger(hour=8, minute=0))
async def generar_alertas_batch():
    """Genera alertas masivas cada mañana."""
    async with AsyncSessionLocal() as db:
        service = AlertaService(db)
        logger.info("Tarea de generación de alertas ejecutada")

Task 3 — Automatic waste logging (desperdicio_task.py)

At 02:00 every morning, this task scans the inventory for items whose estado_caducidad is "vencido" and, for each one that has not yet been linked to a waste event, creates a Desperdicio record automatically:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from sqlalchemy import select

from app.core.database import AsyncSessionLocal
from app.models.desperdicio import Desperdicio
from app.models.inventario import Inventario

scheduler = AsyncIOScheduler(timezone="America/Lima")


@scheduler.scheduled_job(CronTrigger(hour=2, minute=0))
async def marcar_vencidos_como_desperdicio():
    """Marca automáticamente productos vencidos como desperdicio."""
    async with AsyncSessionLocal() as db:
        stmt = select(Inventario).where(Inventario.estado_caducidad == "vencido")
        result = await db.execute(stmt)
        vencidos = result.scalars().all()

        count = 0
        for item in vencidos:
            existing = await db.execute(
                select(Desperdicio).where(
                    Desperdicio.id_inventario == item.id_inventario
                )
            )
            if not existing.scalar_one_or_none():
                desperdicio = Desperdicio(
                    id_inventario=item.id_inventario,
                    cantidad=item.cantidad,
                    motivo="vencido",
                    comentario="Marcado automáticamente por tarea programada",
                )
                db.add(desperdicio)
                count += 1

        await db.commit()
        logger.info("Marcados %d productos vencidos como desperdicio", count)
This ensures that waste metrics remain accurate even when users do not manually log discarded items. See the Waste Tracking guide for more on waste events.

Task schedule at a glance

Time (default TZ)TaskModule
00:05Recalculate all semáforo statessemaforo_task.py
02:00Auto-log expired items as wastedesperdicio_task.py
08:00Generate expiry alerts (semaforo)semaforo_task.py
08:00Generate expiry alerts (batch)alerta_task.py

Timezone configuration

All three schedulers are initialised with timezone="America/Lima" hardcoded in their respective task modules. The application settings (config.py) expose a SCHEDULER_TIMEZONE field (defaulting to "America/Lima") that can be set via the environment or .env file, but the current task modules do not read that setting — they each instantiate their own AsyncIOScheduler with the timezone literal. To change the timezone you must update the timezone= argument in each task file directly.
The timezone setting affects when the cron jobs fire in wall-clock time. If your server runs on UTC, a job scheduled for 08:00 in America/Lima (UTC−5) will actually execute at 13:00 UTC.

Triggering semáforo recalculation on demand

You do not have to wait for the 00:05 cron to refresh inventory states. The API exposes two manual recalculation endpoints: Recalculate all households:
curl -X POST https://your-fridgeradar-host/api/v1/semaforo/recalcular \
  -H "Authorization: Bearer <token>"
Recalculate a specific household:
curl -X POST https://your-fridgeradar-host/api/v1/semaforo/recalcular/1 \
  -H "Authorization: Bearer <token>"
These are useful after bulk imports, data corrections, or whenever you want freshness states updated immediately rather than waiting for the nightly run.
The scheduler runs inside the FastAPI process on the same asyncio event loop. If the web server is restarted or crashes, all scheduled jobs stop until the process comes back up. For a production deployment where continuity matters, consider extracting the tasks into a dedicated worker process (e.g. using Celery Beat with a Redis broker, or a separate APScheduler process) so that scheduled jobs survive web server restarts independently.

Build docs developers (and LLMs) love