Spaces:
Running
Running
app file in src folder
Browse files- src/streamlit_app.py +141 -38
- streamlit_app.py +0 -143
src/streamlit_app.py
CHANGED
|
@@ -1,40 +1,143 @@
|
|
| 1 |
-
import altair as alt
|
| 2 |
-
import numpy as np
|
| 3 |
-
import pandas as pd
|
| 4 |
import streamlit as st
|
|
|
|
| 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 |
-
st.
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
import requests
|
| 3 |
|
| 4 |
+
API_URL = "https://kleb38-oc-p5.hf.space"
|
| 5 |
+
|
| 6 |
+
st.set_page_config(page_title="FUTURISYS — Prédiction RH", page_icon="👥", layout="wide")
|
| 7 |
+
st.title("👥 FUTURISYS — Prédiction de départ RH")
|
| 8 |
+
|
| 9 |
+
tab1, tab2 = st.tabs(["📝 Prédiction manuelle", "🔍 Recherche par ID"])
|
| 10 |
+
|
| 11 |
+
# ─── Fonctions communes ────────────────────────────────────────────────────────
|
| 12 |
+
|
| 13 |
+
def afficher_resultat(data):
|
| 14 |
+
prediction = data["statut_employe"]
|
| 15 |
+
score = data["probability_score"]
|
| 16 |
+
facteurs = data["top_5_factors"]
|
| 17 |
+
|
| 18 |
+
# Couleur selon le risque
|
| 19 |
+
if "HIGH" in prediction:
|
| 20 |
+
st.error(f"🚨 {prediction}")
|
| 21 |
+
else:
|
| 22 |
+
st.success(f"✅ {prediction}")
|
| 23 |
+
|
| 24 |
+
st.metric("Score de probabilité", f"{score * 100:.1f}%")
|
| 25 |
+
st.markdown(f"*Seuil stratégique : {data['model_threshold']} — {data['note']}*")
|
| 26 |
+
|
| 27 |
+
st.subheader("Top 5 facteurs SHAP")
|
| 28 |
+
for rang, (facteur, details) in enumerate(facteurs.items()):
|
| 29 |
+
interpretation = details["interpretation"]
|
| 30 |
+
valeur = details["feature_value"]
|
| 31 |
+
st.markdown(f"**{rang+1}. {facteur}** — {interpretation} *(valeur : {valeur})*")
|
| 32 |
+
|
| 33 |
+
# ─── Onglet 1 : Formulaire manuel ─────────────────────────────────────────────
|
| 34 |
+
|
| 35 |
+
with tab1:
|
| 36 |
+
st.header("Saisir les données d'un employé")
|
| 37 |
+
|
| 38 |
+
col1, col2, col3 = st.columns(3)
|
| 39 |
+
|
| 40 |
+
with col1:
|
| 41 |
+
st.subheader("Informations personnelles")
|
| 42 |
+
genre = st.selectbox("Genre", ["M", "F"])
|
| 43 |
+
age = st.number_input("Âge", min_value=18, max_value=65, value=35)
|
| 44 |
+
statut_marital = st.selectbox("Statut marital", ["Célibataire", "Marié(e)", "Divorcé(e)"])
|
| 45 |
+
niveau_education = st.number_input("Niveau d'éducation", min_value=1, max_value=5, value=3)
|
| 46 |
+
domaine_etude = st.selectbox("Domaine d'étude", [
|
| 47 |
+
"RH", "Marketing", "Infra & Cloud", "Transformation digitale", "Entrepreneuriat"
|
| 48 |
+
])
|
| 49 |
+
ayant_enfants = st.selectbox("Ayant des enfants", ["Oui", "Non"])
|
| 50 |
+
distance_domicile = st.number_input("Distance domicile-travail (km)", min_value=1, max_value=100, value=10)
|
| 51 |
+
frequence_deplacement = st.selectbox("Fréquence de déplacement", ["Jamais", "Occasionnel", "Fréquent"])
|
| 52 |
+
|
| 53 |
+
with col2:
|
| 54 |
+
st.subheader("Poste et expérience")
|
| 55 |
+
departement = st.selectbox("Département", ["Commercial", "Consulting", "RH"])
|
| 56 |
+
poste = st.selectbox("Poste", [
|
| 57 |
+
"Cadre Commercial", "Consultant", "Directeur RH",
|
| 58 |
+
"Responsable RH", "Représentant Commercial", "Responsable Commercial"
|
| 59 |
+
])
|
| 60 |
+
revenu_mensuel = st.number_input("Revenu mensuel (€)", min_value=1000, max_value=20000, value=5000)
|
| 61 |
+
heure_supplementaires = st.selectbox("Heures supplémentaires", ["Oui", "Non"])
|
| 62 |
+
nb_experiences = st.number_input("Nombre d'expériences précédentes", min_value=0, max_value=10, value=2)
|
| 63 |
+
annees_experience = st.number_input("Années d'expérience totale", min_value=0, max_value=40, value=10)
|
| 64 |
+
annees_entreprise = st.number_input("Années dans l'entreprise", min_value=0, max_value=40, value=5)
|
| 65 |
+
annees_poste = st.number_input("Années dans le poste actuel", min_value=0, max_value=20, value=3)
|
| 66 |
+
annees_promotion = st.number_input("Années depuis la dernière promotion", min_value=0, max_value=15, value=1)
|
| 67 |
+
annees_manager = st.number_input("Années sous responsable actuel", min_value=0, max_value=20, value=2)
|
| 68 |
+
nb_formations = st.number_input("Nombre de formations suivies", min_value=0, max_value=6, value=2)
|
| 69 |
+
augmentation = st.selectbox("Augmentation salaire précédente", [
|
| 70 |
+
"11%", "12%", "13%", "14%", "15%", "16%", "17%", "18%",
|
| 71 |
+
"19%", "20%", "21%", "22%", "23%", "24%", "25%"
|
| 72 |
+
])
|
| 73 |
+
|
| 74 |
+
with col3:
|
| 75 |
+
st.subheader("Satisfaction")
|
| 76 |
+
satisfaction_env = st.slider("Satisfaction environnement", 1, 4, 3)
|
| 77 |
+
satisfaction_travail = st.slider("Satisfaction nature du travail", 1, 4, 3)
|
| 78 |
+
satisfaction_equipe = st.slider("Satisfaction équipe", 1, 4, 3)
|
| 79 |
+
satisfaction_equilibre = st.slider("Satisfaction équilibre pro/perso", 1, 4, 3)
|
| 80 |
+
note_eval_prec = st.slider("Note évaluation précédente", 1, 4, 3)
|
| 81 |
+
note_eval_actuelle = st.slider("Note évaluation actuelle", 1, 4, 3)
|
| 82 |
+
niveau_hierarchique = st.number_input("Niveau hiérarchique", min_value=1, max_value=5, value=2)
|
| 83 |
+
nb_participation_pee = st.number_input("Participations PEE", min_value=0, max_value=6, value=3)
|
| 84 |
+
nb_employes_sous_resp = st.number_input("Employés sous responsabilité", min_value=0, max_value=20, value=0)
|
| 85 |
+
|
| 86 |
+
if st.button("🔮 Prédire le risque de départ", type="primary"):
|
| 87 |
+
payload = {
|
| 88 |
+
"Genre": genre,
|
| 89 |
+
"Statut Marital": statut_marital,
|
| 90 |
+
"Département": departement,
|
| 91 |
+
"Poste": poste,
|
| 92 |
+
"Domaine d'étude": domaine_etude,
|
| 93 |
+
"Fréquence de déplacement": frequence_deplacement,
|
| 94 |
+
"Heures supplémentaires": heure_supplementaires,
|
| 95 |
+
"Âge": age,
|
| 96 |
+
"Revenu mensuel": revenu_mensuel,
|
| 97 |
+
"Nombre d'expériences précédentes": nb_experiences,
|
| 98 |
+
"Années d'expérience totale": annees_experience,
|
| 99 |
+
"Années dans l'entreprise": annees_entreprise,
|
| 100 |
+
"Années dans le poste actuel": annees_poste,
|
| 101 |
+
"Nombre de formations suivies": nb_formations,
|
| 102 |
+
"Distance domicile-travail": distance_domicile,
|
| 103 |
+
"Niveau d'éducation": niveau_education,
|
| 104 |
+
"Années depuis la dernière promotion": annees_promotion,
|
| 105 |
+
"Années sous responsable actuel": annees_manager,
|
| 106 |
+
"Satisfaction environnement": satisfaction_env,
|
| 107 |
+
"Note évaluation précédente": note_eval_prec,
|
| 108 |
+
"Satisfaction nature du travail": satisfaction_travail,
|
| 109 |
+
"Satisfaction équipe": satisfaction_equipe,
|
| 110 |
+
"Satisfaction équilibre pro/perso": satisfaction_equilibre,
|
| 111 |
+
"Note évaluation actuelle": note_eval_actuelle,
|
| 112 |
+
"Augmentation salaire précédente": augmentation
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
with st.spinner("Prédiction en cours..."):
|
| 116 |
+
try:
|
| 117 |
+
response = requests.post(f"{API_URL}/predict", json=payload)
|
| 118 |
+
if response.status_code == 200:
|
| 119 |
+
afficher_resultat(response.json())
|
| 120 |
+
else:
|
| 121 |
+
st.error(f"Erreur API : {response.status_code} — {response.json()}")
|
| 122 |
+
except Exception as e:
|
| 123 |
+
st.error(f"Impossible de contacter l'API : {e}")
|
| 124 |
+
|
| 125 |
+
# ─── Onglet 2 : Recherche par ID ──────────────────────────────────────────────
|
| 126 |
+
|
| 127 |
+
with tab2:
|
| 128 |
+
st.header("Rechercher un employé par ID")
|
| 129 |
+
|
| 130 |
+
id_employee = st.number_input("ID Employé", min_value=1, value=1, step=1)
|
| 131 |
+
|
| 132 |
+
if st.button("🔍 Rechercher et prédire", type="primary"):
|
| 133 |
+
with st.spinner("Recherche en cours..."):
|
| 134 |
+
try:
|
| 135 |
+
response = requests.get(f"{API_URL}/predict/{id_employee}")
|
| 136 |
+
if response.status_code == 200:
|
| 137 |
+
afficher_resultat(response.json())
|
| 138 |
+
elif response.status_code == 404:
|
| 139 |
+
st.warning(f"⚠️ {response.json()['detail']}")
|
| 140 |
+
else:
|
| 141 |
+
st.error(f"Erreur API : {response.status_code}")
|
| 142 |
+
except Exception as e:
|
| 143 |
+
st.error(f"Impossible de contacter l'API : {e}")
|
streamlit_app.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
-
import requests
|
| 3 |
-
|
| 4 |
-
API_URL = "https://kleb38-oc-p5.hf.space"
|
| 5 |
-
|
| 6 |
-
st.set_page_config(page_title="FUTURISYS — Prédiction RH", page_icon="👥", layout="wide")
|
| 7 |
-
st.title("👥 FUTURISYS — Prédiction de départ RH")
|
| 8 |
-
|
| 9 |
-
tab1, tab2 = st.tabs(["📝 Prédiction manuelle", "🔍 Recherche par ID"])
|
| 10 |
-
|
| 11 |
-
# ─── Fonctions communes ────────────────────────────────────────────────────────
|
| 12 |
-
|
| 13 |
-
def afficher_resultat(data):
|
| 14 |
-
prediction = data["statut_employe"]
|
| 15 |
-
score = data["probability_score"]
|
| 16 |
-
facteurs = data["top_5_factors"]
|
| 17 |
-
|
| 18 |
-
# Couleur selon le risque
|
| 19 |
-
if "HIGH" in prediction:
|
| 20 |
-
st.error(f"🚨 {prediction}")
|
| 21 |
-
else:
|
| 22 |
-
st.success(f"✅ {prediction}")
|
| 23 |
-
|
| 24 |
-
st.metric("Score de probabilité", f"{score * 100:.1f}%")
|
| 25 |
-
st.markdown(f"*Seuil stratégique : {data['model_threshold']} — {data['note']}*")
|
| 26 |
-
|
| 27 |
-
st.subheader("Top 5 facteurs SHAP")
|
| 28 |
-
for rang, (facteur, details) in enumerate(facteurs.items()):
|
| 29 |
-
interpretation = details["interpretation"]
|
| 30 |
-
valeur = details["feature_value"]
|
| 31 |
-
st.markdown(f"**{rang+1}. {facteur}** — {interpretation} *(valeur : {valeur})*")
|
| 32 |
-
|
| 33 |
-
# ─── Onglet 1 : Formulaire manuel ─────────────────────────────────────────────
|
| 34 |
-
|
| 35 |
-
with tab1:
|
| 36 |
-
st.header("Saisir les données d'un employé")
|
| 37 |
-
|
| 38 |
-
col1, col2, col3 = st.columns(3)
|
| 39 |
-
|
| 40 |
-
with col1:
|
| 41 |
-
st.subheader("Informations personnelles")
|
| 42 |
-
genre = st.selectbox("Genre", ["M", "F"])
|
| 43 |
-
age = st.number_input("Âge", min_value=18, max_value=65, value=35)
|
| 44 |
-
statut_marital = st.selectbox("Statut marital", ["Célibataire", "Marié(e)", "Divorcé(e)"])
|
| 45 |
-
niveau_education = st.number_input("Niveau d'éducation", min_value=1, max_value=5, value=3)
|
| 46 |
-
domaine_etude = st.selectbox("Domaine d'étude", [
|
| 47 |
-
"RH", "Marketing", "Infra & Cloud", "Transformation digitale", "Entrepreneuriat"
|
| 48 |
-
])
|
| 49 |
-
ayant_enfants = st.selectbox("Ayant des enfants", ["Oui", "Non"])
|
| 50 |
-
distance_domicile = st.number_input("Distance domicile-travail (km)", min_value=1, max_value=100, value=10)
|
| 51 |
-
frequence_deplacement = st.selectbox("Fréquence de déplacement", ["Jamais", "Occasionnel", "Fréquent"])
|
| 52 |
-
|
| 53 |
-
with col2:
|
| 54 |
-
st.subheader("Poste et expérience")
|
| 55 |
-
departement = st.selectbox("Département", ["Commercial", "Consulting", "RH"])
|
| 56 |
-
poste = st.selectbox("Poste", [
|
| 57 |
-
"Cadre Commercial", "Consultant", "Directeur RH",
|
| 58 |
-
"Responsable RH", "Représentant Commercial", "Responsable Commercial"
|
| 59 |
-
])
|
| 60 |
-
revenu_mensuel = st.number_input("Revenu mensuel (€)", min_value=1000, max_value=20000, value=5000)
|
| 61 |
-
heure_supplementaires = st.selectbox("Heures supplémentaires", ["Oui", "Non"])
|
| 62 |
-
nb_experiences = st.number_input("Nombre d'expériences précédentes", min_value=0, max_value=10, value=2)
|
| 63 |
-
annees_experience = st.number_input("Années d'expérience totale", min_value=0, max_value=40, value=10)
|
| 64 |
-
annees_entreprise = st.number_input("Années dans l'entreprise", min_value=0, max_value=40, value=5)
|
| 65 |
-
annees_poste = st.number_input("Années dans le poste actuel", min_value=0, max_value=20, value=3)
|
| 66 |
-
annees_promotion = st.number_input("Années depuis la dernière promotion", min_value=0, max_value=15, value=1)
|
| 67 |
-
annees_manager = st.number_input("Années sous responsable actuel", min_value=0, max_value=20, value=2)
|
| 68 |
-
nb_formations = st.number_input("Nombre de formations suivies", min_value=0, max_value=6, value=2)
|
| 69 |
-
augmentation = st.selectbox("Augmentation salaire précédente", [
|
| 70 |
-
"11%", "12%", "13%", "14%", "15%", "16%", "17%", "18%",
|
| 71 |
-
"19%", "20%", "21%", "22%", "23%", "24%", "25%"
|
| 72 |
-
])
|
| 73 |
-
|
| 74 |
-
with col3:
|
| 75 |
-
st.subheader("Satisfaction")
|
| 76 |
-
satisfaction_env = st.slider("Satisfaction environnement", 1, 4, 3)
|
| 77 |
-
satisfaction_travail = st.slider("Satisfaction nature du travail", 1, 4, 3)
|
| 78 |
-
satisfaction_equipe = st.slider("Satisfaction équipe", 1, 4, 3)
|
| 79 |
-
satisfaction_equilibre = st.slider("Satisfaction équilibre pro/perso", 1, 4, 3)
|
| 80 |
-
note_eval_prec = st.slider("Note évaluation précédente", 1, 4, 3)
|
| 81 |
-
note_eval_actuelle = st.slider("Note évaluation actuelle", 1, 4, 3)
|
| 82 |
-
niveau_hierarchique = st.number_input("Niveau hiérarchique", min_value=1, max_value=5, value=2)
|
| 83 |
-
nb_participation_pee = st.number_input("Participations PEE", min_value=0, max_value=6, value=3)
|
| 84 |
-
nb_employes_sous_resp = st.number_input("Employés sous responsabilité", min_value=0, max_value=20, value=0)
|
| 85 |
-
|
| 86 |
-
if st.button("🔮 Prédire le risque de départ", type="primary"):
|
| 87 |
-
payload = {
|
| 88 |
-
"Genre": genre,
|
| 89 |
-
"Statut Marital": statut_marital,
|
| 90 |
-
"Département": departement,
|
| 91 |
-
"Poste": poste,
|
| 92 |
-
"Domaine d'étude": domaine_etude,
|
| 93 |
-
"Fréquence de déplacement": frequence_deplacement,
|
| 94 |
-
"Heures supplémentaires": heure_supplementaires,
|
| 95 |
-
"Âge": age,
|
| 96 |
-
"Revenu mensuel": revenu_mensuel,
|
| 97 |
-
"Nombre d'expériences précédentes": nb_experiences,
|
| 98 |
-
"Années d'expérience totale": annees_experience,
|
| 99 |
-
"Années dans l'entreprise": annees_entreprise,
|
| 100 |
-
"Années dans le poste actuel": annees_poste,
|
| 101 |
-
"Nombre de formations suivies": nb_formations,
|
| 102 |
-
"Distance domicile-travail": distance_domicile,
|
| 103 |
-
"Niveau d'éducation": niveau_education,
|
| 104 |
-
"Années depuis la dernière promotion": annees_promotion,
|
| 105 |
-
"Années sous responsable actuel": annees_manager,
|
| 106 |
-
"Satisfaction environnement": satisfaction_env,
|
| 107 |
-
"Note évaluation précédente": note_eval_prec,
|
| 108 |
-
"Satisfaction nature du travail": satisfaction_travail,
|
| 109 |
-
"Satisfaction équipe": satisfaction_equipe,
|
| 110 |
-
"Satisfaction équilibre pro/perso": satisfaction_equilibre,
|
| 111 |
-
"Note évaluation actuelle": note_eval_actuelle,
|
| 112 |
-
"Augmentation salaire précédente": augmentation
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
with st.spinner("Prédiction en cours..."):
|
| 116 |
-
try:
|
| 117 |
-
response = requests.post(f"{API_URL}/predict", json=payload)
|
| 118 |
-
if response.status_code == 200:
|
| 119 |
-
afficher_resultat(response.json())
|
| 120 |
-
else:
|
| 121 |
-
st.error(f"Erreur API : {response.status_code} — {response.json()}")
|
| 122 |
-
except Exception as e:
|
| 123 |
-
st.error(f"Impossible de contacter l'API : {e}")
|
| 124 |
-
|
| 125 |
-
# ─── Onglet 2 : Recherche par ID ──────────────────────────────────────────────
|
| 126 |
-
|
| 127 |
-
with tab2:
|
| 128 |
-
st.header("Rechercher un employé par ID")
|
| 129 |
-
|
| 130 |
-
id_employee = st.number_input("ID Employé", min_value=1, value=1, step=1)
|
| 131 |
-
|
| 132 |
-
if st.button("🔍 Rechercher et prédire", type="primary"):
|
| 133 |
-
with st.spinner("Recherche en cours..."):
|
| 134 |
-
try:
|
| 135 |
-
response = requests.get(f"{API_URL}/predict/{id_employee}")
|
| 136 |
-
if response.status_code == 200:
|
| 137 |
-
afficher_resultat(response.json())
|
| 138 |
-
elif response.status_code == 404:
|
| 139 |
-
st.warning(f"⚠️ {response.json()['detail']}")
|
| 140 |
-
else:
|
| 141 |
-
st.error(f"Erreur API : {response.status_code}")
|
| 142 |
-
except Exception as e:
|
| 143 |
-
st.error(f"Impossible de contacter l'API : {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|