227 lines
9.8 KiB
Python
227 lines
9.8 KiB
Python
# === Constantes et imports ===
|
|
import streamlit as st
|
|
import requests
|
|
import re
|
|
import os
|
|
import yaml
|
|
import markdown
|
|
from bs4 import BeautifulSoup
|
|
from datetime import datetime, timezone
|
|
from latex2mathml.converter import convert as latex_to_mathml
|
|
from utils.tickets.display import afficher_tickets_par_fiche
|
|
from utils.tickets.creation import formulaire_creation_ticket_dynamique
|
|
from utils.tickets.core import rechercher_tickets_gitea
|
|
|
|
from config import GITEA_TOKEN, GITEA_URL, ORGANISATION, DEPOT_FICHES, ENV, FICHES_CRITICITE
|
|
|
|
from utils.gitea import charger_arborescence_fiches, recuperer_date_dernier_commit
|
|
|
|
from utils.fiche_utils import load_seuils, render_fiche_markdown
|
|
from utils.dynamic import (
|
|
build_dynamic_sections,
|
|
build_ivc_sections,
|
|
build_ihh_sections,
|
|
build_isg_sections,
|
|
build_production_sections,
|
|
build_minerai_sections
|
|
)
|
|
|
|
# === Logique métier ===
|
|
def fichier_plus_recent(chemin_fichier, reference):
|
|
try:
|
|
modif = datetime.fromtimestamp(os.path.getmtime(chemin_fichier), tz=timezone.utc)
|
|
return modif > reference
|
|
except Exception:
|
|
return False
|
|
|
|
def doit_regenerer_fiche(html_path, fiche_type, fiche_choisie, commit_url, fichiers_criticite):
|
|
if not os.path.exists(html_path):
|
|
return True
|
|
|
|
local_mtime = datetime.fromtimestamp(os.path.getmtime(html_path), tz=timezone.utc)
|
|
remote_mtime = recuperer_date_dernier_commit(commit_url)
|
|
|
|
if remote_mtime is None or remote_mtime > local_mtime:
|
|
return True
|
|
|
|
if fichier_plus_recent(fichiers_criticite.get("IHH"), local_mtime):
|
|
return True
|
|
|
|
if fiche_type == "minerai" or "minerai" in fiche_choisie.lower():
|
|
if fichier_plus_recent(fichiers_criticite.get("IVC"), local_mtime):
|
|
return True
|
|
if fichier_plus_recent(fichiers_criticite.get("ICS"), local_mtime):
|
|
return True
|
|
|
|
return False
|
|
|
|
# === Fonctions de transformation ===
|
|
def remplacer_latex_par_mathml(markdown_text):
|
|
def remplacer_bloc_display(match):
|
|
formule_latex = match.group(1).strip()
|
|
try:
|
|
mathml = latex_to_mathml(formule_latex, display='block')
|
|
return f'<div class="math-block">{mathml}</div>'
|
|
except Exception as e:
|
|
return f"<pre>Erreur LaTeX block: {e}</pre>"
|
|
|
|
def remplacer_bloc_inline(match):
|
|
formule_latex = match.group(1).strip()
|
|
try:
|
|
mathml = latex_to_mathml(formule_latex, display='inline')
|
|
return f'<span class="math-inline">{mathml}</span>'
|
|
except Exception as e:
|
|
return f"<code>Erreur LaTeX inline: {e}</code>"
|
|
|
|
markdown_text = re.sub(r"\$\$(.*?)\$\$", remplacer_bloc_display, markdown_text, flags=re.DOTALL)
|
|
markdown_text = re.sub(r"(?<!\$)\$(.+?)\$(?!\$)", remplacer_bloc_inline, markdown_text, flags=re.DOTALL)
|
|
return markdown_text
|
|
|
|
def markdown_to_html_rgaa(markdown_text, caption_text=None):
|
|
html = markdown.markdown(markdown_text, extensions=['tables'])
|
|
soup = BeautifulSoup(html, "html.parser")
|
|
for i, table in enumerate(soup.find_all("table"), start=1):
|
|
table["role"] = "table"
|
|
table["summary"] = caption_text
|
|
if caption_text:
|
|
caption = soup.new_tag("caption")
|
|
caption.string = caption_text
|
|
table.insert(len(table.contents), caption)
|
|
for th in table.find_all("th"):
|
|
th["scope"] = "col"
|
|
return str(soup)
|
|
|
|
# === Fonctions principales ===
|
|
def creer_fiche(md_source, dossier, nom_fichier, seuils):
|
|
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md_source)
|
|
context = yaml.safe_load(front_match.group(1)) if front_match else {}
|
|
|
|
type_fiche = context.get("type_fiche")
|
|
if type_fiche == "indice":
|
|
indice = context.get("indice_court")
|
|
if indice == "ICS":
|
|
md_source = build_dynamic_sections(md_source)
|
|
elif indice == "IVC":
|
|
md_source = build_ivc_sections(md_source)
|
|
elif indice == "IHH":
|
|
md_source = build_ihh_sections(md_source)
|
|
elif indice == "ISG":
|
|
md_source = build_isg_sections(md_source)
|
|
elif type_fiche in ["assemblage", "fabrication"]:
|
|
md_source = build_production_sections(md_source)
|
|
elif type_fiche == "minerai":
|
|
md_source = build_minerai_sections(md_source)
|
|
|
|
contenu_md = render_fiche_markdown(md_source, seuils)
|
|
md_path = os.path.join("Fiches", dossier, nom_fichier)
|
|
os.makedirs(os.path.dirname(md_path), exist_ok=True)
|
|
with open(md_path, "w", encoding="utf-8") as f:
|
|
f.write(contenu_md)
|
|
|
|
lignes = contenu_md.split('\n')
|
|
sections_n1 = []
|
|
section_n1_actuelle = {"titre": None, "intro": [], "sections_n2": {}}
|
|
section_n2_actuelle = None
|
|
for ligne in lignes:
|
|
if re.match(r'^#[^#]', ligne):
|
|
if section_n1_actuelle["titre"] or section_n1_actuelle["intro"] or section_n1_actuelle["sections_n2"]:
|
|
sections_n1.append(section_n1_actuelle)
|
|
section_n1_actuelle = {"titre": ligne.strip('# ').strip(), "intro": [], "sections_n2": {}}
|
|
section_n2_actuelle = None
|
|
elif re.match(r'^##[^#]', ligne):
|
|
section_n2_actuelle = ligne.strip('# ').strip()
|
|
section_n1_actuelle["sections_n2"][section_n2_actuelle] = [f"## {section_n2_actuelle}"]
|
|
elif section_n2_actuelle:
|
|
section_n1_actuelle["sections_n2"][section_n2_actuelle].append(ligne)
|
|
else:
|
|
section_n1_actuelle["intro"].append(ligne)
|
|
|
|
if section_n1_actuelle["titre"] or section_n1_actuelle["intro"] or section_n1_actuelle["sections_n2"]:
|
|
sections_n1.append(section_n1_actuelle)
|
|
|
|
bloc_titre = sections_n1[0]["titre"] if sections_n1 and sections_n1[0]["titre"] else "fiche"
|
|
titre_id = re.sub(r'\W+', '-', bloc_titre.lower()).strip('-')
|
|
|
|
html_output = [f'<section role="region" aria-labelledby="{titre_id}">', f'<h1 id="{titre_id}">{bloc_titre}</h1>']
|
|
for bloc in sections_n1:
|
|
if bloc["titre"] and bloc["titre"] != bloc_titre:
|
|
html_output.append(f"<h2>{bloc['titre']}</h2>")
|
|
if bloc["intro"]:
|
|
intro_md = remplacer_latex_par_mathml("\n".join(bloc["intro"]))
|
|
html_intro = markdown_to_html_rgaa(intro_md)
|
|
html_output.append(html_intro)
|
|
for sous_titre, contenu in bloc["sections_n2"].items():
|
|
contenu_md = remplacer_latex_par_mathml("\n".join(contenu))
|
|
contenu_html = markdown_to_html_rgaa(contenu_md, caption_text=sous_titre)
|
|
html_output.append(f"<details><summary>{sous_titre}</summary>{contenu_html}</details>")
|
|
|
|
html_output.append("</section>")
|
|
html_dir = os.path.join("HTML", dossier)
|
|
os.makedirs(html_dir, exist_ok=True)
|
|
html_path = os.path.join(html_dir, os.path.splitext(nom_fichier)[0] + ".html")
|
|
with open(html_path, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(html_output))
|
|
|
|
return html_path
|
|
|
|
def afficher_fiches():
|
|
if "fiches_arbo" not in st.session_state:
|
|
st.session_state["fiches_arbo"] = charger_arborescence_fiches()
|
|
|
|
arbo = st.session_state.get("fiches_arbo", {})
|
|
if not arbo:
|
|
st.warning("Aucune fiche disponible pour le moment.")
|
|
return
|
|
|
|
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
|
dossier_choisi = st.selectbox("Choisissez un dossier", ["-- Sélectionner un dossier --"] + dossiers)
|
|
|
|
if dossier_choisi and dossier_choisi != "-- Sélectionner un dossier --":
|
|
fiches = arbo.get(dossier_choisi, [])
|
|
noms_fiches = [f['nom'] for f in fiches]
|
|
fiche_choisie = st.selectbox("Choisissez une fiche", ["-- Sélectionner une fiche --"] + noms_fiches)
|
|
|
|
if fiche_choisie and fiche_choisie != "-- Sélectionner une fiche --":
|
|
fiche_info = next((f for f in fiches if f["nom"] == fiche_choisie), None)
|
|
if fiche_info:
|
|
try:
|
|
headers = {"Authorization": f"token {GITEA_TOKEN}"}
|
|
reponse_fiche = requests.get(fiche_info["download_url"], headers=headers)
|
|
reponse_fiche.raise_for_status()
|
|
md_source = reponse_fiche.text
|
|
|
|
if "seuils" not in st.session_state:
|
|
SEUILS = load_seuils("assets/config.yaml")
|
|
st.session_state["seuils"] = SEUILS
|
|
else:
|
|
SEUILS = st.session_state["seuils"]
|
|
|
|
html_path = os.path.join("HTML", dossier_choisi, os.path.splitext(fiche_choisie)[0] + ".html")
|
|
path_relative = f"Documents/{dossier_choisi}/{fiche_choisie}"
|
|
commits_url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/commits?path={path_relative}&sha={ENV}"
|
|
|
|
regenerate = doit_regenerer_fiche(
|
|
html_path=html_path,
|
|
fiche_type=fiche_info.get("type", ""),
|
|
fiche_choisie=fiche_choisie,
|
|
commit_url=commits_url,
|
|
fichiers_criticite=FICHES_CRITICITE
|
|
)
|
|
|
|
if regenerate:
|
|
st.info("DEBUG : Régénération de la fiche")
|
|
html_path = creer_fiche(md_source, dossier_choisi, fiche_choisie, SEUILS)
|
|
else:
|
|
st.info("DEBUG : Pas de régénération")
|
|
|
|
with open(html_path, "r", encoding="utf-8") as f:
|
|
st.markdown(f.read(), unsafe_allow_html=True)
|
|
|
|
st.markdown("<hr style='border: 1px solid #ccc; margin: 2rem 0;' />", unsafe_allow_html=True)
|
|
st.markdown("## Gestion des tickets pour cette fiche")
|
|
afficher_tickets_par_fiche(rechercher_tickets_gitea(fiche_choisie))
|
|
formulaire_creation_ticket_dynamique(fiche_choisie)
|
|
|
|
except Exception as e:
|
|
st.error(f"Erreur lors du chargement de la fiche : {e}")
|