Spaces:
Sleeping
Sleeping
| # # 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__) | |
| 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}") | |
| 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) |