All guides
IntermediateSelf-pacedFree GuideSection 54/95

Job Tracker — Rappels et notifications

Practise development foundations: programming, web, APIs, data and AI tools. Work through sections and exercises to build your first projects.

The full lessons for this course are currently available in French. Use the section menu to navigate the French course.

💡

Free content, available without an account. Tools and models change: use the current model guide to check their versions. Models and sources

Section 11.3.15 : Job Tracker — Rappels et notifications

🎯 Objectif pédagogique

Ajouter un système de rappels et notifications au Job Tracker : relances automatiques, alertes deadline, et résumé quotidien par email. Vous serez capable d'intégrer des notifications dans une application Python.


Ne jamais oublier une relance

Marc a 15 candidatures en cours. Certaines attendent depuis 10 jours — il faut relancer. Un entretien est dans 2 jours — il faut préparer. Sans système de rappels, des opportunités passent à la trappe.

Système de rappels

# notifications.py — Système de rappels et alertes
import sqlite3
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta
from database import get_connection
import os

class NotificationManager:
    def __init__(self):
        self.rules = [
            \{
                "name": "Relance candidature",
                "condition": self._needs_follow_up,
                "message": self._follow_up_message,
                "priority": "medium"
            \},
            \{
                "name": "Entretien imminent",
                "condition": self._interview_soon,
                "message": self._interview_message,
                "priority": "high"
            \},
            \{
                "name": "Candidature stale",
                "condition": self._is_stale,
                "message": self._stale_message,
                "priority": "low"
            \}
        ]
    
    def _needs_follow_up(self, app):
        """Candidature > 7 jours sans mise à jour, statut 'applied'."""
        if app['status'] != 'applied':
            return False
        last = datetime.fromisoformat(app['last_update'] or app['created_at'])
        return (datetime.now() - last).days >= 7
    
    def _interview_soon(self, app):
        """Entretien dans les 48h."""
        conn = get_connection()
        events = conn.execute(
            """SELECT * FROM events 
               WHERE application_id = ? AND event_type = 'interview_scheduled'
               ORDER BY event_date DESC LIMIT 1""",
            (app['id'],)
        ).fetchone()
        conn.close()
        
        if not events:
            return False
        
        interview_date = datetime.fromisoformat(events['event_date'])
        hours_until = (interview_date - datetime.now()).total_seconds() / 3600
        return 0 < hours_until <= 48
    
    def _is_stale(self, app):
        """Candidature > 30 jours sans mise à jour."""
        if app['status'] in ('offer', 'rejected'):
            return False
        last = datetime.fromisoformat(app['last_update'] or app['created_at'])
        return (datetime.now() - last).days >= 30
    
    def _follow_up_message(self, app):
        days = (datetime.now() - datetime.fromisoformat(
            app['last_update'] or app['created_at']
        )).days
        return f"📧 Relancer \{app['company']\} — \{app['position']\} (\{days\} jours sans réponse)"
    
    def _interview_message(self, app):
        return f"🎯 ENTRETIEN IMMINENT — \{app['company']\} — Préparez-vous !"
    
    def _stale_message(self, app):
        return f"⚠️ \{app['company']\} — Aucune mise à jour depuis 30+ jours. Archiver ?"
    
    def check_all(self):
        """Vérifier toutes les règles pour toutes les candidatures actives."""
        conn = get_connection()
        apps = conn.execute(
            "SELECT * FROM applications WHERE archived = 0"
        ).fetchall()
        conn.close()
        
        notifications = []
        for app in apps:
            app_dict = dict(app)
            for rule in self.rules:
                if rule["condition"](app_dict):
                    notifications.append(\{
                        "app": app_dict,
                        "rule": rule["name"],
                        "message": rule["message"](app_dict),
                        "priority": rule["priority"]
                    \})
        
        # Trier par priorité
        priority_order = \{"high": 0, "medium": 1, "low": 2\}
        notifications.sort(key=lambda n: priority_order[n["priority"]])
        
        return notifications


def send_daily_digest(notifications, recipient_email):
    """Envoyer un email récapitulatif quotidien."""
    if not notifications:
        return
    
    # Construire le contenu
    high = [n for n in notifications if n['priority'] == 'high']
    medium = [n for n in notifications if n['priority'] == 'medium']
    low = [n for n in notifications if n['priority'] == 'low']
    
    body = "🎯 JOB TRACKER — Rapport quotidien\n"
    body += f"Date : \{datetime.now().strftime('%d/%m/%Y')\}\n"
    body += "=" * 50 + "\n\n"
    
    if high:
        body += "🔴 URGENT :\n"
        for n in high:
            body += f"  → \{n['message']\}\n"
        body += "\n"
    
    if medium:
        body += "🟡 À FAIRE :\n"
        for n in medium:
            body += f"  → \{n['message']\}\n"
        body += "\n"
    
    if low:
        body += "🔵 INFO :\n"
        for n in low:
            body += f"  → \{n['message']\}\n"
    
    # Envoyer par email (SMTP)
    smtp_server = os.environ.get("SMTP_SERVER", "smtp.gmail.com")
    smtp_port = int(os.environ.get("SMTP_PORT", "587"))
    smtp_user = os.environ.get("SMTP_USER")
    smtp_pass = os.environ.get("SMTP_PASS")
    
    if not smtp_user:
        print("⚠️ SMTP non configuré. Notifications affichées :")
        print(body)
        return
    
    msg = MIMEMultipart()
    msg['Subject'] = f"Job Tracker — \{len(notifications)\} actions (\{datetime.now().strftime('%d/%m')\})"
    msg['From'] = smtp_user
    msg['To'] = recipient_email
    msg.attach(MIMEText(body, 'plain', 'utf-8'))
    
    with smtplib.SMTP(smtp_server, smtp_port) as server:
        server.starttls()
        server.login(smtp_user, smtp_pass)
        server.send_message(msg)
    
    print(f"✅ Digest envoyé à \{recipient_email\}")

Intégration dans Streamlit

# Dans app.py — Ajouter la page Notifications
from notifications import NotificationManager

def render_notifications():
    """Page des notifications et rappels."""
    st.title("🔔 Notifications & Rappels")
    
    manager = NotificationManager()
    notifications = manager.check_all()
    
    if not notifications:
        st.success("✅ Aucune action requise — tout est à jour !")
        return
    
    st.warning(f"⚠️ \{len(notifications)\} actions requises")
    
    for notif in notifications:
        priority_colors = \{"high": "🔴", "medium": "🟡", "low": "🔵"\}
        icon = priority_colors[notif['priority']]
        
        with st.container(border=True):
            col1, col2 = st.columns([4, 1])
            with col1:
                st.markdown(f"\{icon\} **\{notif['message']\}**")
                st.caption(f"Règle : \{notif['rule']\}")
            with col2:
                if notif['priority'] == 'medium':
                    if st.button("✅ Relancé", key=f"done_\{notif['app']['id']\}"):
                        update_status(notif['app']['id'], 'screening')
                        st.rerun()

Automatiser avec un script cron / scheduler

# daily_check.py — Script lancé quotidiennement
from notifications import NotificationManager, send_daily_digest

def main():
    manager = NotificationManager()
    notifications = manager.check_all()
    
    print(f"[\{__import__('datetime').datetime.now()\}] \{len(notifications)\} notifications")
    
    # Afficher dans le terminal
    for n in notifications:
        print(f"  [\{n['priority'].upper()\}] \{n['message']\}")
    
    # Envoyer par email
    send_daily_digest(notifications, "marc@email.com")

if __name__ == "__main__":
    main()

# Lancer quotidiennement avec cron (Linux/Mac) :
# crontab -e
# 0 8 * * * cd /path/to/job-tracker && python daily_check.py

# Ou avec Task Scheduler (Windows) :
# schtasks /create /sc daily /tn "JobTracker" /tr "python daily_check.py" /st 08:00

Alternative : Make + n'importe quel trigger

Au lieu d'un script cron, Marc peut aussi utiliser un scénario Make : Schedule (tous les jours à 8h) → HTTP Request (vers une API Flask locale ou Cloud Function) → Email. L'avantage : pas besoin d'un serveur toujours allumé.

🏋️ Exercice pratique (25 minutes)

  1. →Ajoutez notifications.py à votre Job Tracker
  2. →Créez 3 candidatures test : une vieille (>7 jours), une récente, une avec entretien
  3. →Exécutez python daily_check.py et vérifiez les notifications
  4. →Intégrez la page Notifications dans l'app Streamlit
Quick Check

Pourquoi utiliser un système de règles (rules) plutôt qu'un simple 'if' pour les notifications ?

Quick Check

Quelle est la différence entre un script cron et un scheduler Make pour les rappels ?

Your progress

Progress is self-reported and stored in your browser. The final completion record is not a qualification or an independent assessment.

View progress and completion record

Learn AI, from prompts to agents

15 free guidesQuizzes, diagrams and workshopsNo account needed

One practical AI workflow in your inbox

Methods, resources and new exercises. Free, unsubscribe at any time.