File size: 14,882 Bytes
2cb2920 2f16005 2cb2920 | 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 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | import pandas as pd
import streamlit as st
from jobspy import scrape_jobs
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
from openai import OpenAI
import json
import os
from datetime import datetime
from dotenv import load_dotenv
# Charger les variables d'environnement depuis un fichier .env
load_dotenv()
# Configuration de l'API OpenAI (ChatGPT) avec OpenRouter
openai = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="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",
)
# Initialisation des variables de session
if 'global_jobs_df' not in st.session_state:
st.session_state.global_jobs_df = pd.DataFrame()
if 'selected_job' not in st.session_state:
st.session_state.selected_job = {}
# Fonction pour collecter les offres d'emploi
def collect_job_data(keywords, location, results_wanted=100):
try:
jobs = scrape_jobs(
site_name=[
'google',
],
search_term=keywords,
location=location,
results_wanted=results_wanted
)
return jobs
except Exception as e:
st.error(f"Erreur lors de la collecte des données: {e}")
return pd.DataFrame()
# Fonction pour enrichir les données d'emploi avec l'IA
def enrich_job_data(jobs_df):
if jobs_df.empty:
return jobs_df
# Vectorisation des descriptions pour l'analyse
vectorizer = TfidfVectorizer(max_features=1000)
job_vectors = vectorizer.fit_transform(jobs_df['description'].fillna(''))
# Extraction des compétences clés (version simplifiée)
jobs_df['key_skills'] = jobs_df['description'].apply(extract_skills_from_text)
# Ajout des vecteurs pour le matching
jobs_df['vector'] = list(job_vectors.toarray())
return jobs_df
# Extraction simplifiée des compétences
def extract_skills_from_text(text):
if not isinstance(text, str):
return []
common_skills = ["python", "java", "sql", "javascript", "machine learning",
"data analysis", "project management", "communication"]
return [skill for skill in common_skills if skill.lower() in text.lower()]
# Fonction de matching entre profil utilisateur et offres
def match_jobs_to_profile(jobs_df, user_profile):
if jobs_df.empty:
return jobs_df
# Créer un vecteur pour les préférences utilisateur
user_keywords = ' '.join([
user_profile.get('job_title', ''),
user_profile.get('skills', ''),
user_profile.get('interests', '')
])
vectorizer = TfidfVectorizer(max_features=1000)
all_texts = jobs_df['description'].fillna('').tolist() + [user_keywords]
vectors = vectorizer.fit_transform(all_texts)
user_vector = vectors[-1]
job_vectors = vectors[:-1]
# Calcul des scores de similarité
similarity_scores = cosine_similarity(user_vector, job_vectors).flatten()
jobs_df['match_score'] = similarity_scores
matched_jobs = jobs_df.sort_values('match_score', ascending=False).reset_index(drop=True)
return matched_jobs
# Génération de CV avec ChatGPT API
def generate_cv(job_details, user_profile):
try:
prompt = f"""
Génère un CV professionnel pour la candidature au poste suivant:
POSTE: {job_details.get('title', '')}
ENTREPRISE: {job_details.get('company', '')}
DESCRIPTION DU POSTE : {job_details.get('description', '')}
PROFIL DU CANDIDAT:
Nom: {user_profile.get('name', '')}
Formation: {user_profile.get('education', '')}
Expériences: {user_profile.get('experience', '')}
Compétences: {user_profile.get('skills', '')}
Format le CV de manière professionnelle et adapte-le spécifiquement pour ce poste.
"""
with st.spinner('Génération du CV en cours...'):
response = openai.chat.completions.create(
model="qwen/qwen2.5-vl-72b-instruct:free",
messages=[
{"role": "system", "content": "Tu es un expert en rédaction de CV qui adapte parfaitement les profils aux offres d'emploi."},
{"role": "user", "content": prompt}
],
max_tokens=1500
)
return response.choices[0].message.content
except Exception as e:
return f"Erreur lors de la génération du CV: {str(e)}"
# Génération de lettre de motivation avec ChatGPT API
def generate_cover_letter(job_details, user_profile):
try:
prompt = f"""
Génère une lettre de motivation personnalisée pour la candidature au poste suivant:
POSTE: {job_details.get('title', '')}
ENTREPRISE: {job_details.get('company', '')}
DESCRIPTION DU POSTE: {job_details.get('description', '')}
PROFIL DU CANDIDAT:
Nom: {user_profile.get('name', '')}
Formation: {user_profile.get('education', '')}
Expériences: {user_profile.get('experience', '')}
Compétences: {user_profile.get('skills', '')}
Motivation pour ce poste: {user_profile.get('motivation', '')}
La lettre doit être convaincante, professionnelle et adaptée spécifiquement à cette offre.
"""
with st.spinner('Génération de la lettre de motivation en cours...'):
response = openai.chat.completions.create(
model="qwen/qwen2.5-vl-72b-instruct:free",
messages=[
{"role": "system", "content": "Tu es un expert en rédaction de lettres de motivation persuasives et personnalisées."},
{"role": "user", "content": prompt}
],
max_tokens=1000
)
return response.choices[0].message.content
except Exception as e:
return f"Erreur lors de la génération de la lettre de motivation: {str(e)}"
# Fonction pour la recherche d'emploi
def search_jobs(keywords, location, job_title, skills, interests):
# Profil utilisateur basique
user_profile = {
'job_title': job_title,
'skills': skills,
'interests': interests
}
with st.spinner('Recherche des offres en cours...'):
# Collecte et enrichissement des données
jobs_df = collect_job_data(keywords, location)
enriched_jobs = enrich_job_data(jobs_df)
# Matching avec le profil utilisateur
matched_jobs = match_jobs_to_profile(enriched_jobs, user_profile)
# Stockage pour utilisation ultérieure
st.session_state.global_jobs_df = matched_jobs
return matched_jobs
# Fonction pour sélectionner une offre d'emploi
def select_job(job_index):
if st.session_state.global_jobs_df.empty or job_index >= len(st.session_state.global_jobs_df):
st.error("Offre d'emploi non trouvée.")
return
job = st.session_state.global_jobs_df.iloc[job_index]
st.session_state.selected_job = {
'title': job.get('title', ''),
'company': job.get('company', ''),
'location': job.get('location', ''),
'description': job.get('description', ''),
'date_posted': job.get('date_posted', ''),
'salary': job.get('salary', 'Non spécifié'),
'link': job.get('url', '#')
}
# Interface principale Streamlit
def main():
st.set_page_config(
page_title="Recherche d'Emploi IA",
page_icon="🔍",
layout="wide"
)
st.title("Recherche d'Emploi Intelligente")
# Navigation par onglets
tab1, tab2, tab3 = st.tabs(["Recherche", "Détails de l'offre", "Générer Documents"])
# Onglet Recherche
with tab1:
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("Critères de recherche")
keywords = st.text_input("Mots-clés de recherche")
location = st.text_input("Lieu")
st.subheader("Votre profil")
job_title = st.text_input("Poste recherché")
skills = st.text_input("Compétences (séparées par des virgules)")
interests = st.text_input("Centres d'intérêt")
if st.button("Rechercher", type="primary"):
if not keywords or not location:
st.error("Veuillez saisir des mots-clés et un lieu pour la recherche.")
else:
results = search_jobs(keywords, location, job_title, skills, interests)
if results.empty:
st.error("Aucun résultat trouvé.")
with col2:
st.subheader("Résultats de recherche")
if not st.session_state.global_jobs_df.empty:
# Affichage des résultats sous forme de tableau
results_df = st.session_state.global_jobs_df.head(10)[['title', 'company', 'location']].copy()
results_df['match_score'] = st.session_state.global_jobs_df.head(10)['match_score'].apply(lambda x: f"{x*100:.1f}%")
results_df['actions'] = 'Voir détails'
# Utiliser st.dataframe avec sélection de ligne
selection = st.dataframe(
results_df,
column_config={
"title": "Titre du poste",
"company": "Entreprise",
"location": "Lieu",
"match_score": "Score de correspondance",
"actions": st.column_config.LinkColumn("Actions")
},
use_container_width=True,
hide_index=False
)
st.write("Sélectionnez une offre pour voir les détails")
job_index = st.number_input("ID de l'offre à consulter",
min_value=0,
max_value=len(st.session_state.global_jobs_df)-1 if not st.session_state.global_jobs_df.empty else 0,
step=1)
if st.button("Voir les détails"):
select_job(job_index)
st.switch_page("#détails-de-l'offre") # Navigation vers l'onglet détails
# Onglet Détails de l'offre
with tab2:
if st.session_state.selected_job:
job = st.session_state.selected_job
st.header(f"{job['title']} - {job['company']}")
col1, col2 = st.columns([1, 1])
with col1:
st.write(f"**Lieu:** {job['location']}")
with col2:
st.write(f"**Date de publication:** {job['date_posted']}")
st.write(f"**Salaire:** {job['salary']}")
st.subheader("Description du poste")
st.write(job['description'])
if job['link'] and job['link'] != '#':
st.link_button("Voir l'offre originale", job['link'])
else:
st.info("Veuillez d'abord sélectionner une offre d'emploi dans l'onglet Recherche.")
# Onglet Générer Documents
with tab3:
if not st.session_state.selected_job:
st.info("Veuillez d'abord sélectionner une offre d'emploi dans l'onglet Recherche.")
else:
st.subheader("Générer CV et Lettre de motivation")
st.write("Complétez votre profil pour générer les documents")
name = st.text_input("Nom complet")
education = st.text_input("Formation")
experience = st.text_area("Expériences professionnelles", height=150)
detailed_skills = st.text_area("Compétences détaillées", height=100)
motivation = st.text_area("Votre motivation pour ce poste", height=100)
col1, col2 = st.columns(2)
with col1:
if st.button("Générer CV", type="primary"):
if not name or not education or not experience or not detailed_skills:
st.error("Veuillez remplir tous les champs obligatoires.")
else:
user_profile = {
'name': name,
'education': education,
'experience': experience,
'skills': detailed_skills,
'motivation': motivation
}
cv = generate_cv(st.session_state.selected_job, user_profile)
st.session_state.cv = cv
with col2:
if st.button("Générer Lettre de motivation", type="primary"):
if not name or not education or not experience or not detailed_skills or not motivation:
st.error("Veuillez remplir tous les champs, y compris votre motivation.")
else:
user_profile = {
'name': name,
'education': education,
'experience': experience,
'skills': detailed_skills,
'motivation': motivation
}
cover_letter = generate_cover_letter(st.session_state.selected_job, user_profile)
st.session_state.cover_letter = cover_letter
# Affichage du CV généré
if 'cv' in st.session_state:
st.subheader("CV généré")
st.markdown(st.session_state.cv)
# Bouton pour télécharger le CV
st.download_button(
label="Télécharger le CV",
data=st.session_state.cv,
file_name="CV_généré.md",
mime="text/markdown"
)
# Affichage de la lettre de motivation générée
if 'cover_letter' in st.session_state:
st.subheader("Lettre de motivation générée")
st.markdown(st.session_state.cover_letter)
# Bouton pour télécharger la lettre de motivation
st.download_button(
label="Télécharger la lettre de motivation",
data=st.session_state.cover_letter,
file_name="Lettre_de_motivation.md",
mime="text/markdown"
)
if __name__ == "__main__":
main() |