File size: 26,136 Bytes
45216d7
e85cb9c
662cf94
e28d5a2
662cf94
 
 
e28d5a2
662cf94
 
9f7873d
6cf0508
662cf94
e28d5a2
45216d7
0206d8e
45216d7
e85cb9c
6cf0508
e28d5a2
6cf0508
 
 
 
662cf94
e28d5a2
45216d7
662cf94
 
 
6cf0508
 
45216d7
 
 
 
 
 
662cf94
45216d7
 
 
 
e28d5a2
 
9f7873d
45216d7
e28d5a2
45216d7
 
 
 
 
 
 
9f7873d
45216d7
 
9f7873d
6cf0508
 
 
 
 
45216d7
6cf0508
45216d7
 
 
 
e85cb9c
45216d7
 
 
 
e28d5a2
662cf94
6cf0508
45216d7
 
 
6cf0508
 
 
 
 
 
 
 
 
45216d7
6cf0508
45216d7
 
6cf0508
 
45216d7
6cf0508
 
 
45216d7
6cf0508
662cf94
6cf0508
45216d7
 
 
6cf0508
 
 
 
 
 
 
 
 
 
 
 
45216d7
 
6cf0508
45216d7
 
6cf0508
 
 
45216d7
 
 
 
6cf0508
 
 
45216d7
e28d5a2
662cf94
6cf0508
 
 
45216d7
 
e28d5a2
662cf94
6cf0508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45216d7
6cf0508
 
 
 
 
 
 
45216d7
6cf0508
 
 
 
45216d7
6cf0508
 
 
 
 
 
45216d7
6cf0508
45216d7
e28d5a2
662cf94
6cf0508
 
45216d7
6cf0508
45216d7
6cf0508
 
 
45216d7
e28d5a2
662cf94
6cf0508
45216d7
6cf0508
 
 
45216d7
6cf0508
45216d7
e28d5a2
662cf94
6cf0508
 
 
 
 
 
 
 
 
 
 
 
45216d7
e28d5a2
 
 
 
 
 
 
6cf0508
662cf94
45216d7
e28d5a2
 
 
662cf94
e28d5a2
45216d7
 
e85cb9c
45216d7
662cf94
e28d5a2
662cf94
e28d5a2
45216d7
662cf94
e85cb9c
662cf94
 
45216d7
e28d5a2
45216d7
 
 
 
e85cb9c
 
45216d7
 
 
e28d5a2
45216d7
 
6cf0508
e28d5a2
6cf0508
45216d7
 
e28d5a2
45216d7
 
 
e85cb9c
6cf0508
45216d7
6cf0508
45216d7
e28d5a2
45216d7
e28d5a2
45216d7
 
6cf0508
45216d7
 
 
6cf0508
45216d7
6cf0508
45216d7
 
6cf0508
 
 
45216d7
6cf0508
45216d7
e28d5a2
45216d7
6cf0508
45216d7
e28d5a2
45216d7
 
e85cb9c
e28d5a2
 
45216d7
 
e85cb9c
45216d7
 
 
e85cb9c
45216d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662cf94
 
 
e28d5a2
45216d7
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# --- START OF COMPLETE FLASK APP SCRIPT (v6 - Credentials Fix) ---
from flask import Flask, render_template, request, send_file, flash, redirect, url_for
import os
import convertapi # For PDF conversion
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
import math
import traceback # For detailed error logging

# --- Configuration ---
# **** CORRECTED CONFIGURATION VARIABLE ****
convertapi.api_credentials = 'secret_8wCI6pgOP9AxLVJG'

# Define a temporary directory for generated files
UPLOAD_FOLDER = 'temp_files'
if not os.path.exists(UPLOAD_FOLDER):
    try:
        os.makedirs(UPLOAD_FOLDER)
    except OSError as e:
        print(f"Error creating directory {UPLOAD_FOLDER}: {e}")


# --- Classe de génération de document (v5 logic - unchanged) ---
class EvaluationGymnique:
    def __init__(self):
        self.document = Document()
        try:
            section = self.document.sections[0]
            section.page_height = Cm(29.7); section.page_width = Cm(21)
            section.left_margin = Cm(1.5); section.right_margin = Cm(1.5)
            section.top_margin = Cm(1); section.bottom_margin = Cm(1)
        except Exception as e: print(f"Error setting up document sections: {e}")
        self.centre_examen = "Centre d'examen"; self.type_examen = "Bac Général"; self.serie = "Série"
        self.etablissement = "Établissement"; self.session = "2025"; self.nom_candidat = "Candidat"
        self.elements_techniques = []
        self.base_font_size = 10; self.base_header_font_size = 14; self.base_row_height = 1.1; self.table_font_size = 9
        self.available_height = 27.7; self.fixed_elements_height = 15
        self.dynamic_font_size = self.base_font_size; self.dynamic_header_font_size = self.base_header_font_size
        self.dynamic_table_font_size = self.table_font_size; self.dynamic_row_height = self.base_row_height
        self.spacing_factor = 1.0

    def calculate_dynamic_sizing(self):
        num_elements = len(self.elements_techniques); estimated_table_height = (num_elements + 1) * self.base_row_height * 1.3
        available_space_for_table = self.available_height - self.fixed_elements_height
        if estimated_table_height > available_space_for_table and num_elements > 5:
            reduction_factor = max(0.6, 1 - (max(0, num_elements - 5) * 0.04))
            self.dynamic_font_size = max(self.base_font_size * reduction_factor, 7)
            self.dynamic_header_font_size = max(self.base_header_font_size * reduction_factor, 10)
            self.dynamic_table_font_size = max(self.table_font_size * reduction_factor, 7)
            self.dynamic_row_height = max(self.base_row_height * (reduction_factor + 0.1), 0.8)
            self.spacing_factor = max(reduction_factor, 0.4)
        else:
            self.dynamic_font_size = self.base_font_size; self.dynamic_header_font_size = self.base_header_font_size
            self.dynamic_table_font_size = self.table_font_size; self.dynamic_row_height = self.base_row_height
            self.spacing_factor = 1.0

    def _set_cell_shading(self, cell, fill_color):
        try:
            shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{fill_color}"/>')
            cell._tc.get_or_add_tcPr().append(shading_elm)
        except Exception as e: print(f"Error setting cell shading: {e}")

    def _configure_cell(self, cell, text, bold=False, italic=False, font_size=None, color_rgb=None, alignment=WD_ALIGN_PARAGRAPH.LEFT, v_alignment=WD_ALIGN_VERTICAL.CENTER):
        cell.text = text; cell.vertical_alignment = v_alignment
        paragraph = cell.paragraphs[0]; paragraph.alignment = alignment
        paragraph.paragraph_format.space_before = Pt(0); paragraph.paragraph_format.space_after = Pt(0)
        if paragraph.runs:
            run = paragraph.runs[0]; run.bold = bold; run.italic = italic
            if font_size: run.font.size = Pt(font_size)
            if color_rgb: run.font.color.rgb = color_rgb
        return paragraph

    def ajouter_entete_colore(self):
        try:
            header_paragraph = self.document.add_paragraph(); header_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER; header_paragraph.space_after = Pt(6 * self.spacing_factor)
            header_run = header_paragraph.add_run("ÉVALUATION GYMNASTIQUE"); header_run.bold = True; header_run.font.size = Pt(self.dynamic_header_font_size); header_run.font.color.rgb = RGBColor(0, 32, 96)
            header_table = self.document.add_table(rows=3, cols=2); header_table.style = 'Table Grid'; header_table.autofit = False
            page_width_cm = self.document.sections[0].page_width.cm; left_margin_cm = self.document.sections[0].left_margin.cm; right_margin_cm = self.document.sections[0].right_margin.cm
            available_table_width = page_width_cm - left_margin_cm - right_margin_cm
            col_widths = [available_table_width * 0.55, available_table_width * 0.45]
            for i, width in enumerate(col_widths):
                for cell in header_table.columns[i].cells: cell.width = Cm(width)
            row_height_cm = max(0.6, 0.8 * self.spacing_factor)
            for row in header_table.rows:
                row.height = Cm(row_height_cm)
                for cell in row.cells:
                    cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER; self._set_cell_shading(cell, "D9E2F3")
                    for p in cell.paragraphs: p.paragraph_format.space_before = Pt(0); p.paragraph_format.space_after = Pt(0)
            header_info = [[("Centre d'examen: ", self.centre_examen), ("Examen: ", self.type_examen)], [("Série: ", self.serie), ("Établissement: ", self.etablissement)], [("Session: ", self.session), ("Candidat: ", self.nom_candidat)]]
            text_color = RGBColor(0, 32, 96)
            for r, row_data in enumerate(header_info):
                for c, (label, value) in enumerate(row_data):
                    cell = header_table.cell(r, c); p = cell.paragraphs[0]; p.clear()
                    run_label = p.add_run(label); run_label.bold = True; run_label.font.size = Pt(self.dynamic_font_size); run_label.font.color.rgb = text_color
                    run_value = p.add_run(value); run_value.font.size = Pt(self.dynamic_font_size)
            self.document.add_paragraph().paragraph_format.space_after = Pt(4 * self.spacing_factor)
        except Exception as e: print(f"Error adding header: {e}"); traceback.print_exc()

    def creer_tableau_elements(self):
        try:
            num_elements = len(self.elements_techniques);
            if num_elements == 0: return
            table = self.document.add_table(rows=num_elements + 1, cols=5); table.style = 'Table Grid'; table.alignment = WD_TABLE_ALIGNMENT.CENTER; table.autofit = False
            page_width_cm = self.document.sections[0].page_width.cm; left_margin_cm = self.document.sections[0].left_margin.cm; right_margin_cm = self.document.sections[0].right_margin.cm
            available_table_width = page_width_cm - left_margin_cm - right_margin_cm
            total_prop = 8 + 3 + 2 + 2.5 + 2.5
            col_widths_cm = [available_table_width * (p / total_prop) for p in [8, 3, 2, 2.5, 2.5]]
            for i, width in enumerate(col_widths_cm):
                for cell in table.columns[i].cells: cell.width = Cm(width)
            min_row_height_cm = max(0.8, self.dynamic_row_height)
            for row in table.rows:
                row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST; row.height = Cm(min_row_height_cm)
                for cell in row.cells:
                    cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
                    for p in cell.paragraphs: p.paragraph_format.space_before = Pt(0); p.paragraph_format.space_after = Pt(0)
            header_row = table.rows[0]; header_color = RGBColor(0, 32, 96)
            headers_config = [("ELEMENTS TECHNIQUES", {"bold": True, "font_size": self.dynamic_table_font_size, "color_rgb": header_color, "alignment": WD_ALIGN_PARAGRAPH.CENTER}), ("CATEGORIES D'ELEMENTS TECHNIQUES ET PONDERATION", {"bold": True, "font_size": self.dynamic_table_font_size, "color_rgb": header_color, "alignment": WD_ALIGN_PARAGRAPH.CENTER}), ("", {}), ("APPRECIATIONS", {"bold": True, "font_size": self.dynamic_table_font_size, "color_rgb": header_color, "alignment": WD_ALIGN_PARAGRAPH.CENTER}), ("POINTS Accordés", {"bold": True, "font_size": self.dynamic_table_font_size, "color_rgb": header_color, "alignment": WD_ALIGN_PARAGRAPH.CENTER})]
            for i, (text, config) in enumerate(headers_config):
                 cell = header_row.cells[i]; self._set_cell_shading(cell, "BDD7EE")
                 if i != 2: self._configure_cell(cell, text, **config)
            try: table.cell(0, 1).merge(table.cell(0, 2))
            except Exception as merge_err: print(f"Error merging header cells: {merge_err}")
            for i, element in enumerate(self.elements_techniques, 1):
                if i >= len(table.rows): continue
                self._configure_cell(table.cell(i, 0), f'{element["nom"]}\n', font_size=self.dynamic_table_font_size, alignment=WD_ALIGN_PARAGRAPH.LEFT, v_alignment=WD_ALIGN_VERTICAL.CENTER)
                self._configure_cell(table.cell(i, 1), element["categorie"], bold=True, italic=True, font_size=self.dynamic_table_font_size, alignment=WD_ALIGN_PARAGRAPH.CENTER)
                self._configure_cell(table.cell(i, 2), str(element["points"]), bold=True, italic=True, font_size=self.dynamic_table_font_size, alignment=WD_ALIGN_PARAGRAPH.CENTER)
                self._configure_cell(table.cell(i, 3), "", font_size=self.dynamic_table_font_size)
                self._configure_cell(table.cell(i, 4), "", font_size=self.dynamic_table_font_size)
            self.document.add_paragraph().paragraph_format.space_after = Pt(6 * self.spacing_factor)
        except Exception as e: print(f"Error creating elements table: {e}"); traceback.print_exc()

    def ajouter_note_jury(self):
        try:
            para = self.document.add_paragraph(); para.paragraph_format.space_before = Pt(4 * self.spacing_factor); para.paragraph_format.space_after = Pt(4 * self.spacing_factor)
            run = para.add_run("NB1 : Zone réservée aux membres du jury ! Le jury cochera le point correspondant au niveau de réalisation de l'élément gymnique par le candidat.")
            run.bold = True; run.font.color.rgb = RGBColor(255, 0, 0); run.font.size = Pt(max(self.dynamic_font_size - 2, 6))
        except Exception as e: print(f"Error adding jury note: {e}")

    def creer_tableau_recapitulatif(self):
        try:
            note_table = self.document.add_table(rows=3, cols=13); note_table.style = 'Table Grid'; note_table.alignment = WD_TABLE_ALIGNMENT.CENTER; note_table.autofit = False
            page_width_cm = self.document.sections[0].page_width.cm; left_margin_cm = self.document.sections[0].left_margin.cm; right_margin_cm = self.document.sections[0].right_margin.cm
            available_recap_width = page_width_cm - left_margin_cm - right_margin_cm
            width_A_E_pair = available_recap_width * (1.2 / 13.0); width_final_single = available_recap_width * (1.0 / 13.0)
            col_widths_recap = [];
            for _ in range(5): col_widths_recap.extend([width_A_E_pair / 2, width_A_E_pair / 2])
            col_widths_recap.extend([width_final_single, width_final_single, width_final_single])
            current_total_width = sum(col_widths_recap); width_adjustment = (available_recap_width - current_total_width) / len(col_widths_recap)
            for i, width in enumerate(col_widths_recap):
                adjusted_width = max(0.5, width + width_adjustment);
                for cell in note_table.columns[i].cells: cell.width = Cm(adjusted_width)
            row_height_cm = max(0.5, 0.6 * self.spacing_factor)
            for row in note_table.rows:
                row.height = Cm(row_height_cm)
                for cell in row.cells: cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER;
                for p in cell.paragraphs: p.paragraph_format.space_before = Pt(0); p.paragraph_format.space_after = Pt(0)
            header_color = RGBColor(0, 32, 96); header_fill = "BDD7EE"; recap_font_size = max(self.dynamic_table_font_size - 1, 6)
            for cell in note_table.rows[0].cells: self._set_cell_shading(cell, header_fill)
            type_data = [("A", "1pt"), ("B", "1,5pt"), ("C", "2pts"), ("D", "2,5pts"), ("E", "3pts")]
            for col, (type_lettre, points) in enumerate(type_data):
                idx = col * 2
                if idx + 1 < 13:
                    cell = note_table.cell(0, idx)
                    try:
                        cell.merge(note_table.cell(0, idx + 1)); self._configure_cell(cell, f"Type {type_lettre}\n{points}", bold=True, font_size=recap_font_size, color_rgb=header_color, alignment=WD_ALIGN_PARAGRAPH.CENTER)
                    except Exception as e: print(f"Error merging recap cells at index {idx}: {e}")
            final_headers = [("ROV", "2pts"), ("Projet", "2pts"), ("Réalisation", "16pts")]
            for col_offset, (titre, points) in enumerate(final_headers):
                col = 10 + col_offset
                if col < 13: self._configure_cell(note_table.cell(0, col), f"{titre}\n{points}", bold=True, font_size=recap_font_size, color_rgb=header_color, alignment=WD_ALIGN_PARAGRAPH.CENTER)
            for col in range(5):
                idx = col * 2
                if idx + 1 < 13:
                    self._configure_cell(note_table.cell(1, idx), "NEG", italic=True, font_size=recap_font_size, alignment=WD_ALIGN_PARAGRAPH.CENTER)
                    self._configure_cell(note_table.cell(1, idx + 1), "Note", italic=True, font_size=recap_font_size, alignment=WD_ALIGN_PARAGRAPH.CENTER)
            for col in range(10, 13):
                 if col < 13: self._configure_cell(note_table.cell(1, col), "Note", italic=True, font_size=recap_font_size, alignment=WD_ALIGN_PARAGRAPH.CENTER)
            self.document.add_paragraph().paragraph_format.space_after = Pt(6 * self.spacing_factor)
        except Exception as e: print(f"Error creating recap table: {e}"); traceback.print_exc()

    def ajouter_note_candidat_avec_cadre(self):
        try:
            note_table = self.document.add_table(rows=1, cols=1); note_table.style = 'Table Grid'; note_table.alignment = WD_TABLE_ALIGNMENT.CENTER; note_table.autofit = True
            cell = note_table.cell(0, 0); self._set_cell_shading(cell, "C6E0B4")
            p = cell.paragraphs[0]; p.paragraph_format.space_before = Pt(2); p.paragraph_format.space_after = Pt(2)
            font_size = max(7 * self.spacing_factor, 6)
            run = p.add_run("NB2: Après le choix des catégories d'éléments gymniques par le candidat, ce dernier remplira la colonne de pointage selon l'orientation suivante: A (0.25; 0.5; 0.75; 1) B (0.25; 0.5; 0.75; 1; 1.25; 1.5) C (0.5; 0.75; 1; 1.25; 1.5; 2) D (0.75; 1; 1.25; 1.5; 2; 2.5) et E (0.75; 1; 1.5; 2; 2.5; 3) également, le candidat devra fournir 2 copies de son projet sur une page! (appréciations: NR, NM, PM, M).")
            run.italic = True; run.font.size = Pt(font_size)
            self.document.add_paragraph().paragraph_format.space_after = Pt(8 * self.spacing_factor)
        except Exception as e: print(f"Error adding candidate note box: {e}")

    def ajouter_zone_note(self):
        try:
            para_note_label = self.document.add_paragraph(); para_note_label.alignment = WD_ALIGN_PARAGRAPH.RIGHT; para_note_label.paragraph_format.space_after = Pt(1); para_note_label.paragraph_format.space_before = Pt(4 * self.spacing_factor)
            run = para_note_label.add_run("Note finale/20"); run.bold = True; run.font.size = Pt(self.dynamic_table_font_size + 1); run.font.color.rgb = RGBColor(0, 32, 96)
            box_table = self.document.add_table(rows=1, cols=1); box_table.style = 'Table Grid'; box_table.alignment = WD_TABLE_ALIGNMENT.RIGHT
            box_size = Cm(1.5); cell = box_table.cell(0, 0); cell.width = box_size; box_table.rows[0].height = box_size
            self._configure_cell(cell, "", v_alignment=WD_ALIGN_VERTICAL.CENTER)
            self.document.add_paragraph().paragraph_format.space_after = Pt(8 * self.spacing_factor)
        except Exception as e: print(f"Error adding final score zone: {e}")

    def ajouter_lignes_correcteurs(self):
        try:
            num_elements = len(self.elements_techniques); use_compact_mode = num_elements > 12
            if use_compact_mode:
                para = self.document.add_paragraph(); para.paragraph_format.space_before = Pt(4 * self.spacing_factor); para.paragraph_format.space_after = Pt(4 * self.spacing_factor)
                run = para.add_run("Correcteurs: "); run.bold = True; run.font.size = Pt(self.dynamic_font_size); para.add_run("Projet / Principal / ROV").font.size = Pt(self.dynamic_font_size); para.add_run("\n" + "." * 30)
            else:
                for role in ["Projet", "Principal", "ROV"]:
                    para = self.document.add_paragraph(); para.paragraph_format.space_before = Pt(3 * self.spacing_factor); para.paragraph_format.space_after = Pt(1 * self.spacing_factor)
                    run = para.add_run(f"Correcteur {role} : "); run.bold = True; run.font.size = Pt(self.dynamic_font_size)
                    chars_per_cm_estimate = 3; line_length_cm = 10; points_count = int(line_length_cm * chars_per_cm_estimate)
                    points_count = max(20, points_count); points_count = int(points_count * (self.dynamic_font_size / 10.0) * self.spacing_factor); points_count = max(15, points_count)
                    para.add_run("." * points_count).font.size = Pt(self.dynamic_font_size)
        except Exception as e: print(f"Error adding corrector lines: {e}")

    def modifier_centre_examen(self, nom): self.centre_examen = nom
    def modifier_type_examen(self, type_examen): self.type_examen = type_examen
    def modifier_serie(self, serie): self.serie = serie
    def modifier_etablissement(self, nom): self.etablissement = nom
    def modifier_session(self, annee): self.session = annee
    def modifier_candidat(self, nom): self.nom_candidat = nom

    def ajouter_element(self, nom, categorie, points):
        try: point_value = float(str(points).replace(',', '.'))
        except (ValueError, TypeError): print(f"Warning: Invalid points value '{points}' for element '{nom}'. Using 0.0."); point_value = 0.0
        self.elements_techniques.append({"nom": nom, "categorie": categorie, "points": point_value})

    def generer_document(self, nom_fichier="evaluation_gymnastique.docx"):
        try:
            self.calculate_dynamic_sizing(); self.ajouter_entete_colore(); self.creer_tableau_elements(); self.ajouter_note_jury(); self.creer_tableau_recapitulatif(); self.ajouter_lignes_correcteurs(); self.ajouter_zone_note(); self.ajouter_note_candidat_avec_cadre()
            self.document.save(nom_fichier); print(f"Document '{nom_fichier}' generated successfully.")
            return nom_fichier
        except Exception as e: print(f"FATAL error generating document: {e}"); traceback.print_exc(); return None

# --- Flask Application ---
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.secret_key = os.urandom(24)

@app.route("/eps", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        docx_filepath = None; pdf_filepath = None
        try:
            # --- Get Form Data ---
            centre_examen = request.form.get("centre_examen", "Centre d'examen"); type_examen = request.form.get("type_examen", "Bac Général")
            serie = request.form.get("serie", "Série"); etablissement = request.form.get("etablissement", "Établissement")
            session_value = request.form.get("session", "2025"); nom_candidat = request.form.get("nom_candidat", "Candidat")
            output_format = request.form.get("format", "docx")

            # --- Create Document Instance ---
            evaluation = EvaluationGymnique(); evaluation.modifier_centre_examen(centre_examen); evaluation.modifier_type_examen(type_examen)
            evaluation.modifier_serie(serie); evaluation.modifier_etablissement(etablissement); evaluation.modifier_session(session_value); evaluation.modifier_candidat(nom_candidat)

            # --- Get Elements ---
            element_names = request.form.getlist("new_element_name"); element_categories = request.form.getlist("new_element_categorie"); element_points = request.form.getlist("new_element_points")
            num_elements_added = 0
            for name, cat, pts in zip(element_names, element_categories, element_points):
                if name and name.strip() and cat and cat.strip() and pts and pts.strip():
                    evaluation.ajouter_element(name.strip(), cat.strip(), pts.strip()); num_elements_added += 1
            if num_elements_added == 0: flash("Aucun élément technique complet n'a été fourni. Le document sera généré sans éléments.", "warning")

            # --- Generate DOCX ---
            safe_candidat_name = "".join(c if c.isalnum() else "_" for c in nom_candidat); safe_session = "".join(c if c.isalnum() else "_" for c in session_value)
            base_filename = f"evaluation_{safe_candidat_name}_{safe_session}"; docx_filename = f"{base_filename}.docx"
            docx_filepath = os.path.join(app.config['UPLOAD_FOLDER'], docx_filename)
            generated_docx = evaluation.generer_document(docx_filepath)
            if not generated_docx: flash("Une erreur est survenue lors de la génération du document DOCX.", "error"); return redirect(url_for('index'))

            # --- Convert / Send ---
            if output_format == "pdf":
                 print(f"Output format requested: PDF. Attempting conversion for {docx_filepath}...") # Debug print
                 try:
                    # **** Check credentials before calling convert ****
                    if not convertapi.api_credentials or convertapi.api_credentials == 'YOUR_SECRET': # Check actual variable used
                         flash("La clé API pour ConvertAPI n'est pas configurée correctement. Impossible de générer le PDF.", "error")
                         # Sending DOCX as fallback because API key is missing
                         print("ConvertAPI credentials missing, sending DOCX instead.")
                         return send_file(docx_filepath, as_attachment=True, download_name=docx_filename)

                    print(f"Credentials seem OK ('{convertapi.api_credentials[:5]}...'). Calling convertapi.convert...") # Debug print, hide most of key
                    result = convertapi.convert('pdf', { 'File': docx_filepath }, from_format = 'docx')
                    pdf_filename_base = f"{base_filename}.pdf"; pdf_filepath = os.path.join(app.config['UPLOAD_FOLDER'], pdf_filename_base)
                    result.save_files(pdf_filepath); print(f"PDF saved to {pdf_filepath}")
                    return send_file(pdf_filepath, as_attachment=True, download_name=pdf_filename_base)

                 except convertapi.ApiError as api_err:
                      print(f"ConvertAPI Error during conversion: {api_err}")
                      flash(f"Erreur ConvertAPI: {api_err}. Vérifiez votre clé ou vos crédits. Le fichier DOCX sera téléchargé.", "warning")
                      return send_file(docx_filepath, as_attachment=True, download_name=docx_filename) # Fallback
                 except Exception as e:
                      print(f"Non-API Error during PDF conversion process: {e}"); traceback.print_exc()
                      flash(f"Erreur lors de la conversion PDF: {e}. Le fichier DOCX sera téléchargé.", "warning")
                      return send_file(docx_filepath, as_attachment=True, download_name=docx_filename) # Fallback

            else: # Send DOCX
                print(f"Output format requested: DOCX. Sending {docx_filepath}...") # Debug print
                return send_file(docx_filepath, as_attachment=True, download_name=docx_filename)

        except Exception as e:
            print(f"An error occurred during POST request processing: {e}"); traceback.print_exc()
            flash(f"Une erreur interne est survenue: {e}", "error"); return redirect(url_for('index'))

        # --- Cleanup (Consider moving to a background task) ---
        # This basic cleanup might remove files before they are fully sent or if fallback occurs.
        # Advanced cleanup is recommended for production.
        finally:
             if pdf_filepath and os.path.exists(pdf_filepath):
                 # If PDF was created, definitely remove the source DOCX now
                 if docx_filepath and os.path.exists(docx_filepath):
                     try: os.remove(docx_filepath); print(f"Removed temp file: {docx_filepath}")
                     except OSError as e: print(f"Error removing temp docx: {e}")
                 # Optionally remove PDF after sending?
                 # try: os.remove(pdf_filepath); print(f"Removed temp file: {pdf_filepath}")
                 # except OSError as e: print(f"Error removing temp pdf: {e}")
             elif docx_filepath and os.path.exists(docx_filepath) and output_format == 'docx':
                 # If only DOCX was generated and sent, maybe remove it?
                 # try: os.remove(docx_filepath); print(f"Removed temp file: {docx_filepath}")
                 # except OSError as e: print(f"Error removing temp docx: {e}")
                 pass # Keeping DOCX for now after sending


    # --- GET Request ---
    return render_template("index.html")

if __name__ == "__main__":
    if not os.path.exists(UPLOAD_FOLDER):
        try: os.makedirs(UPLOAD_FOLDER)
        except OSError as e: print(f"CRITICAL: Could not create upload folder '{UPLOAD_FOLDER}'. Exiting. Error: {e}"); exit()
    # Changed port back to 5000 for consistency if preferred
    app.run(debug=True, host='0.0.0.0', port=5000)