Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| import json | |
| import numpy as np | |
| METIERS_CIBLES = [ | |
| { | |
| "titre": "Data Engineer", | |
| "requis": [ | |
| {"domaine": "Data", "methode": "Architecture", "outil": "SQL"}, | |
| {"domaine": "Informatique", "methode": "Développement", "outil": "Python"}, | |
| {"domaine": "Informatique", "methode": "Cloud", "outil": "AWS"} | |
| ] | |
| }, | |
| { | |
| "titre": "Responsable Transformation Digitale", | |
| "requis": [ | |
| {"domaine": "Gestion", "methode": "Agilité", "outil": "Scrum"}, | |
| {"domaine": "Marketing", "methode": "Analyse", "outil": "Google Analytics"}, | |
| {"domaine": "Data", "methode": "Visualisation", "outil": "Power BI"} | |
| ] | |
| }, | |
| { | |
| "titre": "Product Owner", | |
| "requis": [ | |
| {"domaine": "Gestion", "methode": "Agilité", "outil": "Jira"}, | |
| {"domaine": "Gestion", "methode": "Planning", "outil": "Trello"}, | |
| {"domaine": "Marketing", "methode": "Stratégie Digitale", "outil": "Figma"} | |
| ] | |
| }, | |
| { | |
| "titre": "Analyste Cybersécurité", | |
| "requis": [ | |
| {"domaine": "Informatique", "methode": "Sécurité", "outil": "Firewalls"}, | |
| {"domaine": "Informatique", "methode": "Cloud", "outil": "Docker"}, | |
| {"domaine": "Informatique", "methode": "Scripting", "outil": "Bash"} | |
| ] | |
| } | |
| ] | |
| # --- 2. FONCTIONS DE CALCUL --- | |
| def calculer_score(candidat, poste): | |
| score_total = 0 | |
| max_score_possible = len(poste['requis']) * 3 | |
| for req in poste['requis']: | |
| points_competence = 0 | |
| for comp in candidat['competences']: | |
| if comp['outil'].lower() == req['outil'].lower(): | |
| points_competence = max(points_competence, 3) | |
| elif comp['methode'].lower() == req['methode'].lower(): | |
| points_competence = max(points_competence, 2) | |
| elif comp['domaine'].lower() == req['domaine'].lower(): | |
| points_competence = max(points_competence, 1) | |
| score_total += points_competence | |
| return (score_total / max_score_possible) * 100 | |
| # --- 3. INTERFACE PRINCIPALE --- | |
| def main(): | |
| st.set_page_config(page_title="Skill Mapping MVP", layout="wide") | |
| # --- VERROU DE SÉCURITÉ SÉCURISÉ --- | |
| if "authenticated" not in st.session_state: | |
| st.session_state["authenticated"] = False | |
| if not st.session_state["authenticated"]: | |
| st.title("🔐 Accès Restreint") | |
| password_input = st.text_input("Veuillez saisir le code d'accès :", type="password") | |
| if st.button("Se connecter"): | |
| # On compare la saisie avec le secret caché sur Hugging Face | |
| if password_input == st.secrets["MY_PASSWORD"]: | |
| st.session_state["authenticated"] = True | |
| st.rerun() | |
| else: | |
| st.error("Code incorrect.") | |
| return | |
| st.title("🎯 Mapping des talents Internes") | |
| try: | |
| with open('data.json', 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| collaborateurs = data['collaborateurs'] | |
| except FileNotFoundError: | |
| st.error("Fichier 'data.json' non trouvé.") | |
| return | |
| # --- SIDEBAR & SÉLECTION --- | |
| st.sidebar.header("Paramètres") | |
| titre_metier = st.sidebar.selectbox("Poste cible :", [m['titre'] for m in METIERS_CIBLES]) | |
| poste_data = next(m for m in METIERS_CIBLES if m['titre'] == titre_metier) | |
| # Calcul des scores (pré-traitement) | |
| resultats = [] | |
| for c in collaborateurs: | |
| score = calculer_score(c, poste_data) | |
| resultats.append({ | |
| "Nom": c['nom'], | |
| "Métier Actuel": c['metier_actuel'], | |
| "Score de Match": round(score, 1), | |
| "data_brute": c | |
| }) | |
| df = pd.DataFrame(resultats).sort_values(by="Score de Match", ascending=False) | |
| # --- CRÉATION DES ONGLETS --- | |
| tab1, tab2, tab3 = st.tabs(["📊 Classement", "🌌 Cartographie 2D", "🔍 Analyse Individuelle"]) | |
| # --- ONGLET 1 : CLASSEMENT --- | |
| with tab1: | |
| st.subheader(f"Adéquation des profils pour : {titre_metier}") | |
| st.dataframe( | |
| df[["Nom", "Métier Actuel", "Score de Match"]], | |
| column_config={ | |
| "Score de Match": st.column_config.ProgressColumn( | |
| "Adéquation", | |
| format="%.1f%%", | |
| min_value=0, | |
| max_value=100 | |
| ), | |
| }, | |
| hide_index=True, | |
| use_container_width=True | |
| ) | |
| # --- ONGLET 2 : MAPPING 2D --- | |
| with tab2: | |
| st.subheader("Positionnement par rapport au poste cible") | |
| df_map = df.copy() | |
| # Calcul des coordonnées | |
| angles = np.linspace(0, 2 * np.pi, len(df_map), endpoint=False) | |
| df_map['distance'] = 100 - df_map['Score de Match'] | |
| df_map['x'] = df_map['distance'] * np.cos(angles) | |
| df_map['y'] = df_map['distance'] * np.sin(angles) | |
| # Création du graphique de base | |
| fig_2d = px.scatter( | |
| df_map, x='x', y='y', text='Nom', | |
| color='Score de Match', | |
| color_continuous_scale="RdYlGn", | |
| range_color=[0, 100], | |
| template="plotly_white" | |
| ) | |
| # Ajout manuel du point CIBLE au centre pour être sûr qu'il soit là | |
| fig_2d.add_scatter( | |
| x=[0], y=[0], mode='markers+text', text=['🎯 CIBLE'], | |
| marker=dict(size=20, color='black'), name='Cible' | |
| ) | |
| # Amélioration du texte et des marqueurs | |
| fig_2d.update_traces( | |
| textposition='top center', | |
| marker=dict(size=12, line=dict(width=1, color='DarkSlateGrey')) | |
| ) | |
| # Ajout des cercles de radar DERRIÈRE les points | |
| for r in [25, 50, 75, 100]: | |
| fig_2d.add_shape( | |
| type="circle", x0=-r, y0=-r, x1=r, y1=r, | |
| line=dict(color="lightgray", width=1, dash="dot"), | |
| layer="below" # <--- IMPORTANT : Force les cercles en arrière-plan | |
| ) | |
| fig_2d.update_layout( | |
| showlegend=False, height=700, | |
| xaxis=dict(visible=False, range=[-110, 110]), | |
| yaxis=dict(visible=False, range=[-110, 110]) | |
| ) | |
| st.plotly_chart(fig_2d, use_container_width=True) | |
| # --- ONGLET 3 : FOCUS CANDIDAT (ANALYSE DES ÉCARTS) --- | |
| with tab3: | |
| st.subheader("Détail des écarts par collaborateur") | |
| # Sélection du candidat | |
| nom_selectionne = st.selectbox( | |
| "Choisir un collaborateur à analyser :", | |
| df['Nom'].tolist() | |
| ) | |
| cand = next(c for c in collaborateurs if c['nom'] == nom_selectionne) | |
| st.markdown(f"**Métier actuel :** `{cand['metier_actuel']}`") | |
| st.write(f"**Score de correspondance :** {df.loc[df['Nom'] == nom_selectionne, 'Score de Match'].values[0]}%") | |
| st.divider() # Petite ligne de séparation | |
| col_left, col_right = st.columns(2) | |
| with col_left: | |
| st.markdown(f"### ✅ Compétences actuelles") | |
| for comp in cand['competences']: | |
| st.markdown(f"- **{comp['outil']}** \n *{comp['domaine']} > {comp['methode']}*") | |
| with col_right: | |
| st.markdown("### 🚩 Comparaison avec le besoin") | |
| for req in poste_data['requis']: | |
| match_outil = any(c['outil'].lower() == req['outil'].lower() for c in cand['competences']) | |
| match_methode = any(c['methode'].lower() == req['methode'].lower() for c in cand['competences']) | |
| if match_outil: | |
| st.success(f"**{req['outil']}** : Maîtrisé") | |
| elif match_methode: | |
| st.warning(f"**{req['outil']}** : Partiel (connaît la méthode '{req['methode']}')") | |
| else: | |
| st.error(f"**{req['outil']}** : Manquant") | |
| if __name__ == "__main__": | |
| main() |