File size: 11,584 Bytes
6ec767d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb0ec4d
 
 
 
 
 
 
6ec767d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281







# # 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)