import os
import io
import pandas as pd
import pdfplumber
from flask import Flask, request, render_template_string, Response, send_from_directory
app = Flask(__name__)
# Dossier temporaire pour stocker le fichier généré à l'intérieur du conteneur
TEMP_DIR = "/app/downloads"
if not os.path.exists(TEMP_DIR):
os.makedirs(TEMP_DIR)
# --- DESIGN HTML & JAVASCRIPT ---
# Ce bloc est envoyé en premier au navigateur pour préparer l'interface de log
HTML_LAYOUT = """
Extracteur PDF - Progression en direct
Analyse du catalogue en cours...
Chaque page du fichier est analysée pour extraire les fiches de formation.
> Initialisation du moteur d'extraction...
"""
# --- PAGE D'ACCUEIL ---
@app.route("/")
def home():
return render_template_string("""
Extracteur de formations
📄
Extracteur PDF
Sélectionnez le catalogue de formations (PDF) pour extraire les données vers Excel.
""")
# --- MOTEUR D'EXTRACTION ---
@app.route("/process", methods=["POST"])
def process():
file = request.files['pdf_file']
if not file: return "Fichier manquant"
file_content = file.read() # On garde le fichier en mémoire vive
def generate():
yield HTML_LAYOUT # Envoie l'interface au navigateur
all_formations = []
COLONNES_ADMISES = ["Référence", "Durée", "Type", "Publics éligibles", "Niveau", "Sessions", "Tarifs"]
try:
with pdfplumber.open(io.BytesIO(file_content)) as pdf:
total_pages = len(pdf.pages)
yield f""
for i, page in enumerate(pdf.pages):
page_num = i + 1
percent = int((page_num / total_pages) * 100)
try:
# Zone de détection (haut de page)
check_zone = page.crop((page.width * 0.5, 0, page.width, 400))
text = check_zone.extract_text() or ""
if "Référence" in text:
# --- EXTRACTION DE LA FICHE ---
header = page.crop((0, 0, page.width, 100))
titre_brut = header.extract_text().split('\n')[0].strip()
# Sécurisation du titre pour JS (on enlève les quotes)
titre_clean = titre_brut.replace("'", "").replace('"', '').strip()[:60]
data = {'Titre': titre_brut, 'Page_Source': page_num}
# Extraction du tableau à droite
right_side = page.crop((page.width * 0.6, 180, page.width, 600))
table = right_side.extract_table()
if table:
for row in table:
if len(row) >= 2 and row[0]:
cle = row[0].replace('\n', ' ').strip()
for col in COLONNES_ADMISES:
if col.lower() in cle.lower():
data[col] = row[1].replace('\n', ' ').strip()
break
all_formations.append(data)
yield f""
else:
# --- PAGE IGNORÉE ---
yield f""
except Exception as page_err:
yield f""
# --- FIN DE BOUCLE : SAUVEGARDE EXCEL ---
yield f""
if all_formations:
df = pd.DataFrame(all_formations)
output_path = os.path.join(TEMP_DIR, "resultat.xlsx")
# On utilise xlsxwriter pour la stabilité
df.to_excel(output_path, index=False, engine='xlsxwriter')
yield f""
yield ""
else:
yield f""
except Exception as global_err:
yield f""
yield ""
return Response(generate(), mimetype='text/html')
# --- TÉLÉCHARGEMENT ---
@app.route("/download")
def download():
return send_from_directory(TEMP_DIR, "resultat.xlsx", as_attachment=True)
if __name__ == "__main__":
# Écoute sur 0.0.0.0 pour Docker
app.run(host='0.0.0.0', port=5000, debug=False)