Eric2mangel commited on
Commit
a810a33
·
verified ·
1 Parent(s): 89e9963

Upload app.py

Browse files

Problème affichage onglet 2

Files changed (1) hide show
  1. app.py +209 -203
app.py CHANGED
@@ -1,204 +1,210 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import plotly.express as px
4
- import json
5
- import numpy as np
6
-
7
-
8
- METIERS_CIBLES = [
9
- {
10
- "titre": "Data Engineer",
11
- "requis": [
12
- {"domaine": "Data", "methode": "Architecture", "outil": "SQL"},
13
- {"domaine": "Informatique", "methode": "Développement", "outil": "Python"},
14
- {"domaine": "Informatique", "methode": "Cloud", "outil": "AWS"}
15
- ]
16
- },
17
- {
18
- "titre": "Responsable Transformation Digitale",
19
- "requis": [
20
- {"domaine": "Gestion", "methode": "Agilité", "outil": "Scrum"},
21
- {"domaine": "Marketing", "methode": "Analyse", "outil": "Google Analytics"},
22
- {"domaine": "Data", "methode": "Visualisation", "outil": "Power BI"}
23
- ]
24
- },
25
- {
26
- "titre": "Product Owner",
27
- "requis": [
28
- {"domaine": "Gestion", "methode": "Agilité", "outil": "Jira"},
29
- {"domaine": "Gestion", "methode": "Planning", "outil": "Trello"},
30
- {"domaine": "Marketing", "methode": "Stratégie Digitale", "outil": "Figma"}
31
- ]
32
- },
33
- {
34
- "titre": "Analyste Cybersécurité",
35
- "requis": [
36
- {"domaine": "Informatique", "methode": "Sécurité", "outil": "Firewalls"},
37
- {"domaine": "Informatique", "methode": "Cloud", "outil": "Docker"},
38
- {"domaine": "Informatique", "methode": "Scripting", "outil": "Bash"}
39
- ]
40
- }
41
- ]
42
-
43
- # --- 2. FONCTIONS DE CALCUL ---
44
-
45
- def calculer_score(candidat, poste):
46
- score_total = 0
47
- max_score_possible = len(poste['requis']) * 3
48
-
49
- for req in poste['requis']:
50
- points_competence = 0
51
- for comp in candidat['competences']:
52
- if comp['outil'].lower() == req['outil'].lower():
53
- points_competence = max(points_competence, 3)
54
- elif comp['methode'].lower() == req['methode'].lower():
55
- points_competence = max(points_competence, 2)
56
- elif comp['domaine'].lower() == req['domaine'].lower():
57
- points_competence = max(points_competence, 1)
58
- score_total += points_competence
59
- return (score_total / max_score_possible) * 100
60
-
61
- # --- 3. INTERFACE PRINCIPALE ---
62
-
63
- def main():
64
- st.set_page_config(page_title="Skill Mapping MVP", layout="wide")
65
-
66
- # --- VERROU DE SÉCURITÉ SÉCURISÉ ---
67
- if "authenticated" not in st.session_state:
68
- st.session_state["authenticated"] = False
69
-
70
- if not st.session_state["authenticated"]:
71
- st.title("🔐 Accès Restreint")
72
- password_input = st.text_input("Veuillez saisir le code d'accès :", type="password")
73
-
74
- if st.button("Se connecter"):
75
- # On compare la saisie avec le secret caché sur Hugging Face
76
- if password_input == st.secrets["MY_PASSWORD"]:
77
- st.session_state["authenticated"] = True
78
- st.rerun()
79
- else:
80
- st.error("Code incorrect.")
81
- return
82
-
83
- st.title("🎯 Mapping des talents Internes")
84
-
85
- try:
86
- with open('data.json', 'r', encoding='utf-8') as f:
87
- data = json.load(f)
88
- collaborateurs = data['collaborateurs']
89
- except FileNotFoundError:
90
- st.error("Fichier 'data.json' non trouvé.")
91
- return
92
-
93
- # --- SIDEBAR & SÉLECTION ---
94
- st.sidebar.header("Paramètres")
95
- titre_metier = st.sidebar.selectbox("Poste cible :", [m['titre'] for m in METIERS_CIBLES])
96
- poste_data = next(m for m in METIERS_CIBLES if m['titre'] == titre_metier)
97
-
98
- # Calcul des scores (pré-traitement)
99
- resultats = []
100
- for c in collaborateurs:
101
- score = calculer_score(c, poste_data)
102
- resultats.append({
103
- "Nom": c['nom'],
104
- "Métier Actuel": c['metier_actuel'],
105
- "Score de Match": round(score, 1),
106
- "data_brute": c
107
- })
108
- df = pd.DataFrame(resultats).sort_values(by="Score de Match", ascending=False)
109
-
110
- # --- CRÉATION DES ONGLETS ---
111
- tab1, tab2, tab3 = st.tabs(["📊 Classement", "🌌 Cartographie 2D", "🔍 Analyse Individuelle"])
112
-
113
- # --- ONGLET 1 : CLASSEMENT ---
114
- with tab1:
115
- st.subheader(f"Adéquation des profils pour : {titre_metier}")
116
- st.dataframe(
117
- df[["Nom", "Métier Actuel", "Score de Match"]],
118
- column_config={
119
- "Score de Match": st.column_config.ProgressColumn(
120
- "Adéquation",
121
- format="%.1f%%",
122
- min_value=0,
123
- max_value=100
124
- ),
125
- },
126
- hide_index=True,
127
- use_container_width=True
128
- )
129
-
130
- # --- ONGLET 2 : MAPPING 2D ---
131
- with tab2:
132
- st.subheader("Positionnement par rapport au poste cible")
133
- st.info("Plus un collaborateur est proche du centre (CIBLE), plus ses compétences correspondent au besoin.")
134
-
135
- df_map = df.copy()
136
- angles = np.linspace(0, 2 * np.pi, len(df_map), endpoint=False)
137
- df_map['distance'] = 100 - df_map['Score de Match']
138
- df_map['x'] = df_map['distance'] * np.cos(angles)
139
- df_map['y'] = df_map['distance'] * np.sin(angles)
140
-
141
- target_point = pd.DataFrame({
142
- 'Nom': ['CIBLE'], 'x': [0], 'y': [0],
143
- 'Score de Match': [100], 'size': [35]
144
- })
145
- df_map['size'] = 15
146
- df_total = pd.concat([df_map, target_point], ignore_index=True)
147
-
148
- fig_2d = px.scatter(
149
- df_total, x='x', y='y', text='Nom',
150
- color='Score de Match', size='size',
151
- color_continuous_scale="RdYlGn",
152
- range_color=[0, 100],
153
- template="plotly_white"
154
- )
155
-
156
- fig_2d.update_layout(
157
- showlegend=False,
158
- height=600,
159
- xaxis=dict(visible=False),
160
- yaxis=dict(visible=False)
161
- )
162
-
163
- # AJOUTE CETTE LIGNE ICI :
164
- fig_2d.update_traces(textposition='bottom center')
165
- # Ajout des cercles de radar pour le repère visuel
166
- for r in [25, 50, 75]:
167
- fig_2d.add_shape(type="circle", x0=-r, y0=-r, x1=r, y1=r,
168
- line_color="gray", line_width=1, line_dash="dot", opacity=0.3)
169
-
170
- st.plotly_chart(fig_2d, use_container_width=True)
171
-
172
- # --- ONGLET 3 : FOCUS CANDIDAT (ANALYSE DES ÉCARTS) ---
173
- with tab3:
174
- st.subheader("Détail des écarts par collaborateur")
175
-
176
- # Sélection du candidat
177
- nom_selectionne = st.selectbox(
178
- "Choisir un collaborateur à analyser :",
179
- df['Nom'].tolist()
180
- )
181
- cand = next(c for c in collaborateurs if c['nom'] == nom_selectionne)
182
-
183
- col_left, col_right = st.columns(2)
184
-
185
- with col_left:
186
- st.markdown(f"### ✅ Compétences actuelles")
187
- for comp in cand['competences']:
188
- st.markdown(f"- **{comp['outil']}** \n *{comp['domaine']} > {comp['methode']}*")
189
-
190
- with col_right:
191
- st.markdown("### 🚩 Comparaison avec le besoin")
192
- for req in poste_data['requis']:
193
- match_outil = any(c['outil'].lower() == req['outil'].lower() for c in cand['competences'])
194
- match_methode = any(c['methode'].lower() == req['methode'].lower() for c in cand['competences'])
195
-
196
- if match_outil:
197
- st.success(f"**{req['outil']}** : Maîtrisé")
198
- elif match_methode:
199
- st.warning(f"**{req['outil']}** : Partiel (connaît la méthode '{req['methode']}')")
200
- else:
201
- st.error(f"**{req['outil']}** : Manquant")
202
-
203
- if __name__ == "__main__":
 
 
 
 
 
 
204
  main()
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import json
5
+ import numpy as np
6
+
7
+
8
+ METIERS_CIBLES = [
9
+ {
10
+ "titre": "Data Engineer",
11
+ "requis": [
12
+ {"domaine": "Data", "methode": "Architecture", "outil": "SQL"},
13
+ {"domaine": "Informatique", "methode": "Développement", "outil": "Python"},
14
+ {"domaine": "Informatique", "methode": "Cloud", "outil": "AWS"}
15
+ ]
16
+ },
17
+ {
18
+ "titre": "Responsable Transformation Digitale",
19
+ "requis": [
20
+ {"domaine": "Gestion", "methode": "Agilité", "outil": "Scrum"},
21
+ {"domaine": "Marketing", "methode": "Analyse", "outil": "Google Analytics"},
22
+ {"domaine": "Data", "methode": "Visualisation", "outil": "Power BI"}
23
+ ]
24
+ },
25
+ {
26
+ "titre": "Product Owner",
27
+ "requis": [
28
+ {"domaine": "Gestion", "methode": "Agilité", "outil": "Jira"},
29
+ {"domaine": "Gestion", "methode": "Planning", "outil": "Trello"},
30
+ {"domaine": "Marketing", "methode": "Stratégie Digitale", "outil": "Figma"}
31
+ ]
32
+ },
33
+ {
34
+ "titre": "Analyste Cybersécurité",
35
+ "requis": [
36
+ {"domaine": "Informatique", "methode": "Sécurité", "outil": "Firewalls"},
37
+ {"domaine": "Informatique", "methode": "Cloud", "outil": "Docker"},
38
+ {"domaine": "Informatique", "methode": "Scripting", "outil": "Bash"}
39
+ ]
40
+ }
41
+ ]
42
+
43
+ # --- 2. FONCTIONS DE CALCUL ---
44
+
45
+ def calculer_score(candidat, poste):
46
+ score_total = 0
47
+ max_score_possible = len(poste['requis']) * 3
48
+
49
+ for req in poste['requis']:
50
+ points_competence = 0
51
+ for comp in candidat['competences']:
52
+ if comp['outil'].lower() == req['outil'].lower():
53
+ points_competence = max(points_competence, 3)
54
+ elif comp['methode'].lower() == req['methode'].lower():
55
+ points_competence = max(points_competence, 2)
56
+ elif comp['domaine'].lower() == req['domaine'].lower():
57
+ points_competence = max(points_competence, 1)
58
+ score_total += points_competence
59
+ return (score_total / max_score_possible) * 100
60
+
61
+ # --- 3. INTERFACE PRINCIPALE ---
62
+
63
+ def main():
64
+ st.set_page_config(page_title="Skill Mapping MVP", layout="wide")
65
+
66
+ # --- VERROU DE SÉCURITÉ SÉCURISÉ ---
67
+ if "authenticated" not in st.session_state:
68
+ st.session_state["authenticated"] = False
69
+
70
+ if not st.session_state["authenticated"]:
71
+ st.title("🔐 Accès Restreint")
72
+ password_input = st.text_input("Veuillez saisir le code d'accès :", type="password")
73
+
74
+ if st.button("Se connecter"):
75
+ # On compare la saisie avec le secret caché sur Hugging Face
76
+ if password_input == st.secrets["MY_PASSWORD"]:
77
+ st.session_state["authenticated"] = True
78
+ st.rerun()
79
+ else:
80
+ st.error("Code incorrect.")
81
+ return
82
+
83
+ st.title("🎯 Mapping des talents Internes")
84
+
85
+ try:
86
+ with open('data.json', 'r', encoding='utf-8') as f:
87
+ data = json.load(f)
88
+ collaborateurs = data['collaborateurs']
89
+ except FileNotFoundError:
90
+ st.error("Fichier 'data.json' non trouvé.")
91
+ return
92
+
93
+ # --- SIDEBAR & SÉLECTION ---
94
+ st.sidebar.header("Paramètres")
95
+ titre_metier = st.sidebar.selectbox("Poste cible :", [m['titre'] for m in METIERS_CIBLES])
96
+ poste_data = next(m for m in METIERS_CIBLES if m['titre'] == titre_metier)
97
+
98
+ # Calcul des scores (pré-traitement)
99
+ resultats = []
100
+ for c in collaborateurs:
101
+ score = calculer_score(c, poste_data)
102
+ resultats.append({
103
+ "Nom": c['nom'],
104
+ "Métier Actuel": c['metier_actuel'],
105
+ "Score de Match": round(score, 1),
106
+ "data_brute": c
107
+ })
108
+ df = pd.DataFrame(resultats).sort_values(by="Score de Match", ascending=False)
109
+
110
+ # --- CRÉATION DES ONGLETS ---
111
+ tab1, tab2, tab3 = st.tabs(["📊 Classement", "🌌 Cartographie 2D", "🔍 Analyse Individuelle"])
112
+
113
+ # --- ONGLET 1 : CLASSEMENT ---
114
+ with tab1:
115
+ st.subheader(f"Adéquation des profils pour : {titre_metier}")
116
+ st.dataframe(
117
+ df[["Nom", "Métier Actuel", "Score de Match"]],
118
+ column_config={
119
+ "Score de Match": st.column_config.ProgressColumn(
120
+ "Adéquation",
121
+ format="%.1f%%",
122
+ min_value=0,
123
+ max_value=100
124
+ ),
125
+ },
126
+ hide_index=True,
127
+ use_container_width=True
128
+ )
129
+
130
+ # --- ONGLET 2 : MAPPING 2D ---
131
+ with tab2:
132
+ st.subheader("Positionnement par rapport au poste cible")
133
+
134
+ df_map = df.copy()
135
+ # Calcul des coordonnées
136
+ angles = np.linspace(0, 2 * np.pi, len(df_map), endpoint=False)
137
+ df_map['distance'] = 100 - df_map['Score de Match']
138
+ df_map['x'] = df_map['distance'] * np.cos(angles)
139
+ df_map['y'] = df_map['distance'] * np.sin(angles)
140
+
141
+ # Création du graphique de base
142
+ fig_2d = px.scatter(
143
+ df_map, x='x', y='y', text='Nom',
144
+ color='Score de Match',
145
+ color_continuous_scale="RdYlGn",
146
+ range_color=[0, 100],
147
+ template="plotly_white"
148
+ )
149
+
150
+ # Ajout manuel du point CIBLE au centre pour être sûr qu'il soit là
151
+ fig_2d.add_scatter(
152
+ x=[0], y=[0], mode='markers+text', text=['🎯 CIBLE'],
153
+ marker=dict(size=20, color='black'), name='Cible'
154
+ )
155
+
156
+ # Amélioration du texte et des marqueurs
157
+ fig_2d.update_traces(
158
+ textposition='top center',
159
+ marker=dict(size=12, line=dict(width=1, color='DarkSlateGrey'))
160
+ )
161
+
162
+ # Ajout des cercles de radar DERRIÈRE les points
163
+ for r in [25, 50, 75, 100]:
164
+ fig_2d.add_shape(
165
+ type="circle", x0=-r, y0=-r, x1=r, y1=r,
166
+ line=dict(color="lightgray", width=1, dash="dot"),
167
+ layer="below" # <--- IMPORTANT : Force les cercles en arrière-plan
168
+ )
169
+
170
+ fig_2d.update_layout(
171
+ showlegend=False, height=700,
172
+ xaxis=dict(visible=False, range=[-110, 110]),
173
+ yaxis=dict(visible=False, range=[-110, 110])
174
+ )
175
+
176
+ st.plotly_chart(fig_2d, use_container_width=True)
177
+
178
+ # --- ONGLET 3 : FOCUS CANDIDAT (ANALYSE DES ÉCARTS) ---
179
+ with tab3:
180
+ st.subheader("Détail des écarts par collaborateur")
181
+
182
+ # Sélection du candidat
183
+ nom_selectionne = st.selectbox(
184
+ "Choisir un collaborateur à analyser :",
185
+ df['Nom'].tolist()
186
+ )
187
+ cand = next(c for c in collaborateurs if c['nom'] == nom_selectionne)
188
+
189
+ col_left, col_right = st.columns(2)
190
+
191
+ with col_left:
192
+ st.markdown(f"### ✅ Compétences actuelles")
193
+ for comp in cand['competences']:
194
+ st.markdown(f"- **{comp['outil']}** \n *{comp['domaine']} > {comp['methode']}*")
195
+
196
+ with col_right:
197
+ st.markdown("### 🚩 Comparaison avec le besoin")
198
+ for req in poste_data['requis']:
199
+ match_outil = any(c['outil'].lower() == req['outil'].lower() for c in cand['competences'])
200
+ match_methode = any(c['methode'].lower() == req['methode'].lower() for c in cand['competences'])
201
+
202
+ if match_outil:
203
+ st.success(f"**{req['outil']}** : Maîtrisé")
204
+ elif match_methode:
205
+ st.warning(f"**{req['outil']}** : Partiel (connaît la méthode '{req['methode']}')")
206
+ else:
207
+ st.error(f"**{req['outil']}** : Manquant")
208
+
209
+ if __name__ == "__main__":
210
  main()