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