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)