husseinelsaadi commited on
Commit
a746471
·
1 Parent(s): 5fcca4c
Files changed (1) hide show
  1. backend/services/report_generator.py +185 -1
backend/services/report_generator.py CHANGED
@@ -145,9 +145,189 @@ def generate_llm_interview_report(application) -> str:
145
  lines.append(f"Error loading interview log: {e}")
146
 
147
  return '\n'.join(lines)
148
-
 
 
 
 
 
149
 
150
  def create_pdf_report(report_text: str) -> BytesIO:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  """
152
  Alternative implementation using reportlab for better PDF generation.
153
  Install with: pip install reportlab
@@ -232,5 +412,9 @@ def create_pdf_report(report_text: str) -> BytesIO:
232
  doc.build(story)
233
  buffer.seek(0)
234
  return buffer
 
 
 
 
235
 
236
  __all__ = ['generate_llm_interview_report', 'create_pdf_report']
 
145
  lines.append(f"Error loading interview log: {e}")
146
 
147
  return '\n'.join(lines)
148
+ from io import BytesIO
149
+ from matplotlib.backends.backend_pdf import PdfPages
150
+ import matplotlib.pyplot as plt
151
+ import matplotlib.patches as mpatches
152
+ from typing import List, Tuple
153
+ import textwrap
154
 
155
  def create_pdf_report(report_text: str) -> BytesIO:
156
+ """Convert a formatted report into a visually appealing PDF with enhanced formatting."""
157
+ buffer = BytesIO()
158
+
159
+ # Configuration
160
+ PAGE_WIDTH = 8.27 # A4 width in inches
161
+ PAGE_HEIGHT = 11.69 # A4 height in inches
162
+ MARGIN_LEFT = 0.75
163
+ MARGIN_RIGHT = 0.75
164
+ MARGIN_TOP = 1.0
165
+ MARGIN_BOTTOM = 1.0
166
+
167
+ # Calculate usable width for text
168
+ usable_width = PAGE_WIDTH - MARGIN_LEFT - MARGIN_RIGHT
169
+ chars_per_inch = 12 # Approximate for 10pt font
170
+ wrap_width = int(usable_width * chars_per_inch)
171
+
172
+ # Text styling
173
+ FONT_SIZE_NORMAL = 10
174
+ FONT_SIZE_HEADING = 12
175
+ LINE_HEIGHT = 0.02 # Relative to page height
176
+ QUESTION_COLOR = '#2C3E50' # Dark blue-gray
177
+ ANSWER_COLOR = '#34495E' # Slightly lighter
178
+ SCORE_COLOR = '#27AE60' # Green
179
+ FEEDBACK_COLOR = '#E74C3C' # Red
180
+
181
+ # Prepare formatted content
182
+ raw_lines = report_text.split("\n")
183
+ wrapper = textwrap.TextWrapper(width=wrap_width, break_long_words=False, replace_whitespace=False)
184
+
185
+ formatted_content = []
186
+
187
+ for line in raw_lines:
188
+ stripped = line.strip()
189
+
190
+ if stripped.startswith("Question"):
191
+ # Add spacing before questions (except the first one)
192
+ if formatted_content:
193
+ formatted_content.append({"text": "", "style": "normal"})
194
+
195
+ # Question with special formatting
196
+ formatted_content.append({
197
+ "text": stripped,
198
+ "style": "question",
199
+ "color": QUESTION_COLOR,
200
+ "bold": True,
201
+ "size": FONT_SIZE_HEADING
202
+ })
203
+
204
+ elif stripped.startswith("Answer:"):
205
+ # Extract and wrap answer text
206
+ answer_text = stripped.replace("Answer:", "", 1).strip()
207
+ wrapped_lines = wrapper.wrap(f"Answer: {answer_text}") if answer_text else ["Answer:"]
208
+
209
+ for idx, wrapped_line in enumerate(wrapped_lines):
210
+ formatted_content.append({
211
+ "text": " " + wrapped_line if idx == 0 else " " + wrapped_line,
212
+ "style": "answer",
213
+ "color": ANSWER_COLOR,
214
+ "size": FONT_SIZE_NORMAL
215
+ })
216
+
217
+ elif stripped.startswith("Score:"):
218
+ formatted_content.append({
219
+ "text": f" {stripped}",
220
+ "style": "score",
221
+ "color": SCORE_COLOR,
222
+ "bold": True,
223
+ "size": FONT_SIZE_NORMAL
224
+ })
225
+
226
+ elif stripped.startswith("Feedback:"):
227
+ # Wrap feedback text
228
+ feedback_text = stripped.replace("Feedback:", "", 1).strip()
229
+ wrapped_lines = wrapper.wrap(f"Feedback: {feedback_text}") if feedback_text else ["Feedback:"]
230
+
231
+ for idx, wrapped_line in enumerate(wrapped_lines):
232
+ formatted_content.append({
233
+ "text": " " + wrapped_line if idx == 0 else " " + wrapped_line,
234
+ "style": "feedback",
235
+ "color": FEEDBACK_COLOR,
236
+ "size": FONT_SIZE_NORMAL
237
+ })
238
+ else:
239
+ # Regular text
240
+ if stripped:
241
+ wrapped_lines = wrapper.wrap(line)
242
+ for wrapped_line in wrapped_lines:
243
+ formatted_content.append({
244
+ "text": wrapped_line,
245
+ "style": "normal",
246
+ "color": "black",
247
+ "size": FONT_SIZE_NORMAL
248
+ })
249
+ else:
250
+ formatted_content.append({"text": "", "style": "normal"})
251
+
252
+ # Calculate lines per page based on actual line height
253
+ usable_height = PAGE_HEIGHT - MARGIN_TOP - MARGIN_BOTTOM
254
+ lines_per_page = int(usable_height / (LINE_HEIGHT * PAGE_HEIGHT))
255
+
256
+ # Create PDF
257
+ with PdfPages(buffer) as pdf:
258
+ page_start = 0
259
+ page_num = 1
260
+
261
+ while page_start < len(formatted_content):
262
+ # Create figure
263
+ fig = plt.figure(figsize=(PAGE_WIDTH, PAGE_HEIGHT))
264
+ fig.patch.set_facecolor('white')
265
+ ax = fig.add_subplot(111)
266
+ ax.axis('off')
267
+
268
+ # Add subtle page border
269
+ border = mpatches.Rectangle(
270
+ (0.5, 0.5), PAGE_WIDTH - 1, PAGE_HEIGHT - 1,
271
+ fill=False, edgecolor='#BDC3C7', linewidth=0.5
272
+ )
273
+ ax.add_patch(border)
274
+
275
+ # Add page content
276
+ y_position = 1 - MARGIN_TOP / PAGE_HEIGHT
277
+ lines_on_page = 0
278
+
279
+ for idx in range(page_start, min(page_start + lines_per_page, len(formatted_content))):
280
+ item = formatted_content[idx]
281
+
282
+ # Apply text styling
283
+ weight = 'bold' if item.get('bold', False) else 'normal'
284
+ size = item.get('size', FONT_SIZE_NORMAL)
285
+ color = item.get('color', 'black')
286
+
287
+ # Add text to page
288
+ ax.text(
289
+ MARGIN_LEFT / PAGE_WIDTH,
290
+ y_position,
291
+ item['text'],
292
+ transform=ax.transAxes,
293
+ fontsize=size,
294
+ fontweight=weight,
295
+ color=color,
296
+ fontfamily='DejaVu Sans',
297
+ verticalalignment='top'
298
+ )
299
+
300
+ # Move to next line
301
+ y_position -= LINE_HEIGHT
302
+ lines_on_page += 1
303
+
304
+ # Check if we need a new page (with some buffer)
305
+ if y_position < MARGIN_BOTTOM / PAGE_HEIGHT + 0.05:
306
+ break
307
+
308
+ # Add page number
309
+ ax.text(
310
+ 0.5,
311
+ MARGIN_BOTTOM / PAGE_HEIGHT / 2,
312
+ f"Page {page_num}",
313
+ transform=ax.transAxes,
314
+ fontsize=9,
315
+ color='#7F8C8D',
316
+ horizontalalignment='center'
317
+ )
318
+
319
+ # Save page
320
+ pdf.savefig(fig, bbox_inches='tight', pad_inches=0)
321
+ plt.close(fig)
322
+
323
+ # Move to next page
324
+ page_start += lines_on_page
325
+ page_num += 1
326
+
327
+ buffer.seek(0)
328
+ return buffer
329
+
330
+ def create_pdf_report_advanced(report_text: str) -> BytesIO:
331
  """
332
  Alternative implementation using reportlab for better PDF generation.
333
  Install with: pip install reportlab
 
412
  doc.build(story)
413
  buffer.seek(0)
414
  return buffer
415
+
416
+ except ImportError:
417
+ # Fallback to matplotlib version
418
+ return create_pdf_report(report_text)
419
 
420
  __all__ = ['generate_llm_interview_report', 'create_pdf_report']