Modification internationalisation
This commit is contained in:
parent
99ae3123e9
commit
f4a28db6f7
@ -31,11 +31,11 @@ def preparer_graphe(G):
|
|||||||
|
|
||||||
def selectionner_niveaux():
|
def selectionner_niveaux():
|
||||||
"""Interface pour sélectionner les niveaux de départ et d'arrivée."""
|
"""Interface pour sélectionner les niveaux de départ et d'arrivée."""
|
||||||
st.markdown(f"## {str(_('pages.analyse.selection_nodes', 'Sélection des nœuds de départ et d\'arrivée'))}")
|
st.markdown(f"## {str(_('pages.analyse.selection_nodes'))}")
|
||||||
valeur_defaut = str(_("pages.analyse.select_level", "-- Sélectionner un niveau --"))
|
valeur_defaut = str(_("pages.analyse.select_level"))
|
||||||
niveau_choix = [valeur_defaut] + list(niveau_labels.values())
|
niveau_choix = [valeur_defaut] + list(niveau_labels.values())
|
||||||
|
|
||||||
niveau_depart = st.selectbox(str(_("pages.analyse.start_level", "Niveau de départ")), niveau_choix, key="analyse_niveau_depart")
|
niveau_depart = st.selectbox(str(_("pages.analyse.start_level")), niveau_choix, key="analyse_niveau_depart")
|
||||||
if niveau_depart == valeur_defaut:
|
if niveau_depart == valeur_defaut:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ def selectionner_niveaux():
|
|||||||
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
|
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
|
||||||
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
|
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
|
||||||
|
|
||||||
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level", "Niveau d'arrivée")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
||||||
if analyse_niveau_arrivee == valeur_defaut:
|
if analyse_niveau_arrivee == valeur_defaut:
|
||||||
return niveau_depart_int, None
|
return niveau_depart_int, None
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
|||||||
"""Interface pour sélectionner les minerais si nécessaire."""
|
"""Interface pour sélectionner les minerais si nécessaire."""
|
||||||
minerais_selection = None
|
minerais_selection = None
|
||||||
if niveau_depart < 2 < niveau_arrivee:
|
if niveau_depart < 2 < niveau_arrivee:
|
||||||
st.markdown(f"### {str(_('pages.analyse.select_minerals', 'Sélectionner un ou plusieurs minerais'))}")
|
st.markdown(f"### {str(_('pages.analyse.select_minerals'))}")
|
||||||
# Tous les nœuds de niveau 2 (minerai)
|
# Tous les nœuds de niveau 2 (minerai)
|
||||||
minerais_nodes = sorted([
|
minerais_nodes = sorted([
|
||||||
n for n, d in G.nodes(data=True)
|
n for n, d in G.nodes(data=True)
|
||||||
@ -63,7 +63,7 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
|||||||
])
|
])
|
||||||
|
|
||||||
minerais_selection = st.multiselect(
|
minerais_selection = st.multiselect(
|
||||||
str(_("pages.analyse.filter_by_minerals", "Filtrer par minerais (optionnel)")),
|
str(_("pages.analyse.filter_by_minerals")),
|
||||||
minerais_nodes,
|
minerais_nodes,
|
||||||
key="analyse_minerais"
|
key="analyse_minerais"
|
||||||
)
|
)
|
||||||
@ -74,15 +74,15 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
|||||||
def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
||||||
"""Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée."""
|
"""Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée."""
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown(f"## {str(_('pages.analyse.fine_selection', 'Sélection fine des items'))}")
|
st.markdown(f"## {str(_('pages.analyse.fine_selection'))}")
|
||||||
|
|
||||||
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
|
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
|
||||||
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
|
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
|
||||||
|
|
||||||
noeuds_depart = st.multiselect(str(_("pages.analyse.filter_start_nodes", "Filtrer par noeuds de départ (optionnel)")),
|
noeuds_depart = st.multiselect(str(_("pages.analyse.filter_start_nodes")),
|
||||||
sorted(depart_nodes),
|
sorted(depart_nodes),
|
||||||
key="analyse_noeuds_depart")
|
key="analyse_noeuds_depart")
|
||||||
noeuds_arrivee = st.multiselect(str(_("pages.analyse.filter_end_nodes", "Filtrer par noeuds d'arrivée (optionnel)")),
|
noeuds_arrivee = st.multiselect(str(_("pages.analyse.filter_end_nodes")),
|
||||||
sorted(arrivee_nodes),
|
sorted(arrivee_nodes),
|
||||||
key="analyse_noeuds_arrivee")
|
key="analyse_noeuds_arrivee")
|
||||||
|
|
||||||
@ -95,26 +95,26 @@ def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
|||||||
def configurer_filtres_vulnerabilite():
|
def configurer_filtres_vulnerabilite():
|
||||||
"""Interface pour configurer les filtres de vulnérabilité."""
|
"""Interface pour configurer les filtres de vulnérabilité."""
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown(f"## {str(_('pages.analyse.vulnerability_filters', 'Sélection des filtres pour identifier les vulnérabilités'))}")
|
st.markdown(f"## {str(_('pages.analyse.vulnerability_filters'))}")
|
||||||
|
|
||||||
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics", "Filtrer les chemins contenant au moins minerai critique pour un composant (ICS > 66 %)")),
|
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics")),
|
||||||
key="analyse_filtrer_ics")
|
key="analyse_filtrer_ics")
|
||||||
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc", "Filtrer les chemins contenant au moins un minerai critique par rapport à la concurrence sectorielle (IVC > 30)")),
|
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc")),
|
||||||
key="analyse_filtrer_ivc")
|
key="analyse_filtrer_ivc")
|
||||||
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh", "Filtrer les chemins contenant au moins une opération critique par rapport à la concentration géographique ou industrielle (IHH pays ou acteurs > 25)")),
|
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh")),
|
||||||
key="analyse_filtrer_ihh")
|
key="analyse_filtrer_ihh")
|
||||||
|
|
||||||
ihh_type = "Pays"
|
ihh_type = "Pays"
|
||||||
if filtrer_ihh:
|
if filtrer_ihh:
|
||||||
ihh_type = st.radio(str(_("pages.analyse.apply_ihh_filter", "Appliquer le filtre IHH sur :")),
|
ihh_type = st.radio(str(_("pages.analyse.apply_ihh_filter")),
|
||||||
[str(_("pages.analyse.countries", "Pays")), str(_("pages.analyse.actors", "Acteurs"))],
|
[str(_("pages.analyse.countries")), str(_("pages.analyse.actors"))],
|
||||||
horizontal=True,
|
horizontal=True,
|
||||||
key="analyse_ihh_type")
|
key="analyse_ihh_type")
|
||||||
|
|
||||||
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg", "Filtrer les chemins contenant un pays instable (ISG ≥ 60)")),
|
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg")),
|
||||||
key="analyse_filtrer_isg")
|
key="analyse_filtrer_isg")
|
||||||
logique_filtrage = st.radio(str(_("pages.analyse.filter_logic", "Logique de filtrage")),
|
logique_filtrage = st.radio(str(_("pages.analyse.filter_logic")),
|
||||||
[str(_("pages.analyse.or", "OU")), str(_("pages.analyse.and", "ET"))],
|
[str(_("pages.analyse.or")), str(_("pages.analyse.and"))],
|
||||||
horizontal=True,
|
horizontal=True,
|
||||||
key="analyse_logique_filtrage")
|
key="analyse_logique_filtrage")
|
||||||
|
|
||||||
@ -122,16 +122,9 @@ def configurer_filtres_vulnerabilite():
|
|||||||
|
|
||||||
|
|
||||||
def interface_analyse(G_temp):
|
def interface_analyse(G_temp):
|
||||||
st.markdown(f"# {str(_('pages.analyse.title', 'Analyse du graphe'))}")
|
st.markdown(f"# {str(_('pages.analyse.title'))}")
|
||||||
with st.expander(str(_("pages.analyse.help", "Comment utiliser cet onglet ?")), expanded=False):
|
with st.expander(str(_("pages.analyse.help")), expanded=False):
|
||||||
st.markdown("\n".join(_("pages.analyse.help_content", [
|
st.markdown("\n".join(_("pages.analyse.help_content")))
|
||||||
"1. Sélectionnez le niveau de départ (produit final, composant ou minerai)",
|
|
||||||
"2. Choisissez le niveau d'arrivée souhaité",
|
|
||||||
"3. Affinez votre sélection en spécifiant soit un ou des minerais à cibler spécifiquement ou des items précis à chaque niveau (optionnel)",
|
|
||||||
"4. Définissez les critères d'analyse en sélectionnant les indices de vulnérabilité pertinents",
|
|
||||||
"5. Choisissez le mode de combinaison des indices (ET/OU) selon votre besoin d'analyse",
|
|
||||||
"6. Explorez le graphique généré en utilisant les contrôles de zoom et de déplacement ; vous pouvez basculer en mode plein écran pour le graphe"
|
|
||||||
])))
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -155,7 +148,7 @@ def interface_analyse(G_temp):
|
|||||||
|
|
||||||
# Lancement de l'analyse
|
# Lancement de l'analyse
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
if st.button(str(_("pages.analyse.run_analysis", "Lancer l'analyse")), type="primary", key="analyse_lancer"):
|
if st.button(str(_("pages.analyse.run_analysis")), type="primary", key="analyse_lancer"):
|
||||||
afficher_sankey(
|
afficher_sankey(
|
||||||
G_temp,
|
G_temp,
|
||||||
niveau_depart=niveau_depart,
|
niveau_depart=niveau_depart,
|
||||||
@ -172,4 +165,4 @@ def interface_analyse(G_temp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.graph_preview_error', 'Erreur de prévisualisation du graphe :'))} {e}")
|
st.error(f"{str(_('errors.graph_preview_error'))} {e}")
|
||||||
|
|||||||
@ -187,16 +187,22 @@ def couleur_criticite(p):
|
|||||||
|
|
||||||
def edge_info(G, u, v):
|
def edge_info(G, u, v):
|
||||||
"""Génère l'info-bulle pour un lien"""
|
"""Génère l'info-bulle pour un lien"""
|
||||||
|
# Liste d'attributs à exclure des infobulles des liens
|
||||||
|
attributs_exclus = ["poids", "color", "fontcolor"]
|
||||||
|
|
||||||
data = G.get_edge_data(u, v)
|
data = G.get_edge_data(u, v)
|
||||||
if not data:
|
if not data:
|
||||||
return f"{str(_('pages.analyse.sankey.relation', 'Relation'))} : {u} → {v}"
|
return f"{str(_('pages.analyse.sankey.relation'))} : {u} → {v}"
|
||||||
if isinstance(data, dict) and all(isinstance(k, int) for k in data):
|
if isinstance(data, dict) and all(isinstance(k, int) for k in data):
|
||||||
data = data[0]
|
data = data[0]
|
||||||
base = [f"{k}: {v}" for k, v in data.items()]
|
base = [f"{k}: {v}" for k, v in data.items() if k not in attributs_exclus]
|
||||||
return f"{str(_('pages.analyse.sankey.relation', 'Relation'))} : {u} → {v}<br>" + "<br>".join(base)
|
return f"{str(_('pages.analyse.sankey.relation'))} : {u} → {v}<br>" + "<br>".join(base)
|
||||||
|
|
||||||
def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
||||||
"""Prépare les données pour le graphique Sankey"""
|
"""Prépare les données pour le graphique Sankey"""
|
||||||
|
# Liste d'attributs à exclure des infobulles des nœuds
|
||||||
|
node_attributs_exclus = ["fillcolor", "niveau"]
|
||||||
|
|
||||||
df_liens = pd.DataFrame(list(liens_chemins), columns=["source", "target"])
|
df_liens = pd.DataFrame(list(liens_chemins), columns=["source", "target"])
|
||||||
df_liens = df_liens.groupby(["source", "target"]).size().reset_index(name="value")
|
df_liens = df_liens.groupby(["source", "target"]).size().reset_index(name="value")
|
||||||
|
|
||||||
@ -215,7 +221,7 @@ def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
|||||||
noeuds_utilises.add(n)
|
noeuds_utilises.add(n)
|
||||||
|
|
||||||
df_liens["color"] = df_liens.apply(
|
df_liens["color"] = df_liens.apply(
|
||||||
lambda row: couleur_criticite(row["criticite"]) if row["criticite"] > 0 else "gray",
|
lambda row: couleur_criticite(row["criticite"]) if row["criticite"] > 0 else "white",
|
||||||
axis=1
|
axis=1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -226,7 +232,7 @@ def preparer_donnees_sankey(G, liens_chemins, niveaux, chemins):
|
|||||||
|
|
||||||
customdata = []
|
customdata = []
|
||||||
for n in sorted_nodes:
|
for n in sorted_nodes:
|
||||||
info = [f"{k}: {v}" for k, v in G.nodes[n].items()]
|
info = [f"{k}: {v}" for k, v in G.nodes[n].items() if k not in node_attributs_exclus]
|
||||||
niveau = niveaux.get(n, 99)
|
niveau = niveaux.get(n, 99)
|
||||||
|
|
||||||
# Ajout d'un ISG hérité si applicable
|
# Ajout d'un ISG hérité si applicable
|
||||||
@ -268,12 +274,17 @@ def creer_graphique_sankey(G, niveaux, df_liens, sorted_nodes, customdata, link_
|
|||||||
value=values,
|
value=values,
|
||||||
color=df_liens["color"].tolist(),
|
color=df_liens["color"].tolist(),
|
||||||
customdata=link_customdata,
|
customdata=link_customdata,
|
||||||
hovertemplate="%{customdata}<extra></extra>"
|
hovertemplate="%{customdata}<extra></extra>",
|
||||||
|
line=dict(
|
||||||
|
width=1, # Set fixed width to 3 pixels (or use 2 if preferred)
|
||||||
|
color="grey"
|
||||||
|
),
|
||||||
|
arrowlen=10
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
fig.update_layout(
|
fig.update_layout(
|
||||||
title_text=str(_("pages.analyse.sankey.filtered_hierarchy", "Hiérarchie filtrée par niveaux et noeuds")),
|
title_text=str(_("pages.analyse.sankey.filtered_hierarchy")),
|
||||||
paper_bgcolor="white",
|
paper_bgcolor="white",
|
||||||
plot_bgcolor="white"
|
plot_bgcolor="white"
|
||||||
)
|
)
|
||||||
@ -303,7 +314,7 @@ def exporter_graphe_filtre(G, liens_chemins):
|
|||||||
|
|
||||||
with open(dot_path, encoding="utf-8") as f:
|
with open(dot_path, encoding="utf-8") as f:
|
||||||
st.download_button(
|
st.download_button(
|
||||||
label=str(_("pages.analyse.sankey.download_dot", "Télécharger le fichier DOT filtré")),
|
label=str(_("pages.analyse.sankey.download_dot")),
|
||||||
data=f.read(),
|
data=f.read(),
|
||||||
file_name="graphe_filtré.dot",
|
file_name="graphe_filtré.dot",
|
||||||
mime="text/plain"
|
mime="text/plain"
|
||||||
@ -325,7 +336,7 @@ def afficher_sankey(
|
|||||||
chemins = extraire_chemins_selon_criteres(G, niveaux, niveau_depart, noeuds_depart, noeuds_arrivee, minerais)
|
chemins = extraire_chemins_selon_criteres(G, niveaux, niveau_depart, noeuds_depart, noeuds_arrivee, minerais)
|
||||||
|
|
||||||
if not chemins:
|
if not chemins:
|
||||||
st.warning(str(_("pages.analyse.sankey.no_paths", "Aucun chemin trouvé pour les critères spécifiés.")))
|
st.warning(str(_("pages.analyse.sankey.no_paths")))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Étape 3 : Filtrage des chemins selon les critères de vulnérabilité
|
# Étape 3 : Filtrage des chemins selon les critères de vulnérabilité
|
||||||
@ -335,12 +346,13 @@ def afficher_sankey(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not liens_chemins:
|
if not liens_chemins:
|
||||||
st.warning(str(_("pages.analyse.sankey.no_matching_paths", "Aucun chemin ne correspond aux critères.")))
|
st.warning(str(_("pages.analyse.sankey.no_matching_paths")))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Étape 4 : Préparation des données pour le graphique Sankey
|
# Étape 4 : Préparation des données pour le graphique Sankey
|
||||||
df_liens, sorted_nodes, customdata, link_customdata, node_indices = preparer_donnees_sankey(
|
df_liens, sorted_nodes, customdata, link_customdata, node_indices = preparer_donnees_sankey(
|
||||||
G, liens_chemins, niveaux, chemins_filtres if any([filtrer_ics, filtrer_ivc, filtrer_ihh, filtrer_isg]) else chemins
|
G, liens_chemins, niveaux,
|
||||||
|
chemins_filtres if any([filtrer_ics, filtrer_ivc, filtrer_ihh, filtrer_isg]) else chemins
|
||||||
)
|
)
|
||||||
|
|
||||||
# Étape 5 : Création et affichage du graphique Sankey
|
# Étape 5 : Création et affichage du graphique Sankey
|
||||||
|
|||||||
@ -18,23 +18,9 @@ from .utils.fiche_utils import load_seuils, doit_regenerer_fiche
|
|||||||
from .generer import generer_fiche
|
from .generer import generer_fiche
|
||||||
|
|
||||||
def interface_fiches():
|
def interface_fiches():
|
||||||
st.markdown(f"# {str(_('pages.fiches.title', 'Découverte des fiches'))}")
|
st.markdown(f"# {str(_('pages.fiches.title'))}")
|
||||||
with st.expander(str(_("pages.fiches.help", "Comment utiliser cet onglet ?")), expanded=False):
|
with st.expander(str(_("pages.fiches.help")), expanded=False):
|
||||||
st.markdown("\n".join([
|
st.markdown("\n".join(_("pages.fiches.help_content")))
|
||||||
" " + line for line in _("pages.fiches.help_content", [
|
|
||||||
"1. Parcourez la liste des fiches disponibles par catégorie",
|
|
||||||
"2. Sélectionnez une fiche pour afficher son contenu complet",
|
|
||||||
"3. Consultez les données détaillées, graphiques et analyses supplémentaires",
|
|
||||||
"4. Utilisez ces informations pour approfondir votre compréhension des vulnérabilités identifiées",
|
|
||||||
"",
|
|
||||||
"Les catégories sont les suivantes :",
|
|
||||||
"* Assemblage : opération d'assemblage des produits finaux à partir des composants",
|
|
||||||
"* Connexe : opérations diverses nécessaires pour fabriquer le numérique, mais n'entrant pas directement dans sa composition",
|
|
||||||
"* Criticités : indices utilisés pour identifier et évaluer les vulnérabilités",
|
|
||||||
"* Fabrication : opération de fabrication des composants à partir de minerais",
|
|
||||||
"* Minerai : description et opérations d'extraction et de traitement des minerais"
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
if "fiches_arbo" not in st.session_state:
|
if "fiches_arbo" not in st.session_state:
|
||||||
@ -42,24 +28,24 @@ def interface_fiches():
|
|||||||
|
|
||||||
arbo = st.session_state.get("fiches_arbo", {})
|
arbo = st.session_state.get("fiches_arbo", {})
|
||||||
if not arbo:
|
if not arbo:
|
||||||
st.warning(str(_("pages.fiches.no_files", "Aucune fiche disponible pour le moment.")))
|
st.warning(str(_("pages.fiches.no_files")))
|
||||||
return
|
return
|
||||||
|
|
||||||
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
||||||
dossier_choisi = st.selectbox(
|
dossier_choisi = st.selectbox(
|
||||||
str(_("pages.fiches.choose_category", "Choisissez une catégorie de fiches")),
|
str(_("pages.fiches.choose_category",)),
|
||||||
[str(_("pages.fiches.select_folder", "-- Sélectionner un dossier --"))] + dossiers
|
[str(_("pages.fiches.select_folder"))] + dossiers
|
||||||
)
|
)
|
||||||
|
|
||||||
if dossier_choisi and dossier_choisi != str(_("pages.fiches.select_folder", "-- Sélectionner un dossier --")):
|
if dossier_choisi and dossier_choisi != str(_("pages.fiches.select_folder")):
|
||||||
fiches = arbo.get(dossier_choisi, [])
|
fiches = arbo.get(dossier_choisi, [])
|
||||||
noms_fiches = [f['nom'] for f in fiches]
|
noms_fiches = [f['nom'] for f in fiches]
|
||||||
fiche_choisie = st.selectbox(
|
fiche_choisie = st.selectbox(
|
||||||
str(_("pages.fiches.choose_file", "Choisissez une fiche")),
|
str(_("pages.fiches.choose_file")),
|
||||||
[str(_("pages.fiches.select_file", "-- Sélectionner une fiche --"))] + noms_fiches
|
[str(_("pages.fiches.select_file"))] + noms_fiches
|
||||||
)
|
)
|
||||||
|
|
||||||
if fiche_choisie and fiche_choisie != str(_("pages.fiches.select_file", "-- Sélectionner une fiche --")):
|
if fiche_choisie and fiche_choisie != str(_("pages.fiches.select_file")):
|
||||||
fiche_info = next((f for f in fiches if f["nom"] == fiche_choisie), None)
|
fiche_info = next((f for f in fiches if f["nom"] == fiche_choisie), None)
|
||||||
if fiche_info:
|
if fiche_info:
|
||||||
try:
|
try:
|
||||||
@ -101,17 +87,17 @@ def interface_fiches():
|
|||||||
if os.path.exists(pdf_path):
|
if os.path.exists(pdf_path):
|
||||||
with open(pdf_path, "rb") as pdf_file:
|
with open(pdf_path, "rb") as pdf_file:
|
||||||
st.download_button(
|
st.download_button(
|
||||||
label=str(_("pages.fiches.download_pdf", "Télécharger cette fiche en PDF")),
|
label=str(_("pages.fiches.download_pdf")),
|
||||||
data=pdf_file,
|
data=pdf_file,
|
||||||
file_name=pdf_name,
|
file_name=pdf_name,
|
||||||
mime="application/pdf",
|
mime="application/pdf",
|
||||||
help=str(_("pages.fiches.download_pdf", "Télécharger cette fiche en PDF")),
|
help=str(_("pages.fiches.download_pdf")),
|
||||||
key="telecharger_fiche_pdf"
|
key="telecharger_fiche_pdf"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
st.warning(str(_("pages.fiches.pdf_unavailable", "Le fichier PDF de cette fiche n'est pas disponible.")))
|
st.warning(str(_("pages.fiches.pdf_unavailable")))
|
||||||
|
|
||||||
st.markdown(f"## {str(_('pages.fiches.ticket_management', 'Gestion des tickets pour cette fiche'))}")
|
st.markdown(f"## {str(_('pages.fiches.ticket_management'))}")
|
||||||
afficher_tickets_par_fiche(rechercher_tickets_gitea(fiche_choisie))
|
afficher_tickets_par_fiche(rechercher_tickets_gitea(fiche_choisie))
|
||||||
formulaire_creation_ticket_dynamique(fiche_choisie)
|
formulaire_creation_ticket_dynamique(fiche_choisie)
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ def gitea_request(method, url, **kwargs):
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response
|
return response
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
st.error(f"{str(_('errors.gitea_error', 'Erreur Gitea'))} ({method.upper()}): {e}")
|
st.error(f"{str(_('errors.gitea_error'))} ({method.upper()}): {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -39,9 +39,9 @@ def charger_fiches_et_labels():
|
|||||||
"item": item.strip()
|
"item": item.strip()
|
||||||
}
|
}
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
st.error(f"❌ {str(_('errors.file_not_found', 'Le fichier'))} {chemin_csv} {str(_('errors.is_missing', 'est introuvable.'))}")
|
st.error(f"❌ {str(_('errors.file_not_found'))} {chemin_csv} {str(_('errors.is_missing'))}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"❌ {str(_('errors.file_loading', 'Erreur lors du chargement des fiches :'))} {str(e)}")
|
st.error(f"❌ {str(_('errors.file_loading'))} {str(e)}")
|
||||||
|
|
||||||
return dictionnaire_fiches
|
return dictionnaire_fiches
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ def rechercher_tickets_gitea(fiche_selectionnee):
|
|||||||
try:
|
try:
|
||||||
issues = reponse.json()
|
issues = reponse.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.json_decode', 'Erreur de décodage JSON :'))} {e}")
|
st.error(f"{str(_('errors.json_decode'))} {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
correspondances = charger_fiches_et_labels()
|
correspondances = charger_fiches_et_labels()
|
||||||
@ -87,7 +87,7 @@ def get_labels_existants():
|
|||||||
try:
|
try:
|
||||||
return {label['name']: label['id'] for label in reponse.json()}
|
return {label['name']: label['id'] for label in reponse.json()}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.label_parsing', 'Erreur de parsing des labels :'))} {e}")
|
st.error(f"{str(_('errors.label_parsing'))} {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@ -114,6 +114,6 @@ def creer_ticket_gitea(titre, corps, labels):
|
|||||||
|
|
||||||
issue_url = reponse.json().get("html_url", "")
|
issue_url = reponse.json().get("html_url", "")
|
||||||
if issue_url:
|
if issue_url:
|
||||||
st.success(f"{str(_('pages.fiches.tickets.created_success', 'Ticket créé !'))} [Voir le ticket]({issue_url})")
|
st.success(f"{str(_('pages.fiches.tickets.created_success'))} [Voir le ticket]({issue_url})")
|
||||||
else:
|
else:
|
||||||
st.success(str(_('pages.fiches.tickets.created', 'Ticket créé avec succès.')))
|
st.success(str(_('pages.fiches.tickets.created')))
|
||||||
|
|||||||
@ -39,7 +39,7 @@ def generer_labels(fiche_selectionnee):
|
|||||||
if len(cible["operations"]) == 1:
|
if len(cible["operations"]) == 1:
|
||||||
labels.append(cible["operations"][0])
|
labels.append(cible["operations"][0])
|
||||||
elif len(cible["operations"]) > 1:
|
elif len(cible["operations"]) > 1:
|
||||||
selected_ops = st.multiselect(str(_("pages.fiches.tickets.contribution_type", "Labels opération à associer")),
|
selected_ops = st.multiselect(str(_("pages.fiches.tickets.contribution_type")),
|
||||||
cible["operations"],
|
cible["operations"],
|
||||||
default=cible["operations"])
|
default=cible["operations"])
|
||||||
|
|
||||||
@ -53,14 +53,14 @@ def creer_champs_formulaire(sections, fiche_selectionnee):
|
|||||||
for section, aide in sections.items():
|
for section, aide in sections.items():
|
||||||
if "Type de contribution" in section:
|
if "Type de contribution" in section:
|
||||||
options = sorted(set(re.findall(r"- \[.\] (.+)", aide)))
|
options = sorted(set(re.findall(r"- \[.\] (.+)", aide)))
|
||||||
if str(_("pages.fiches.tickets.other", "Autre")) not in options:
|
if str(_("pages.fiches.tickets.other")) not in options:
|
||||||
options.append(str(_("pages.fiches.tickets.other", "Autre")))
|
options.append(str(_("pages.fiches.tickets.other")))
|
||||||
choix = st.radio(str(_("pages.fiches.tickets.contribution_type", "Type de contribution")), options)
|
choix = st.radio(str(_("pages.fiches.tickets.contribution_type")), options)
|
||||||
reponses[section] = st.text_input(str(_("pages.fiches.tickets.specify", "Précisez")), "") if choix == str(_("pages.fiches.tickets.other", "Autre")) else choix
|
reponses[section] = st.text_input(str(_("pages.fiches.tickets.specify")), "") if choix == str(_("pages.fiches.tickets.other")) else choix
|
||||||
elif "Fiche concernée" in section:
|
elif "Fiche concernée" in section:
|
||||||
url_fiche = f"https://fabnum-git.peccini.fr/FabNum/Fiches/src/branch/{ENV}/Documents/{fiche_selectionnee.replace(' ', '%20')}"
|
url_fiche = f"https://fabnum-git.peccini.fr/FabNum/Fiches/src/branch/{ENV}/Documents/{fiche_selectionnee.replace(' ', '%20')}"
|
||||||
reponses[section] = url_fiche
|
reponses[section] = url_fiche
|
||||||
st.text_input(str(_("pages.fiches.tickets.concerned_card", "Fiche concernée")), value=url_fiche, disabled=True)
|
st.text_input(str(_("pages.fiches.tickets.concerned_card")), value=url_fiche, disabled=True)
|
||||||
elif "Sujet de la proposition" in section:
|
elif "Sujet de la proposition" in section:
|
||||||
reponses[section] = st.text_input(section, help=aide)
|
reponses[section] = st.text_input(section, help=aide)
|
||||||
else:
|
else:
|
||||||
@ -72,9 +72,9 @@ def creer_champs_formulaire(sections, fiche_selectionnee):
|
|||||||
def afficher_controles_formulaire():
|
def afficher_controles_formulaire():
|
||||||
"""Affiche les boutons de contrôle du formulaire."""
|
"""Affiche les boutons de contrôle du formulaire."""
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
if col1.button(str(_("pages.fiches.tickets.preview", "Prévisualiser le ticket"))):
|
if col1.button(str(_("pages.fiches.tickets.preview"))):
|
||||||
st.session_state.previsualiser = True
|
st.session_state.previsualiser = True
|
||||||
if col2.button(str(_("pages.fiches.tickets.cancel", "Annuler"))):
|
if col2.button(str(_("pages.fiches.tickets.cancel"))):
|
||||||
st.session_state.previsualiser = False
|
st.session_state.previsualiser = False
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
|||||||
if not st.session_state.get("previsualiser", False):
|
if not st.session_state.get("previsualiser", False):
|
||||||
return
|
return
|
||||||
|
|
||||||
st.subheader(str(_("pages.fiches.tickets.preview_title", "Prévisualisation du ticket")))
|
st.subheader(str(_("pages.fiches.tickets.preview_title")))
|
||||||
for section, texte in reponses.items():
|
for section, texte in reponses.items():
|
||||||
st.markdown(f"#### {section}")
|
st.markdown(f"#### {section}")
|
||||||
st.code(texte, language="markdown")
|
st.code(texte, language="markdown")
|
||||||
@ -92,9 +92,9 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
|||||||
titre_ticket = reponses.get("Sujet de la proposition", "").strip() or "Ticket FabNum"
|
titre_ticket = reponses.get("Sujet de la proposition", "").strip() or "Ticket FabNum"
|
||||||
final_labels = nettoyer_labels(labels + selected_ops + ([cible["item"]] if cible else []))
|
final_labels = nettoyer_labels(labels + selected_ops + ([cible["item"]] if cible else []))
|
||||||
|
|
||||||
st.markdown(f"**{str(_('pages.fiches.tickets.summary', 'Résumé'))} :**\n- **{str(_('pages.fiches.tickets.title', 'Titre'))}** : `{titre_ticket}`\n- **{str(_('pages.fiches.tickets.labels', 'Labels'))}** : `{', '.join(final_labels)}`")
|
st.markdown(f"**{str(_('pages.fiches.tickets.summary'))} :**\n- **{str(_('pages.fiches.tickets.title'))}** : `{titre_ticket}`\n- **{str(_('pages.fiches.tickets.labels'))}** : `{', '.join(final_labels)}`")
|
||||||
|
|
||||||
if st.button(str(_("pages.fiches.tickets.confirm", "Confirmer la création du ticket"))):
|
if st.button(str(_("pages.fiches.tickets.confirm"))):
|
||||||
labels_existants = get_labels_existants()
|
labels_existants = get_labels_existants()
|
||||||
labels_ids = [labels_existants[l] for l in final_labels if l in labels_existants]
|
labels_ids = [labels_existants[l] for l in final_labels if l in labels_existants]
|
||||||
if "Backlog" in labels_existants:
|
if "Backlog" in labels_existants:
|
||||||
@ -104,16 +104,16 @@ def gerer_previsualisation_et_soumission(reponses, labels, selected_ops, cible):
|
|||||||
creer_ticket_gitea(titre_ticket, corps, labels_ids)
|
creer_ticket_gitea(titre_ticket, corps, labels_ids)
|
||||||
|
|
||||||
st.session_state.previsualiser = False
|
st.session_state.previsualiser = False
|
||||||
st.success(str(_("pages.fiches.tickets.created", "Ticket créé et formulaire vidé.")))
|
st.success(str(_("pages.fiches.tickets.created")))
|
||||||
|
|
||||||
|
|
||||||
def formulaire_creation_ticket_dynamique(fiche_selectionnee):
|
def formulaire_creation_ticket_dynamique(fiche_selectionnee):
|
||||||
"""Fonction principale pour le formulaire de création de ticket."""
|
"""Fonction principale pour le formulaire de création de ticket."""
|
||||||
with st.expander(str(_("pages.fiches.tickets.create_new", "Créer un nouveau ticket lié à cette fiche")), expanded=False):
|
with st.expander(str(_("pages.fiches.tickets.create_new")), expanded=False):
|
||||||
# Chargement et vérification du modèle
|
# Chargement et vérification du modèle
|
||||||
contenu_modele = charger_modele_ticket()
|
contenu_modele = charger_modele_ticket()
|
||||||
if not contenu_modele:
|
if not contenu_modele:
|
||||||
st.error(str(_("pages.fiches.tickets.model_load_error", "Impossible de charger le modèle de ticket.")))
|
st.error(str(_("pages.fiches.tickets.model_load_error")))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Traitement du modèle et génération du formulaire
|
# Traitement du modèle et génération du formulaire
|
||||||
@ -136,5 +136,5 @@ def charger_modele_ticket():
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return base64.b64decode(r.json().get("content", "")).decode("utf-8")
|
return base64.b64decode(r.json().get("content", "")).decode("utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('pages.fiches.tickets.model_error', 'Erreur chargement modèle :'))} {e}")
|
st.error(f"{str(_('pages.fiches.tickets.model_error'))} {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@ -12,21 +12,21 @@ from .core import rechercher_tickets_gitea
|
|||||||
def extraire_statut_par_label(ticket):
|
def extraire_statut_par_label(ticket):
|
||||||
labels = [label.get('name', '') for label in ticket.get('labels', [])]
|
labels = [label.get('name', '') for label in ticket.get('labels', [])]
|
||||||
for statut in ["Backlog",
|
for statut in ["Backlog",
|
||||||
str(_("pages.fiches.tickets.status.awaiting", "En attente de traitement")),
|
str(_("pages.fiches.tickets.status.awaiting")),
|
||||||
str(_("pages.fiches.tickets.status.in_progress", "En cours")),
|
str(_("pages.fiches.tickets.status.in_progress")),
|
||||||
str(_("pages.fiches.tickets.status.completed", "Terminés")),
|
str(_("pages.fiches.tickets.status.completed")),
|
||||||
str(_("pages.fiches.tickets.status.rejected", "Non retenus"))]:
|
str(_("pages.fiches.tickets.status.rejected"))]:
|
||||||
if statut in labels:
|
if statut in labels:
|
||||||
return statut
|
return statut
|
||||||
return str(_("pages.fiches.tickets.status.others", "Autres"))
|
return str(_("pages.fiches.tickets.status.others"))
|
||||||
|
|
||||||
|
|
||||||
def afficher_tickets_par_fiche(tickets):
|
def afficher_tickets_par_fiche(tickets):
|
||||||
if not tickets:
|
if not tickets:
|
||||||
st.info(str(_("pages.fiches.tickets.no_linked_tickets", "Aucun ticket lié à cette fiche.")))
|
st.info(str(_("pages.fiches.tickets.no_linked_tickets")))
|
||||||
return
|
return
|
||||||
|
|
||||||
st.markdown(str(_("pages.fiches.tickets.associated_tickets", "**Tickets associés à cette fiche**")))
|
st.markdown(str(_("pages.fiches.tickets.associated_tickets")))
|
||||||
tickets_groupes = defaultdict(list)
|
tickets_groupes = defaultdict(list)
|
||||||
for ticket in tickets:
|
for ticket in tickets:
|
||||||
statut = extraire_statut_par_label(ticket)
|
statut = extraire_statut_par_label(ticket)
|
||||||
@ -34,14 +34,14 @@ def afficher_tickets_par_fiche(tickets):
|
|||||||
|
|
||||||
nb_backlogs = len(tickets_groupes["Backlog"])
|
nb_backlogs = len(tickets_groupes["Backlog"])
|
||||||
if nb_backlogs:
|
if nb_backlogs:
|
||||||
st.info(f"⤇ {nb_backlogs} {str(_('pages.fiches.tickets.moderation_notice', 'ticket(s) en attente de modération ne sont pas affichés.'))}")
|
st.info(f"⤇ {nb_backlogs} {str(_('pages.fiches.tickets.moderation_notice'))}")
|
||||||
|
|
||||||
ordre_statuts = [
|
ordre_statuts = [
|
||||||
str(_("pages.fiches.tickets.status.awaiting", "En attente de traitement")),
|
str(_("pages.fiches.tickets.status.awaiting")),
|
||||||
str(_("pages.fiches.tickets.status.in_progress", "En cours")),
|
str(_("pages.fiches.tickets.status.in_progress")),
|
||||||
str(_("pages.fiches.tickets.status.completed", "Terminés")),
|
str(_("pages.fiches.tickets.status.completed")),
|
||||||
str(_("pages.fiches.tickets.status.rejected", "Non retenus")),
|
str(_("pages.fiches.tickets.status.rejected")),
|
||||||
str(_("pages.fiches.tickets.status.others", "Autres"))
|
str(_("pages.fiches.tickets.status.others"))
|
||||||
]
|
]
|
||||||
for statut in ordre_statuts:
|
for statut in ordre_statuts:
|
||||||
if tickets_groupes[statut]:
|
if tickets_groupes[statut]:
|
||||||
@ -61,14 +61,14 @@ def recuperer_commentaires_ticket(issue_index):
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('pages.fiches.tickets.comment_error', 'Erreur lors de la récupération des commentaires :'))} {e}")
|
st.error(f"{str(_('pages.fiches.tickets.comment_error'))} {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def afficher_carte_ticket(ticket):
|
def afficher_carte_ticket(ticket):
|
||||||
titre = ticket.get("title", str(_("pages.fiches.tickets.no_title", "Sans titre")))
|
titre = ticket.get("title", str(_("pages.fiches.tickets.no_title")))
|
||||||
url = ticket.get("html_url", "")
|
url = ticket.get("html_url", "")
|
||||||
user = ticket.get("user", {}).get("login", str(_("pages.fiches.tickets.unknown", "inconnu")))
|
user = ticket.get("user", {}).get("login", str(_("pages.fiches.tickets.unknown")))
|
||||||
created = ticket.get("created_at", "")
|
created = ticket.get("created_at", "")
|
||||||
updated = ticket.get("updated_at", "")
|
updated = ticket.get("updated_at", "")
|
||||||
body = ticket.get("body", "")
|
body = ticket.get("body", "")
|
||||||
@ -86,12 +86,12 @@ def afficher_carte_ticket(ticket):
|
|||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
date_created_str = format_date(created)
|
date_created_str = format_date(created)
|
||||||
maj_info = f"({str(_('pages.fiches.tickets.updated', 'MAJ'))} {format_date(updated)})" if updated and updated != created else ""
|
maj_info = f"({str(_('pages.fiches.tickets.updated'))} {format_date(updated)})" if updated and updated != created else ""
|
||||||
|
|
||||||
commentaires = recuperer_commentaires_ticket(ticket.get("number"))
|
commentaires = recuperer_commentaires_ticket(ticket.get("number"))
|
||||||
commentaires_html = ""
|
commentaires_html = ""
|
||||||
for commentaire in commentaires:
|
for commentaire in commentaires:
|
||||||
auteur = html.escape(commentaire.get('user', {}).get('login', str(_("pages.fiches.tickets.unknown", "inconnu"))))
|
auteur = html.escape(commentaire.get('user', {}).get('login', str(_("pages.fiches.tickets.unknown"))))
|
||||||
contenu = html.escape(commentaire.get('body', ''))
|
contenu = html.escape(commentaire.get('body', ''))
|
||||||
date = format_date(commentaire.get('created_at', ''))
|
date = format_date(commentaire.get('created_at', ''))
|
||||||
commentaires_html += f"""
|
commentaires_html += f"""
|
||||||
@ -105,12 +105,12 @@ def afficher_carte_ticket(ticket):
|
|||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div class=\"conteneur_ticket\">
|
<div class=\"conteneur_ticket\">
|
||||||
<h4><a href='{url}' target='_blank'>{titre}</a></h4>
|
<h4><a href='{url}' target='_blank'>{titre}</a></h4>
|
||||||
<p>{str(_("pages.fiches.tickets.opened_by", "Ouvert par"))} <strong>{html.escape(user)}</strong> {str(_("pages.fiches.tickets.on_date", "le"))} {date_created_str} {maj_info}</p>
|
<p>{str(_("pages.fiches.tickets.opened_by"))} <strong>{html.escape(user)}</strong> {str(_("pages.fiches.tickets.on_date"))} {date_created_str} {maj_info}</p>
|
||||||
<p>{str(_("pages.fiches.tickets.subject_label", "Sujet"))} : <strong>{html.escape(sujet)}</strong></p>
|
<p>{str(_("pages.fiches.tickets.subject_label"))} : <strong>{html.escape(sujet)}</strong></p>
|
||||||
<p>Labels : {' • '.join(labels) if labels else str(_("pages.fiches.tickets.no_labels", "aucun"))}</p>
|
<p>Labels : {' • '.join(labels) if labels else str(_("pages.fiches.tickets.no_labels"))}</p>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
st.markdown(body, unsafe_allow_html=False)
|
st.markdown(body, unsafe_allow_html=False)
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown(str(_("pages.fiches.tickets.comments", "**Commentaire(s) :**")))
|
st.markdown(str(_("pages.fiches.tickets.comments")))
|
||||||
st.markdown(commentaires_html or str(_("pages.fiches.tickets.no_comments", "Aucun commentaire.")), unsafe_allow_html=True)
|
st.markdown(commentaires_html or str(_("pages.fiches.tickets.no_comments")), unsafe_allow_html=True)
|
||||||
|
|||||||
@ -2,22 +2,22 @@ import streamlit as st
|
|||||||
from utils.translations import _
|
from utils.translations import _
|
||||||
|
|
||||||
def ajouter_produit(G):
|
def ajouter_produit(G):
|
||||||
st.markdown(f"## {str(_('pages.personnalisation.add_new_product', 'Ajouter un nouveau produit final'))}")
|
st.markdown(f"## {str(_('pages.personnalisation.add_new_product'))}")
|
||||||
new_prod = st.text_input(str(_("pages.personnalisation.new_product_name", "Nom du nouveau produit (unique)")), key="new_prod")
|
new_prod = st.text_input(str(_("pages.personnalisation.new_product_name")), key="new_prod")
|
||||||
if new_prod:
|
if new_prod:
|
||||||
ops_dispo = sorted([
|
ops_dispo = sorted([
|
||||||
n for n, d in G.nodes(data=True)
|
n for n, d in G.nodes(data=True)
|
||||||
if d.get("niveau") == "10"
|
if d.get("niveau") == "10"
|
||||||
and any(G.has_edge(p, n) and G.nodes[p].get("niveau") == "0" for p in G.predecessors(n))
|
and any(G.has_edge(p, n) and G.nodes[p].get("niveau") == "0" for p in G.predecessors(n))
|
||||||
])
|
])
|
||||||
sel_new_op = st.selectbox(str(_("pages.personnalisation.assembly_operation", "Opération d'assemblage (optionnelle)")), [str(_("pages.personnalisation.none", "-- Aucune --"))] + ops_dispo, index=0)
|
sel_new_op = st.selectbox(str(_("pages.personnalisation.assembly_operation")), [str(_("pages.personnalisation.none"))] + ops_dispo, index=0)
|
||||||
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
|
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
|
||||||
sel_comps = st.multiselect(str(_("pages.personnalisation.components_to_link", "Composants à lier")), options=niveau1)
|
sel_comps = st.multiselect(str(_("pages.personnalisation.components_to_link")), options=niveau1)
|
||||||
if st.button(str(_("pages.personnalisation.create_product", "Créer le produit"))):
|
if st.button(str(_("pages.personnalisation.create_product"))):
|
||||||
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
|
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
|
||||||
if sel_new_op != str(_("pages.personnalisation.none", "-- Aucune --")):
|
if sel_new_op != str(_("pages.personnalisation.none")):
|
||||||
G.add_edge(new_prod, sel_new_op)
|
G.add_edge(new_prod, sel_new_op)
|
||||||
for comp in sel_comps:
|
for comp in sel_comps:
|
||||||
G.add_edge(new_prod, comp)
|
G.add_edge(new_prod, comp)
|
||||||
st.success(f"{new_prod} {str(_('pages.personnalisation.added', 'ajouté'))}")
|
st.success(f"{new_prod} {str(_('pages.personnalisation.added'))}")
|
||||||
return G
|
return G
|
||||||
|
|||||||
@ -1,24 +1,25 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import json
|
import json
|
||||||
|
from utils.translations import get_translation as _
|
||||||
|
|
||||||
def importer_exporter_graph(G):
|
def importer_exporter_graph(G):
|
||||||
st.markdown("## Sauvegarder ou restaurer la configuration")
|
st.markdown(f"## {_('pages.personnalisation.save_restore_config')}")
|
||||||
if st.button("Exporter configuration"):
|
if st.button(str(_("pages.personnalisation.export_config"))):
|
||||||
nodes = [n for n, d in G.nodes(data=True) if d.get("personnalisation") == "oui"]
|
nodes = [n for n, d in G.nodes(data=True) if d.get("personnalisation") == "oui"]
|
||||||
edges = [(u, v) for u, v in G.edges() if u in nodes]
|
edges = [(u, v) for u, v in G.edges() if u in nodes]
|
||||||
conf = {"nodes": nodes, "edges": edges}
|
conf = {"nodes": nodes, "edges": edges}
|
||||||
json_str = json.dumps(conf, ensure_ascii=False)
|
json_str = json.dumps(conf, ensure_ascii=False)
|
||||||
st.download_button(
|
st.download_button(
|
||||||
label="Télécharger (JSON)",
|
label=str(_("pages.personnalisation.download_json")),
|
||||||
data=json_str,
|
data=json_str,
|
||||||
file_name="config_personnalisation.json",
|
file_name="config_personnalisation.json",
|
||||||
mime="application/json"
|
mime="application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
uploaded = st.file_uploader("Importer une configuration JSON (max 100 Ko)", type=["json"])
|
uploaded = st.file_uploader(str(_("pages.personnalisation.import_config")), type=["json"])
|
||||||
if uploaded:
|
if uploaded:
|
||||||
if uploaded.size > 100 * 1024:
|
if uploaded.size > 100 * 1024:
|
||||||
st.error("Fichier trop volumineux (max 100 Ko).")
|
st.error(_("pages.personnalisation.file_too_large"))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
conf = json.loads(uploaded.read().decode("utf-8"))
|
conf = json.loads(uploaded.read().decode("utf-8"))
|
||||||
@ -26,17 +27,17 @@ def importer_exporter_graph(G):
|
|||||||
all_edges = conf.get("edges", [])
|
all_edges = conf.get("edges", [])
|
||||||
|
|
||||||
if not all_nodes:
|
if not all_nodes:
|
||||||
st.warning("Aucun produit trouvé dans le fichier.")
|
st.warning(_("pages.personnalisation.no_products_found"))
|
||||||
else:
|
else:
|
||||||
st.markdown("### Sélection des produits à restaurer")
|
st.markdown(f"### {_('pages.personnalisation.select_products_to_restore')}")
|
||||||
sel_nodes = st.multiselect(
|
sel_nodes = st.multiselect(
|
||||||
"Produits à restaurer",
|
str(_("pages.personnalisation.products_to_restore")),
|
||||||
options=all_nodes,
|
options=all_nodes,
|
||||||
default=all_nodes,
|
default=all_nodes,
|
||||||
key="restaurer_selection"
|
key="restaurer_selection"
|
||||||
)
|
)
|
||||||
|
|
||||||
if st.button("Restaurer les éléments sélectionnés", type="primary"):
|
if st.button(str(_("pages.personnalisation.restore_selected")), type="primary"):
|
||||||
for node in sel_nodes:
|
for node in sel_nodes:
|
||||||
if not G.has_node(node):
|
if not G.has_node(node):
|
||||||
G.add_node(node, niveau="0", personnalisation="oui", label=node)
|
G.add_node(node, niveau="0", personnalisation="oui", label=node)
|
||||||
@ -45,8 +46,8 @@ def importer_exporter_graph(G):
|
|||||||
if u in sel_nodes and v in sel_nodes + list(G.nodes()) and not G.has_edge(u, v):
|
if u in sel_nodes and v in sel_nodes + list(G.nodes()) and not G.has_edge(u, v):
|
||||||
G.add_edge(u, v)
|
G.add_edge(u, v)
|
||||||
|
|
||||||
st.success("Configuration partielle restaurée avec succès.")
|
st.success(_("pages.personnalisation.config_restored"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"Erreur d'import : {e}")
|
st.error(f"{_('errors.import_error')} {e}")
|
||||||
|
|
||||||
return G
|
return G
|
||||||
|
|||||||
@ -7,18 +7,9 @@ from .modification import modifier_produit
|
|||||||
from .import_export import importer_exporter_graph
|
from .import_export import importer_exporter_graph
|
||||||
|
|
||||||
def interface_personnalisation(G):
|
def interface_personnalisation(G):
|
||||||
st.markdown(f"# {str(_('pages.personnalisation.title', 'Personnalisation des produits finaux'))}")
|
st.markdown(f"# {str(_('pages.personnalisation.title'))}")
|
||||||
with st.expander(str(_("pages.personnalisation.help", "Comment utiliser cet onglet ?")), expanded=False):
|
with st.expander(str(_("pages.personnalisation.help")), expanded=False):
|
||||||
st.markdown("\n".join([
|
st.markdown("\n".join(_("pages.personnalisation.help_content")))
|
||||||
" " + line for line in _("pages.personnalisation.help_content", [
|
|
||||||
"1. Cliquez sur « Ajouter un produit final » pour créer un nouveau produit",
|
|
||||||
"2. Donnez un nom à votre produit",
|
|
||||||
"3. Sélectionnez une opération d'assemblage appropriée (si pertinent)",
|
|
||||||
"4. Choisissez les composants qui constituent votre produit dans la liste proposée",
|
|
||||||
"5. Sauvegardez votre configuration pour une réutilisation future",
|
|
||||||
"6. Vous pourrez par la suite modifier ou supprimer vos produits personnalisés"
|
|
||||||
])
|
|
||||||
]))
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
G = ajouter_produit(G)
|
G = ajouter_produit(G)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ def get_produits_personnalises(G):
|
|||||||
def supprimer_produit(G, prod):
|
def supprimer_produit(G, prod):
|
||||||
"""Supprime un produit du graphe."""
|
"""Supprime un produit du graphe."""
|
||||||
G.remove_node(prod)
|
G.remove_node(prod)
|
||||||
st.success(f"{prod} {str(_('pages.personnalisation.deleted', 'supprimé'))}")
|
st.success(f"{prod} {str(_('pages.personnalisation.deleted'))}")
|
||||||
st.session_state.pop("prod_sel", None)
|
st.session_state.pop("prod_sel", None)
|
||||||
return G
|
return G
|
||||||
|
|
||||||
@ -51,11 +51,11 @@ def mettre_a_jour_composants(G, prod, linked, nouveaux):
|
|||||||
return G
|
return G
|
||||||
|
|
||||||
def modifier_produit(G):
|
def modifier_produit(G):
|
||||||
st.markdown(f"## {str(_('pages.personnalisation.modify_product', 'Modifier un produit final ajouté'))}")
|
st.markdown(f"## {str(_('pages.personnalisation.modify_product'))}")
|
||||||
|
|
||||||
# Sélection du produit à modifier
|
# Sélection du produit à modifier
|
||||||
produits0 = get_produits_personnalises(G)
|
produits0 = get_produits_personnalises(G)
|
||||||
sel_display = st.multiselect(str(_("pages.personnalisation.products_to_modify", "Produits à modifier")), options=produits0)
|
sel_display = st.multiselect(str(_("pages.personnalisation.products_to_modify")), options=produits0)
|
||||||
|
|
||||||
if not sel_display:
|
if not sel_display:
|
||||||
return G
|
return G
|
||||||
@ -64,24 +64,24 @@ def modifier_produit(G):
|
|||||||
prod = sel_display[0]
|
prod = sel_display[0]
|
||||||
|
|
||||||
# Suppression du produit si demandé
|
# Suppression du produit si demandé
|
||||||
if st.button(f"{str(_('pages.personnalisation.delete', 'Supprimer'))} {prod}"):
|
if st.button(f"{str(_('pages.personnalisation.delete'))} {prod}"):
|
||||||
return supprimer_produit(G, prod)
|
return supprimer_produit(G, prod)
|
||||||
|
|
||||||
# Gestion des opérations d'assemblage
|
# Gestion des opérations d'assemblage
|
||||||
ops_dispo = get_operations_disponibles(G)
|
ops_dispo = get_operations_disponibles(G)
|
||||||
curr_ops = get_operations_actuelles(G, prod)
|
curr_ops = get_operations_actuelles(G, prod)
|
||||||
default_idx = ops_dispo.index(curr_ops[0]) + 1 if curr_ops and curr_ops[0] in ops_dispo else 0
|
default_idx = ops_dispo.index(curr_ops[0]) + 1 if curr_ops and curr_ops[0] in ops_dispo else 0
|
||||||
sel_op = st.selectbox(str(_("pages.personnalisation.linked_assembly_operation", "Opération d'assemblage liée")), [str(_("pages.personnalisation.none", "-- Aucune --"))] + ops_dispo, index=default_idx)
|
sel_op = st.selectbox(str(_("pages.personnalisation.linked_assembly_operation")), [str(_("pages.personnalisation.none"))] + ops_dispo, index=default_idx)
|
||||||
|
|
||||||
# Gestion des composants
|
# Gestion des composants
|
||||||
niveau1 = get_composants_niveau1(G)
|
niveau1 = get_composants_niveau1(G)
|
||||||
linked = get_composants_lies(G, prod)
|
linked = get_composants_lies(G, prod)
|
||||||
nouveaux = st.multiselect(f"{str(_('pages.personnalisation.components_linked_to', 'Composants liés à'))} {prod}", options=niveau1, default=linked)
|
nouveaux = st.multiselect(f"{str(_('pages.personnalisation.components_linked_to'))} {prod}", options=niveau1, default=linked)
|
||||||
|
|
||||||
# Mise à jour des liens si demandé
|
# Mise à jour des liens si demandé
|
||||||
if st.button(f"{str(_('pages.personnalisation.update', 'Mettre à jour'))} {prod}"):
|
if st.button(f"{str(_('pages.personnalisation.update'))} {prod}"):
|
||||||
G = mettre_a_jour_operations(G, prod, curr_ops, sel_op)
|
G = mettre_a_jour_operations(G, prod, curr_ops, sel_op)
|
||||||
G = mettre_a_jour_composants(G, prod, linked, nouveaux)
|
G = mettre_a_jour_composants(G, prod, linked, nouveaux)
|
||||||
st.success(f"{prod} {str(_('pages.personnalisation.updated', 'mis à jour'))}")
|
st.success(f"{prod} {str(_('pages.personnalisation.updated'))}")
|
||||||
|
|
||||||
return G
|
return G
|
||||||
|
|||||||
@ -12,10 +12,10 @@ def afficher_graphique_altair(df):
|
|||||||
|
|
||||||
# Créer un dictionnaire de mappage entre les catégories originales et leurs traductions
|
# Créer un dictionnaire de mappage entre les catégories originales et leurs traductions
|
||||||
mappage_categories = {
|
mappage_categories = {
|
||||||
"Assemblage": str(_("pages.visualisations.categories.assembly", "Assemblage")),
|
"Assemblage": str(_("pages.visualisations.categories.assembly")),
|
||||||
"Fabrication": str(_("pages.visualisations.categories.manufacturing", "Fabrication")),
|
"Fabrication": str(_("pages.visualisations.categories.manufacturing")),
|
||||||
"Traitement": str(_("pages.visualisations.categories.processing", "Traitement")),
|
"Traitement": str(_("pages.visualisations.categories.processing")),
|
||||||
"Extraction": str(_("pages.visualisations.categories.extraction", "Extraction"))
|
"Extraction": str(_("pages.visualisations.categories.extraction"))
|
||||||
}
|
}
|
||||||
|
|
||||||
# Filtrer les catégories qui existent dans les données
|
# Filtrer les catégories qui existent dans les données
|
||||||
@ -53,8 +53,8 @@ def afficher_graphique_altair(df):
|
|||||||
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
||||||
|
|
||||||
base = alt.Chart(df_cat).encode(
|
base = alt.Chart(df_cat).encode(
|
||||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries", "IHH Pays (%)"))),
|
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries"))),
|
||||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors", "IHH Acteurs (%)"))),
|
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors"))),
|
||||||
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
||||||
)
|
)
|
||||||
@ -83,7 +83,7 @@ def afficher_graphique_altair(df):
|
|||||||
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||||
width=500,
|
width=500,
|
||||||
height=400,
|
height=400,
|
||||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality", "Concentration et criticité – {0}")).format(cat_traduit)
|
title=str(_("pages.visualisations.chart_titles.concentration_criticality")).format(cat_traduit)
|
||||||
).interactive()
|
).interactive()
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
st.altair_chart(chart, use_container_width=True)
|
||||||
@ -91,7 +91,7 @@ def afficher_graphique_altair(df):
|
|||||||
|
|
||||||
def creer_graphes(donnees):
|
def creer_graphes(donnees):
|
||||||
if not donnees:
|
if not donnees:
|
||||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à afficher.")))
|
st.warning(str(_("pages.visualisations.no_data")))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -122,8 +122,8 @@ def creer_graphes(donnees):
|
|||||||
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
||||||
|
|
||||||
base = alt.Chart(df).encode(
|
base = alt.Chart(df).encode(
|
||||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction", "IHH Extraction (%)"))),
|
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction"))),
|
||||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves", "IHH Réserves (%)"))),
|
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves"))),
|
||||||
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
||||||
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
||||||
@ -153,13 +153,13 @@ def creer_graphes(donnees):
|
|||||||
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||||
width=600,
|
width=600,
|
||||||
height=500,
|
height=500,
|
||||||
title=str(_("pages.visualisations.chart_titles.concentration_resources", "Concentration des ressources critiques vs vulnérabilité IVC"))
|
title=str(_("pages.visualisations.chart_titles.concentration_resources"))
|
||||||
).interactive()
|
).interactive()
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
st.altair_chart(chart, use_container_width=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.graph_creation_error', 'Erreur lors de la création du graphique :'))} {e}")
|
st.error(f"{str(_('errors.graph_creation_error'))} {e}")
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_criticite(graph):
|
def lancer_visualisation_ihh_criticite(graph):
|
||||||
@ -173,11 +173,11 @@ def lancer_visualisation_ihh_criticite(graph):
|
|||||||
|
|
||||||
df = recuperer_donnees(graph, noeuds)
|
df = recuperer_donnees(graph, noeuds)
|
||||||
if df.empty:
|
if df.empty:
|
||||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à visualiser.")))
|
st.warning(str(_("pages.visualisations.no_data")))
|
||||||
else:
|
else:
|
||||||
afficher_graphique_altair(df)
|
afficher_graphique_altair(df)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_ivc(graph):
|
def lancer_visualisation_ihh_ivc(graph):
|
||||||
@ -192,4 +192,4 @@ def lancer_visualisation_ihh_ivc(graph):
|
|||||||
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
||||||
creer_graphes(data)
|
creer_graphes(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||||
|
|||||||
@ -8,39 +8,28 @@ from .graphes import (
|
|||||||
|
|
||||||
|
|
||||||
def interface_visualisations(G_temp, G_temp_ivc):
|
def interface_visualisations(G_temp, G_temp_ivc):
|
||||||
st.markdown(f"# {str(_('pages.visualisations.title', 'Visualisations'))}")
|
st.markdown(f"# {str(_('pages.visualisations.title'))}")
|
||||||
with st.expander(str(_("pages.visualisations.help", "Comment utiliser cet onglet ?")), expanded=False):
|
with st.expander(str(_("pages.visualisations.help")), expanded=False):
|
||||||
st.markdown("\n".join(_("pages.visualisations.help_content", [
|
st.markdown("\n".join(_("pages.visualisations.help_content")))
|
||||||
"1. Explorez les graphiques présentant l'Indice de Herfindahl-Hirschmann (IHH)",
|
|
||||||
"2. Analysez sa relation avec la criticité moyenne des minerais ou leur Indice de Vulnérabilité Concurrentielle (IVC)",
|
|
||||||
"3. Zoomer dans les graphes pour mieux découvrir les informations",
|
|
||||||
"",
|
|
||||||
"Il est important de se rappeler que l'IHH a deux seuils :",
|
|
||||||
"* en-dessous de 15, la concentration est considérée comme étant faible",
|
|
||||||
"* au-dessus de 25, elle est considérée comme étant forte",
|
|
||||||
"",
|
|
||||||
"Ainsi plus le positionnement d'un point est en haut à droite des graphiques, plus les risques sont élevés.",
|
|
||||||
"Les graphiques présentent 2 droites horizontales et vetrticales pour matérialiser ces seuils."
|
|
||||||
])))
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_criticality", "Indice de Herfindahl-Hirschmann - IHH vs Criticité"))}
|
st.markdown(f"""## {str(_("pages.visualisations.ihh_criticality"))}
|
||||||
|
|
||||||
{str(_("pages.visualisations.ihh_criticality_desc", "La taille des points donne l'indication de la criticité de substituabilité du minerai."))}
|
{str(_("pages.visualisations.ihh_criticality_desc"))}
|
||||||
""")
|
""")
|
||||||
if st.button(str(_("buttons.run", "Lancer")), key="btn_ihh_criticite"):
|
if st.button(str(_("buttons.run")), key="btn_ihh_criticite"):
|
||||||
try:
|
try:
|
||||||
lancer_visualisation_ihh_criticite(G_temp)
|
lancer_visualisation_ihh_criticite(G_temp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||||
|
|
||||||
st.markdown(f"""## {str(_("pages.visualisations.ihh_ivc", "Indice de Herfindahl-Hirschmann - IHH vs IVC"))}
|
st.markdown(f"""## {str(_("pages.visualisations.ihh_ivc"))}
|
||||||
|
|
||||||
{str(_("pages.visualisations.ihh_ivc_desc", "La taille des points donne l'indication de la criticité concurrentielle du minerai."))}
|
{str(_("pages.visualisations.ihh_ivc_desc"))}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
if st.button(str(_("buttons.run", "Lancer")), key="btn_ihh_ivc"):
|
if st.button(str(_("buttons.run")), key="btn_ihh_ivc"):
|
||||||
try:
|
try:
|
||||||
lancer_visualisation_ihh_ivc(G_temp_ivc)
|
lancer_visualisation_ihh_ivc(G_temp_ivc)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||||
|
|||||||
276
assets/locales/en copy.json
Normal file
276
assets/locales/en copy.json
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"title": "Fabnum – Chain Analysis",
|
||||||
|
"description": "Ecosystem exploration and vulnerability identification.",
|
||||||
|
"dev_mode": "You are in the development environment."
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "FabNum - Digital Manufacturing Chain",
|
||||||
|
"subtitle": "Ecosystem exploration and vulnerability identification."
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"copyright": "Fabnum © 2025",
|
||||||
|
"contact": "Contact",
|
||||||
|
"license": "License",
|
||||||
|
"license_text": "CC BY-NC-ND",
|
||||||
|
"eco_note": "🌱 CO₂ calculations via",
|
||||||
|
"eco_provider": "The Green Web Foundation",
|
||||||
|
"powered_by": "🚀 Powered by",
|
||||||
|
"powered_by_name": "Streamlit"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"menu": "Main Menu",
|
||||||
|
"navigation": "Main Navigation",
|
||||||
|
"theme": "Theme",
|
||||||
|
"theme_light": "Light",
|
||||||
|
"theme_dark": "Dark",
|
||||||
|
"theme_instructions_only": "Theme changes can only be made from the Instructions tab.",
|
||||||
|
"impact": "Environmental Impact",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"title": "Authentication",
|
||||||
|
"username": "Username_token",
|
||||||
|
"token": "Gitea Personal Access Token",
|
||||||
|
"login": "Login",
|
||||||
|
"logout": "Logout",
|
||||||
|
"logged_as": "Logged in as",
|
||||||
|
"error": "❌ Access denied.",
|
||||||
|
"gitea_error": "❌ Unable to verify user with Gitea.",
|
||||||
|
"success": "Successfully logged out."
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"instructions": "Instructions",
|
||||||
|
"personnalisation": "Customization",
|
||||||
|
"analyse": "Analysis",
|
||||||
|
"visualisations": "Visualizations",
|
||||||
|
"fiches": "Cards"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"instructions": {
|
||||||
|
"title": "Instructions"
|
||||||
|
},
|
||||||
|
"personnalisation": {
|
||||||
|
"title": "Final Product Customization",
|
||||||
|
"help": "How to use this tab?",
|
||||||
|
"help_content": [
|
||||||
|
"1. Click on \"Add a final product\" to create a new product",
|
||||||
|
"2. Give your product a name",
|
||||||
|
"3. Select an appropriate assembly operation (if relevant)",
|
||||||
|
"4. Choose the components that make up your product from the list provided",
|
||||||
|
"5. Save your configuration for future reuse",
|
||||||
|
"6. You will be able to modify or delete your custom products later"
|
||||||
|
],
|
||||||
|
"add_new_product": "Add a new final product",
|
||||||
|
"new_product_name": "New product name (unique)",
|
||||||
|
"assembly_operation": "Assembly operation (optional)",
|
||||||
|
"none": "-- None --",
|
||||||
|
"components_to_link": "Components to link",
|
||||||
|
"create_product": "Create product",
|
||||||
|
"added": "added",
|
||||||
|
"modify_product": "Modify an added final product",
|
||||||
|
"products_to_modify": "Products to modify",
|
||||||
|
"delete": "Delete",
|
||||||
|
"linked_assembly_operation": "Linked assembly operation",
|
||||||
|
"components_linked_to": "Components linked to",
|
||||||
|
"update": "Update",
|
||||||
|
"updated": "updated",
|
||||||
|
"deleted": "deleted",
|
||||||
|
"save_restore_config": "Save or restore configuration",
|
||||||
|
"export_config": "Export configuration",
|
||||||
|
"download_json": "Download (JSON)",
|
||||||
|
"import_config": "Import a JSON configuration (max 100 KB)",
|
||||||
|
"file_too_large": "File too large (max 100 KB).",
|
||||||
|
"no_products_found": "No products found in the file.",
|
||||||
|
"select_products_to_restore": "Select products to restore",
|
||||||
|
"products_to_restore": "Products to restore",
|
||||||
|
"restore_selected": "Restore selected items",
|
||||||
|
"config_restored": "Partial configuration successfully restored.",
|
||||||
|
"import_error": "Import error:"
|
||||||
|
},
|
||||||
|
"analyse": {
|
||||||
|
"title": "Graph Analysis",
|
||||||
|
"help": "How to use this tab?",
|
||||||
|
"help_content": [
|
||||||
|
"1. Select the starting level (final product, component, or mineral)",
|
||||||
|
"2. Choose the desired destination level",
|
||||||
|
"3. Refine your selection by specifying either one or more specific minerals to target or specific items at each level (optional)",
|
||||||
|
"4. Define the analysis criteria by selecting the relevant vulnerability indices",
|
||||||
|
"5. Choose the index combination mode (AND/OR) according to your analysis needs",
|
||||||
|
"6. Explore the generated graph using zoom and panning controls; you can switch to full screen mode for the graph"
|
||||||
|
],
|
||||||
|
"selection_nodes": "Selection of start and end nodes",
|
||||||
|
"select_level": "-- Select a level --",
|
||||||
|
"start_level": "Start level",
|
||||||
|
"end_level": "End level",
|
||||||
|
"select_minerals": "Select one or more minerals",
|
||||||
|
"filter_by_minerals": "Filter by minerals (optional)",
|
||||||
|
"fine_selection": "Fine selection of items",
|
||||||
|
"filter_start_nodes": "Filter by start nodes (optional)",
|
||||||
|
"filter_end_nodes": "Filter by end nodes (optional)",
|
||||||
|
"vulnerability_filters": "Selection of filters to identify vulnerabilities",
|
||||||
|
"filter_ics": "Filter paths containing at least one critical mineral for a component (ICS > 66%)",
|
||||||
|
"filter_ivc": "Filter paths containing at least one critical mineral in relation to sectoral competition (IVC > 30)",
|
||||||
|
"filter_ihh": "Filter paths containing at least one critical operation in relation to geographical or industrial concentration (IHH countries or actors > 25)",
|
||||||
|
"apply_ihh_filter": "Apply IHH filter on:",
|
||||||
|
"countries": "Countries",
|
||||||
|
"actors": "Actors",
|
||||||
|
"filter_isg": "Filter paths containing an unstable country (ISG ≥ 60)",
|
||||||
|
"filter_logic": "Filter logic",
|
||||||
|
"or": "OR",
|
||||||
|
"and": "AND",
|
||||||
|
"run_analysis": "Run analysis",
|
||||||
|
"sankey": {
|
||||||
|
"no_paths": "No paths found for the specified criteria.",
|
||||||
|
"no_matching_paths": "No paths match the criteria.",
|
||||||
|
"filtered_hierarchy": "Hierarchy filtered by levels and nodes",
|
||||||
|
"download_dot": "Download filtered DOT file",
|
||||||
|
"relation": "Relation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visualisations": {
|
||||||
|
"title": "Visualizations",
|
||||||
|
"help": "How to use this tab?",
|
||||||
|
"help_content": [
|
||||||
|
"1. Explore the graphs presenting the Herfindahl-Hirschmann Index (IHH)",
|
||||||
|
"2. Analyze its relationship with the average criticality of minerals or their Competitive Vulnerability Index (IVC)",
|
||||||
|
"3. Zoom in on the graphs to better discover the information",
|
||||||
|
"",
|
||||||
|
"It is important to remember that the IHH has two thresholds:",
|
||||||
|
"* below 15, concentration is considered to be low",
|
||||||
|
"* above 25, it is considered to be high",
|
||||||
|
"",
|
||||||
|
"Thus, the higher a point is positioned in the top right of the graphs, the higher the risks.",
|
||||||
|
"The graphs present 2 horizontal and vertical lines to mark these thresholds."
|
||||||
|
],
|
||||||
|
"ihh_criticality": "Herfindahl-Hirschmann Index - IHH vs Criticality",
|
||||||
|
"ihh_criticality_desc": "The size of the points indicates the substitutability criticality of the mineral.",
|
||||||
|
"ihh_ivc": "Herfindahl-Hirschmann Index - IHH vs IVC",
|
||||||
|
"ihh_ivc_desc": "The size of the points indicates the competitive criticality of the mineral.",
|
||||||
|
"launch": "Launch",
|
||||||
|
"no_data": "No data to display.",
|
||||||
|
"categories": {
|
||||||
|
"assembly": "Assembly",
|
||||||
|
"manufacturing": "Manufacturing",
|
||||||
|
"processing": "Processing",
|
||||||
|
"extraction": "Extraction"
|
||||||
|
},
|
||||||
|
"axis_titles": {
|
||||||
|
"ihh_countries": "IHH Countries (%)",
|
||||||
|
"ihh_actors": "IHH Actors (%)",
|
||||||
|
"ihh_extraction": "IHH Extraction (%)",
|
||||||
|
"ihh_reserves": "IHH Reserves (%)"
|
||||||
|
},
|
||||||
|
"chart_titles": {
|
||||||
|
"concentration_criticality": "Concentration and Criticality – {0}",
|
||||||
|
"concentration_resources": "Concentration of Critical Resources vs IVC Vulnerability"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fiches": {
|
||||||
|
"title": "Card Discovery",
|
||||||
|
"help": "How to use this tab?",
|
||||||
|
"help_content": [
|
||||||
|
"1. Browse the list of available cards by category",
|
||||||
|
"2. Select a card to display its full content",
|
||||||
|
"3. Consult detailed data, graphs, and additional analyses",
|
||||||
|
"4. Use this information to deepen your understanding of the identified vulnerabilities",
|
||||||
|
"",
|
||||||
|
"The categories are as follows:",
|
||||||
|
"* Assembly: operation of assembling final products from components",
|
||||||
|
"* Related: various operations necessary to manufacture digital technology, but not directly entering its composition",
|
||||||
|
"* Criticalities: indices used to identify and evaluate vulnerabilities",
|
||||||
|
"* Manufacturing: operation of manufacturing components from minerals",
|
||||||
|
"* Mineral: description and operations of extraction and processing of minerals"
|
||||||
|
],
|
||||||
|
"no_files": "No cards available at the moment.",
|
||||||
|
"choose_category": "Choose a card category",
|
||||||
|
"select_folder": "-- Select a folder --",
|
||||||
|
"choose_file": "Choose a card",
|
||||||
|
"select_file": "-- Select a card --",
|
||||||
|
"loading_error": "Error loading the card:",
|
||||||
|
"download_pdf": "Download this card as PDF",
|
||||||
|
"pdf_unavailable": "The PDF file for this card is not available.",
|
||||||
|
"ticket_management": "Ticket management for this card",
|
||||||
|
"tickets": {
|
||||||
|
"create_new": "Create a new ticket linked to this card",
|
||||||
|
"model_load_error": "Unable to load the ticket template.",
|
||||||
|
"contribution_type": "Contribution type",
|
||||||
|
"specify": "Specify",
|
||||||
|
"other": "Other",
|
||||||
|
"concerned_card": "Concerned card",
|
||||||
|
"subject": "Subject of the proposal",
|
||||||
|
"preview": "Preview ticket",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"preview_title": "Ticket preview",
|
||||||
|
"summary": "Summary",
|
||||||
|
"title": "Title",
|
||||||
|
"labels": "Labels",
|
||||||
|
"confirm": "Confirm ticket creation",
|
||||||
|
"created": "Ticket created and form cleared.",
|
||||||
|
"model_error": "Template loading error:",
|
||||||
|
"no_linked_tickets": "No tickets linked to this card.",
|
||||||
|
"associated_tickets": "Tickets associated with this card",
|
||||||
|
"moderation_notice": "ticket(s) awaiting moderation are not displayed.",
|
||||||
|
"status": {
|
||||||
|
"awaiting": "Awaiting processing",
|
||||||
|
"in_progress": "In progress",
|
||||||
|
"completed": "Completed",
|
||||||
|
"rejected": "Rejected",
|
||||||
|
"others": "Others"
|
||||||
|
},
|
||||||
|
"no_title": "No title",
|
||||||
|
"unknown": "unknown",
|
||||||
|
"subject_label": "Subject",
|
||||||
|
"no_labels": "none",
|
||||||
|
"comments": "Comment(s):",
|
||||||
|
"no_comments": "No comments.",
|
||||||
|
"comment_error": "Error retrieving comments:",
|
||||||
|
"opened_by": "Opened by",
|
||||||
|
"on_date": "on",
|
||||||
|
"updated": "UPDATED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_levels": {
|
||||||
|
"0": "Final product",
|
||||||
|
"1": "Component",
|
||||||
|
"2": "Mineral",
|
||||||
|
"10": "Operation",
|
||||||
|
"11": "Operation country",
|
||||||
|
"12": "Operation actor",
|
||||||
|
"99": "Geographic country"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"log_read_error": "Log reading error:",
|
||||||
|
"graph_preview_error": "Graph preview error:",
|
||||||
|
"graph_creation_error": "Error creating the graph:",
|
||||||
|
"ihh_criticality_error": "Error in IHH vs Criticality visualization:",
|
||||||
|
"ihh_ivc_error": "Error in IHH vs IVC visualization:",
|
||||||
|
"comment_fetch_error": "Error retrieving comments:",
|
||||||
|
"template_load_error": "Template loading error:",
|
||||||
|
"import_error": "Import error:"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"download": "Download",
|
||||||
|
"run": "Run",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"filter": "Filter",
|
||||||
|
"search": "Search",
|
||||||
|
"create": "Create",
|
||||||
|
"update": "Update",
|
||||||
|
"delete": "Delete",
|
||||||
|
"preview": "Preview",
|
||||||
|
"export": "Export",
|
||||||
|
"import": "Import",
|
||||||
|
"restore": "Restore",
|
||||||
|
"browse_files": "Browse files"
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"file_uploader": {
|
||||||
|
"drag_drop_here": "Drag and drop file here",
|
||||||
|
"size_limit": "100 KB limit per file • JSON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ def initialiser_logger():
|
|||||||
|
|
||||||
def connexion():
|
def connexion():
|
||||||
if "logged_in" not in st.session_state or not st.session_state.logged_in:
|
if "logged_in" not in st.session_state or not st.session_state.logged_in:
|
||||||
auth_title = str(_("auth.title", "Authentification"))
|
auth_title = str(_("auth.title"))
|
||||||
st.html(f"""
|
st.html(f"""
|
||||||
<section role="region" aria-label="region-authentification">
|
<section role="region" aria-label="region-authentification">
|
||||||
<div role="region" aria-labelledby="Authentification">
|
<div role="region" aria-labelledby="Authentification">
|
||||||
@ -42,9 +42,9 @@ def connexion():
|
|||||||
with st.form("auth_form"):
|
with st.form("auth_form"):
|
||||||
# Ajout d'un champ identifiant fictif pour activer l'autocomplétion navigateur
|
# Ajout d'un champ identifiant fictif pour activer l'autocomplétion navigateur
|
||||||
# et permettre de stocker le token comme un mot de passe par le navigateur
|
# et permettre de stocker le token comme un mot de passe par le navigateur
|
||||||
identifiant = st.text_input(str(_("auth.username", "Identifiant_token")), value="fabnum-connexion", key="nom_utilisateur")
|
identifiant = st.text_input(str(_("auth.username")), value="fabnum-connexion", key="nom_utilisateur")
|
||||||
token = st.text_input(str(_("auth.token", "Token d'accès personnel Gitea")), type="password")
|
token = st.text_input(str(_("auth.token")), type="password")
|
||||||
submitted = st.form_submit_button(str(_("auth.login", "Se connecter")))
|
submitted = st.form_submit_button(str(_("auth.login")))
|
||||||
|
|
||||||
if submitted and token:
|
if submitted and token:
|
||||||
erreur = True
|
erreur = True
|
||||||
@ -78,11 +78,11 @@ def connexion():
|
|||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
st.error(str(_("auth.gitea_error", "❌ Impossible de vérifier l'utilisateur auprès de Gitea.")))
|
st.error(str(_("auth.gitea_error")))
|
||||||
|
|
||||||
if erreur:
|
if erreur:
|
||||||
logger.warning(f"Accès refusé pour tentative avec token depuis IP {ip}")
|
logger.warning(f"Accès refusé pour tentative avec token depuis IP {ip}")
|
||||||
st.error(str(_("auth.error", "❌ Accès refusé.")))
|
st.error(str(_("auth.error")))
|
||||||
|
|
||||||
st.html("""
|
st.html("""
|
||||||
</div>
|
</div>
|
||||||
@ -92,19 +92,19 @@ def connexion():
|
|||||||
|
|
||||||
def bouton_deconnexion():
|
def bouton_deconnexion():
|
||||||
if st.session_state.get("logged_in", False):
|
if st.session_state.get("logged_in", False):
|
||||||
auth_title = str(_("auth.title", "Authentification"))
|
auth_title = str(_("auth.title"))
|
||||||
st.html(f"""
|
st.html(f"""
|
||||||
<section role="region" aria-label="region-authentification">
|
<section role="region" aria-label="region-authentification">
|
||||||
<div role="region" aria-labelledby="Authentification">
|
<div role="region" aria-labelledby="Authentification">
|
||||||
<p id="Authentification" class="decorative-heading">{auth_title}</p>
|
<p id="Authentification" class="decorative-heading">{auth_title}</p>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
st.sidebar.markdown(f"{str(_('auth.logged_as', 'Connecté en tant que'))} `{st.session_state.username}`")
|
st.sidebar.markdown(f"{str(_('auth.logged_as'))} `{st.session_state.username}`")
|
||||||
if st.sidebar.button(str(_("auth.logout", "Se déconnecter"))):
|
if st.sidebar.button(str(_("auth.logout"))):
|
||||||
st.session_state.logged_in = False
|
st.session_state.logged_in = False
|
||||||
st.session_state.username = ""
|
st.session_state.username = ""
|
||||||
st.session_state.token = ""
|
st.session_state.token = ""
|
||||||
st.success(str(_("auth.success", "Déconnecté avec succès.")))
|
st.success(str(_("auth.success")))
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
st.html("""
|
st.html("""
|
||||||
|
|||||||
@ -11,11 +11,11 @@ def afficher_pied_de_page():
|
|||||||
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
|
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
|
||||||
<div class='info-footer'>
|
<div class='info-footer'>
|
||||||
<p id='footer-appli' class='info-footer'>
|
<p id='footer-appli' class='info-footer'>
|
||||||
{_("footer.copyright", "Fabnum © 2025")} – <a href='mailto:stephan-pro@peccini.fr'>{_("footer.contact", "Contact")}</a> – {_("footer.license", "Licence")} <a href='https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr' target='_blank'>{_("footer.license_text", "CC BY-NC-ND")}</a>
|
{_("footer.copyright")} – <a href='mailto:stephan-pro@peccini.fr'>{_("footer.contact")}</a> – {_("footer.license")} <a href='https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr' target='_blank'>{_("footer.license_text")}</a>
|
||||||
</p>
|
</p>
|
||||||
<p class='footer-note'>
|
<p class='footer-note'>
|
||||||
{_("footer.eco_note", "🌱 Calculs eqCO₂ via")} <a href='https://www.thegreenwebfoundation.org/' target='_blank'>{_("footer.eco_provider", "The Green Web Foundation")}</a><br>
|
{_("footer.eco_note")} <a href='https://www.thegreenwebfoundation.org/' target='_blank'>{_("footer.eco_provider")}</a><br>
|
||||||
{_("footer.powered_by", "🚀 Propulsé par")} <a href='https://streamlit.io/' target='_blank'>{_("footer.powered_by_name", "Streamlit")}</a>
|
{_("footer.powered_by")} <a href='https://streamlit.io/' target='_blank'>{_("footer.powered_by_name")}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,13 +7,13 @@ def afficher_entete():
|
|||||||
header = f"""
|
header = f"""
|
||||||
<header role="banner" aria-labelledby="entete-header">
|
<header role="banner" aria-labelledby="entete-header">
|
||||||
<div class='wide-header'>
|
<div class='wide-header'>
|
||||||
<p id='entete-header' class='titre-header'>{_("header.title", "FabNum - Chaîne de fabrication du numérique")}</p>
|
<p id='entete-header' class='titre-header'>{_("header.title")}</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ENV == "dev":
|
if ENV == "dev":
|
||||||
header += f"<p>🔧 {_("app.dev_mode", "Vous êtes dans l'environnement de développement.")}</p>"
|
header += f"<p>🔧 {_("app.dev_mode")}</p>"
|
||||||
else:
|
else:
|
||||||
header += f"<p>{_("header.subtitle", "Parcours de l'écosystème et identification des vulnérabilités.")}</p>"
|
header += f"<p>{_("header.subtitle")}</p>"
|
||||||
|
|
||||||
header += """
|
header += """
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,22 +7,22 @@ from utils.translations import _
|
|||||||
def afficher_menu():
|
def afficher_menu():
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<nav role="navigation" aria-label="{str(_('sidebar.menu', 'Menu principal'))}">
|
<nav role="navigation" aria-label="{str(_('sidebar.menu'))}">
|
||||||
<div role="region" aria-label="{str(_('sidebar.navigation', 'Navigation principale'))}" class="onglets-accessibles">
|
<div role="region" aria-label="{str(_('sidebar.navigation'))}" class="onglets-accessibles">
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
# Définir la variable instructions_text une seule fois en haut de la fonction
|
# Définir la variable instructions_text une seule fois en haut de la fonction
|
||||||
instructions_text = str(_("navigation.instructions", "Instructions"))
|
instructions_text = str(_("navigation.instructions"))
|
||||||
if "onglet" not in st.session_state:
|
if "onglet" not in st.session_state:
|
||||||
st.session_state.onglet = instructions_text
|
st.session_state.onglet = instructions_text
|
||||||
|
|
||||||
onglet_choisi = None
|
onglet_choisi = None
|
||||||
onglets = [
|
onglets = [
|
||||||
str(_("navigation.instructions", "Instructions")),
|
str(_("navigation.instructions")),
|
||||||
str(_("navigation.personnalisation", "Personnalisation")),
|
str(_("navigation.personnalisation")),
|
||||||
str(_("navigation.analyse", "Analyse")),
|
str(_("navigation.analyse")),
|
||||||
str(_("navigation.visualisations", "Visualisations")),
|
str(_("navigation.visualisations")),
|
||||||
str(_("navigation.fiches", "Fiches"))
|
str(_("navigation.fiches"))
|
||||||
]
|
]
|
||||||
|
|
||||||
for nom in onglets:
|
for nom in onglets:
|
||||||
@ -45,9 +45,9 @@ def afficher_menu():
|
|||||||
#
|
#
|
||||||
if st.session_state.onglet == instructions_text:
|
if st.session_state.onglet == instructions_text:
|
||||||
if "theme_mode" not in st.session_state:
|
if "theme_mode" not in st.session_state:
|
||||||
st.session_state.theme_mode = str(_("sidebar.theme_light", "Clair"))
|
st.session_state.theme_mode = str(_("sidebar.theme_light"))
|
||||||
|
|
||||||
theme_title = str(_("sidebar.theme", "Thème"))
|
theme_title = str(_("sidebar.theme"))
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<section role="region" aria-label="region-theme">
|
<section role="region" aria-label="region-theme">
|
||||||
<div role="region" aria-labelledby="Theme">
|
<div role="region" aria-labelledby="Theme">
|
||||||
@ -55,11 +55,11 @@ def afficher_menu():
|
|||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
theme_options = [
|
theme_options = [
|
||||||
str(_("sidebar.theme_light", "Clair")),
|
str(_("sidebar.theme_light")),
|
||||||
str(_("sidebar.theme_dark", "Sombre"))
|
str(_("sidebar.theme_dark"))
|
||||||
]
|
]
|
||||||
theme = st.radio(
|
theme = st.radio(
|
||||||
str(_("sidebar.theme", "Thème")),
|
str(_("sidebar.theme")),
|
||||||
theme_options,
|
theme_options,
|
||||||
index=theme_options.index(st.session_state.theme_mode),
|
index=theme_options.index(st.session_state.theme_mode),
|
||||||
horizontal=True,
|
horizontal=True,
|
||||||
@ -71,14 +71,14 @@ def afficher_menu():
|
|||||||
</div>
|
</div>
|
||||||
</nav>""", unsafe_allow_html=True)
|
</nav>""", unsafe_allow_html=True)
|
||||||
else :
|
else :
|
||||||
theme_title = str(_("sidebar.theme", "Thème"))
|
theme_title = str(_("sidebar.theme"))
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<section role="region" aria-label="region-theme">
|
<section role="region" aria-label="region-theme">
|
||||||
<div role="region" aria-labelledby="Theme">
|
<div role="region" aria-labelledby="Theme">
|
||||||
<p id="Theme" class="decorative-heading">{theme_title}</p>
|
<p id="Theme" class="decorative-heading">{theme_title}</p>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
st.info(str(_("sidebar.theme_instructions_only", "Le changement de thème ne peut se faire que depuis l'onglet Instructions.")))
|
st.info(str(_("sidebar.theme_instructions_only")))
|
||||||
|
|
||||||
st.markdown("""
|
st.markdown("""
|
||||||
<hr />
|
<hr />
|
||||||
@ -101,8 +101,8 @@ def afficher_menu():
|
|||||||
|
|
||||||
|
|
||||||
def afficher_impact(total_bytes):
|
def afficher_impact(total_bytes):
|
||||||
impact_label = str(_("sidebar.impact", "Impact environnemental"))
|
impact_label = str(_("sidebar.impact"))
|
||||||
loading_text = str(_("sidebar.loading", "Chargement en cours…"))
|
loading_text = str(_("sidebar.loading"))
|
||||||
|
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
components.html(f"""
|
components.html(f"""
|
||||||
|
|||||||
10
fabnum.py
10
fabnum.py
@ -154,11 +154,11 @@ ouvrir_page()
|
|||||||
dot_file_path = None
|
dot_file_path = None
|
||||||
|
|
||||||
# Obtenir les noms traduits des onglets
|
# Obtenir les noms traduits des onglets
|
||||||
instructions_tab = _("navigation.instructions", "Instructions")
|
instructions_tab = _("navigation.instructions")
|
||||||
fiches_tab = _("navigation.fiches", "Fiches")
|
fiches_tab = _("navigation.fiches")
|
||||||
personnalisation_tab = _("navigation.personnalisation", "Personnalisation")
|
personnalisation_tab = _("navigation.personnalisation")
|
||||||
analyse_tab = _("navigation.analyse", "Analyse")
|
analyse_tab = _("navigation.analyse")
|
||||||
visualisations_tab = _("navigation.visualisations", "Visualisations")
|
visualisations_tab = _("navigation.visualisations")
|
||||||
|
|
||||||
if st.session_state.onglet == instructions_tab:
|
if st.session_state.onglet == instructions_tab:
|
||||||
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
|
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
|
||||||
|
|||||||
@ -31,7 +31,7 @@ def load_translations(lang="fr"):
|
|||||||
logger.error(f"Erreur lors du chargement des traductions: {e}")
|
logger.error(f"Erreur lors du chargement des traductions: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_translation(key, default=None):
|
def get_translation(key):
|
||||||
"""
|
"""
|
||||||
Récupère une traduction par sa clé.
|
Récupère une traduction par sa clé.
|
||||||
Les clés peuvent être hiérarchiques, séparées par des points.
|
Les clés peuvent être hiérarchiques, séparées par des points.
|
||||||
@ -51,7 +51,7 @@ def get_translation(key, default=None):
|
|||||||
|
|
||||||
# Si aucune traduction n'est chargée, retourner la valeur par défaut
|
# Si aucune traduction n'est chargée, retourner la valeur par défaut
|
||||||
if not st.session_state.get("translations"):
|
if not st.session_state.get("translations"):
|
||||||
return default if default is not None else key
|
return f"⊗⤇ {key} ⤆⊗"
|
||||||
|
|
||||||
# Parcourir la hiérarchie des clés
|
# Parcourir la hiérarchie des clés
|
||||||
keys = key.split(".")
|
keys = key.split(".")
|
||||||
@ -59,7 +59,7 @@ def get_translation(key, default=None):
|
|||||||
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
if not isinstance(current, dict) or k not in current:
|
if not isinstance(current, dict) or k not in current:
|
||||||
return default if default is not None else key
|
return f"⊗⤇ {key} ⤆⊗"
|
||||||
current = current[k]
|
current = current[k]
|
||||||
|
|
||||||
return current
|
return current
|
||||||
|
|||||||
@ -8,10 +8,10 @@ from utils.translations import _
|
|||||||
|
|
||||||
def afficher_graphique_altair(df):
|
def afficher_graphique_altair(df):
|
||||||
ordre_personnalise = [
|
ordre_personnalise = [
|
||||||
str(_("pages.visualisations.categories.assembly", "Assemblage")),
|
str(_("pages.visualisations.categories.assembly")),
|
||||||
str(_("pages.visualisations.categories.manufacturing", "Fabrication")),
|
str(_("pages.visualisations.categories.manufacturing")),
|
||||||
str(_("pages.visualisations.categories.processing", "Traitement")),
|
str(_("pages.visualisations.categories.processing")),
|
||||||
str(_("pages.visualisations.categories.extraction", "Extraction"))
|
str(_("pages.visualisations.categories.extraction"))
|
||||||
]
|
]
|
||||||
categories = [cat for cat in ordre_personnalise if cat in df['categorie'].unique()]
|
categories = [cat for cat in ordre_personnalise if cat in df['categorie'].unique()]
|
||||||
for cat in categories:
|
for cat in categories:
|
||||||
@ -42,8 +42,8 @@ def afficher_graphique_altair(df):
|
|||||||
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
||||||
|
|
||||||
base = alt.Chart(df_cat).encode(
|
base = alt.Chart(df_cat).encode(
|
||||||
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries", "IHH Pays (%)"))),
|
x=alt.X('ihh_pays:Q', title=str(_("pages.visualisations.axis_titles.ihh_countries"))),
|
||||||
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors", "IHH Acteurs (%)"))),
|
y=alt.Y('ihh_acteurs:Q', title=str(_("pages.visualisations.axis_titles.ihh_actors"))),
|
||||||
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
||||||
)
|
)
|
||||||
@ -70,7 +70,7 @@ def afficher_graphique_altair(df):
|
|||||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
||||||
width=500,
|
width=500,
|
||||||
height=400,
|
height=400,
|
||||||
title=str(_("pages.visualisations.chart_titles.concentration_criticality", "Concentration et criticité – {0}")).format(str(cat))
|
title=str(_("pages.visualisations.chart_titles.concentration_criticality")).format(str(cat))
|
||||||
).interactive()
|
).interactive()
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
st.altair_chart(chart, use_container_width=True)
|
||||||
@ -78,7 +78,7 @@ def afficher_graphique_altair(df):
|
|||||||
|
|
||||||
def creer_graphes(donnees):
|
def creer_graphes(donnees):
|
||||||
if not donnees:
|
if not donnees:
|
||||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à afficher.")))
|
st.warning(str(_("pages.visualisations.no_data")))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -109,8 +109,8 @@ def creer_graphes(donnees):
|
|||||||
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
||||||
|
|
||||||
base = alt.Chart(df).encode(
|
base = alt.Chart(df).encode(
|
||||||
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction", "IHH Extraction (%)"))),
|
x=alt.X('ihh_extraction:Q', title=str(_("pages.visualisations.axis_titles.ihh_extraction"))),
|
||||||
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves", "IHH Réserves (%)"))),
|
y=alt.Y('ihh_reserves:Q', title=str(_("pages.visualisations.axis_titles.ihh_reserves"))),
|
||||||
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
||||||
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
||||||
@ -138,13 +138,13 @@ def creer_graphes(donnees):
|
|||||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
||||||
width=600,
|
width=600,
|
||||||
height=500,
|
height=500,
|
||||||
title=str(_("pages.visualisations.chart_titles.concentration_resources", "Concentration des ressources critiques vs vulnérabilité IVC"))
|
title=str(_("pages.visualisations.chart_titles.concentration_resources"))
|
||||||
).interactive()
|
).interactive()
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
st.altair_chart(chart, use_container_width=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.graph_creation_error', 'Erreur lors de la création du graphique :'))} {e}")
|
st.error(f"{str(_('errors.graph_creation_error'))} {e}")
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_criticite(graph):
|
def lancer_visualisation_ihh_criticite(graph):
|
||||||
@ -158,11 +158,11 @@ def lancer_visualisation_ihh_criticite(graph):
|
|||||||
|
|
||||||
df = recuperer_donnees(graph, noeuds)
|
df = recuperer_donnees(graph, noeuds)
|
||||||
if df.empty:
|
if df.empty:
|
||||||
st.warning(str(_("pages.visualisations.no_data", "Aucune donnée à visualiser.")))
|
st.warning(str(_("pages.visualisations.no_data")))
|
||||||
else:
|
else:
|
||||||
afficher_graphique_altair(df)
|
afficher_graphique_altair(df)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_criticality_error', 'Erreur dans la visualisation IHH vs Criticité :'))} {e}")
|
st.error(f"{str(_('errors.ihh_criticality_error'))} {e}")
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_ivc(graph):
|
def lancer_visualisation_ihh_ivc(graph):
|
||||||
@ -177,4 +177,4 @@ def lancer_visualisation_ihh_ivc(graph):
|
|||||||
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
||||||
creer_graphes(data)
|
creer_graphes(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
st.error(f"{str(_('errors.ihh_ivc_error', 'Erreur dans la visualisation IHH vs IVC :'))} {e}")
|
st.error(f"{str(_('errors.ihh_ivc_error'))} {e}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user