educonnect-api / app.py
saidouu's picture
Add health check route
eb0ec4d
# # version update
# import os
# import pickle
# import threading
# from flask import Flask, request, jsonify
# from flask_cors import CORS
# from supabase import create_client, Client
# from sentence_transformers import SentenceTransformer
# from sklearn.metrics.pairwise import cosine_similarity
# import resend
# app = Flask(__name__)
# CORS(app)
# # ==========================================
# # ⚠️ CONFIGURATION (À VÉRIFIER)
# # ==========================================
# SUPABASE_URL = "https://dvddftdtrkidsulcxaqp.supabase.co"
# SUPABASE_KEY = "sb_secret_CoFpwT9q6IrR-lfzjXynKg_DCoyB8F0" #
# resend.api_key = "re_CYUPs5Nt_A3L3t2EDX1UT5JbBLLycqTHM" #
# supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
# print("⏳ Chargement du modèle IA...")
# model_ia = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') #
# def load_vectors():
# with open("opportunities_vectors.pkl", "rb") as f: #
# return pickle.load(f)
# vector_data = load_vectors()
# # 📧 Notification Email en Arrière-plan
# def send_email_background(nom, email, domaine, opportunites):
# # C'est cette ligne qui définit le nom vu par l'utilisateur
# # sender_email = "EduConnect Afrika <contact@educonnectafrika.com>"
# sender_email = "EduConnect Afrika <contact@afriaisolutions.com>"
# bourses_html = "".join([
# f"<li style='margin-bottom: 10px;'><strong>[{b.get('type', 'Opportunité')}] {b.get('titre', '')}</strong> - 📍 {b.get('pays', 'En ligne')}</li>"
# for b in opportunites
# ])
# try:
# resend.Emails.send({
# "from": sender_email,
# "to": email,
# "subject": f"🎯 Vos opportunités en {domaine} sont prêtes !",
# "html": f"""
# <div style="font-family: Arial, sans-serif; border: 1px solid #e2e8f0; padding: 25px; border-radius: 20px; max-width: 600px; color: #1e293b;">
# <h2 style="color: #1e40af;">Félicitations {nom} !</h2>
# <p>Notre IA a analysé votre profil. Voici les meilleures opportunités pour vous :</p>
# <ul style="background-color: #f8fafc; padding: 20px; border-radius: 12px; list-style-type: none;">
# {bourses_html}
# </ul>
# <p>Accédez à votre espace pour postuler :</p>
# <div style="text-align: center; margin: 30px 0;">
# <a href="http://localhost:8080" style="background-color: #2563eb; color: white; padding: 12px 25px; text-decoration: none; border-radius: 10px; font-weight: bold;">Accéder au Dashboard</a>
# </div>
# <hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 20px 0;">
# <p style="font-size: 11px; color: #64748b; text-align: center;">
# <strong>EduConnect Afrika</strong><br>
# L'avenir de l'orientation académique en Afrique.<br>
# Responsable : Lauryane
# </p>
# </div>
# """
# })
# print(f"✅ Email envoyé avec succès via EduConnect à {email}")
# except Exception as e:
# print(f"❌ Erreur d'envoi : {e}")
# @app.route('/api/recommend', methods=['POST'])
# def get_recommendations():
# data = request.json
# user_id = data.get('user_id')
# try:
# # 1. Profil utilisateur
# res_profile = supabase.table('profiles').select('*').eq('user_id', user_id).execute()
# if not res_profile.data: return jsonify({"error": "Profil introuvable"}), 404
# user = res_profile.data[0]
# filiere = user.get('filiere') or "votre domaine"
# nom_etudiant = user.get('name') or "Étudiant"
# email_etudiant = user.get('email')
# # 2. Vectorisation du profil
# profil_text = f"Niveau: {user.get('niveau')}. Domaine: {filiere}. Intérêts: {user.get('interets')}."
# user_vector = model_ia.encode([profil_text])
# # 3. Similarité et Scoring
# similarities = cosine_similarity(user_vector, vector_data["vectors"])[0]
# top_indices = similarities.argsort()[-15:][::-1] # On prend un peu plus pour mixer
# scores_dict = {int(vector_data["ids"][idx]): min(0.99, float(similarities[idx]) + 0.35) for idx in top_indices}
# # 4. Récupération unifiée des opportunités
# top_ids = list(scores_dict.keys())
# res_opps = supabase.table('opportunities').select('*').in_('id', top_ids).execute()
# recommandations = []
# for opp in res_opps.data:
# opp['score_ia'] = scores_dict[opp['id']]
# recommandations.append(opp)
# # Tri final par score
# recommandations = sorted(recommandations, key=lambda x: x['score_ia'], reverse=True)
# # 🚀 5. Email en arrière-plan
# if email_etudiant:
# thread = threading.Thread(target=send_email_background, args=(nom_etudiant, email_etudiant, filiere, recommandations[:3]))
# thread.start()
# return jsonify({"status": "success", "recommandations": recommandations})
# except Exception as e:
# print(f"❌ Erreur: {e}")
# return jsonify({"error": str(e)}), 500
# if __name__ == '__main__':
# app.run(port=5000, debug=True)
# code pour la prod
# version update - Sécurisée pour Hugging Face Spaces
import os
import pickle
import threading
from flask import Flask, request, jsonify
from flask_cors import CORS
from supabase import create_client, Client
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import resend
app = Flask(__name__)
@app.route('/')
def home():
return jsonify({
"status": "online",
"message": "EduConnect Afrika API is running successfully!",
"version": "1.0.0"
})
# Autorise ton frontend Vercel à appeler cette API
CORS(app)
# ==========================================
# 🔐 CONFIGURATION SÉCURISÉE (VIA SECRETS HF)
# ==========================================
# On récupère les clés depuis l'environnement du serveur
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
resend.api_key = os.getenv("RESEND_API_KEY")
# Vérification au démarrage pour éviter les crashs silencieux
if not SUPABASE_URL or not SUPABASE_KEY:
print("❌ ERREUR : Les variables d'environnement Supabase sont manquantes !")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
print("⏳ Chargement du modèle IA (paraphrase-multilingual)...")
model_ia = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
def load_vectors():
try:
with open("opportunities_vectors.pkl", "rb") as f:
return pickle.load(f)
except FileNotFoundError:
print("❌ ERREUR : Le fichier opportunities_vectors.pkl est introuvable !")
return None
vector_data = load_vectors()
# 📧 Notification Email en Arrière-plan
def send_email_background(nom, email, domaine, opportunites):
# Expéditeur utilisant ton domaine afriaisolutions.com validé sur Resend
sender_email = "EduConnect Afrika <contact@afriaisolutions.com>"
bourses_html = "".join([
f"<li style='margin-bottom: 10px;'><strong>[{b.get('type', 'Opportunité')}] {b.get('titre', '')}</strong> - 📍 {b.get('pays', 'En ligne')}</li>"
for b in opportunites
])
try:
resend.Emails.send({
"from": sender_email,
"to": email,
"subject": f"🎯 Vos opportunités en {domaine} sont prêtes !",
"html": f"""
<div style="font-family: Arial, sans-serif; border: 1px solid #e2e8f0; padding: 25px; border-radius: 20px; max-width: 600px; color: #1e293b;">
<h2 style="color: #1e40af;">Félicitations {nom} !</h2>
<p>Notre IA a analysé votre profil. Voici les meilleures opportunités pour vous :</p>
<ul style="background-color: #f8fafc; padding: 20px; border-radius: 12px; list-style-type: none;">
{bourses_html}
</ul>
<p>Accédez à votre espace pour postuler :</p>
<div style="text-align: center; margin: 30px 0;">
<a href="https://app.educonnectafrika.com" style="background-color: #2563eb; color: white; padding: 12px 25px; text-decoration: none; border-radius: 10px; font-weight: bold;">Accéder au Dashboard</a>
</div>
<hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 20px 0;">
<p style="font-size: 11px; color: #64748b; text-align: center;">
<strong>EduConnect Afrika</strong><br>
L'avenir de l'orientation académique en Afrique.<br>
Responsable : Lauryane
</p>
</div>
"""
})
print(f"✅ Email envoyé avec succès à {email}")
except Exception as e:
print(f"❌ Erreur d'envoi Resend : {e}")
@app.route('/api/recommend', methods=['POST'])
def get_recommendations():
data = request.json
user_id = data.get('user_id')
if not user_id:
return jsonify({"error": "user_id manquant"}), 400
try:
# 1. Récupération du profil
res_profile = supabase.table('profiles').select('*').eq('user_id', user_id).execute()
if not res_profile.data:
return jsonify({"error": "Profil introuvable"}), 404
user = res_profile.data[0]
filiere = user.get('filiere') or "votre domaine"
nom_etudiant = user.get('name') or "Étudiant"
email_etudiant = user.get('email')
# 2. Vectorisation du profil utilisateur
profil_text = f"Niveau: {user.get('niveau')}. Domaine: {filiere}. Intérêts: {user.get('interets')}."
user_vector = model_ia.encode([profil_text])
# 3. Calcul de similarité (Cosine Similarity)
similarities = cosine_similarity(user_vector, vector_data["vectors"])[0]
top_indices = similarities.argsort()[-15:][::-1]
# Scoring IA ajusté
scores_dict = {int(vector_data["ids"][idx]): min(0.99, float(similarities[idx]) + 0.35) for idx in top_indices}
# 4. Récupération des données depuis Supabase
top_ids = list(scores_dict.keys())
res_opps = supabase.table('opportunities').select('*').in_('id', top_ids).execute()
recommandations = []
for opp in res_opps.data:
opp['score_ia'] = scores_dict[opp['id']]
recommandations.append(opp)
# Tri final par score décroissant
recommandations = sorted(recommandations, key=lambda x: x['score_ia'], reverse=True)
# 🚀 5. Notification Email (Asynchrone via Threading)
if email_etudiant:
thread = threading.Thread(target=send_email_background, args=(nom_etudiant, email_etudiant, filiere, recommandations[:3]))
thread.start()
return jsonify({"status": "success", "recommandations": recommandations})
except Exception as e:
print(f"❌ Erreur API : {e}")
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
# Configuration obligatoire pour Hugging Face Spaces (Port 7860)
app.run(host='0.0.0.0', port=7860, debug=False)