husseinelsaadi commited on
Commit
6720344
·
1 Parent(s): 32acb92
backend/models/database.py CHANGED
@@ -61,6 +61,7 @@ class Application(db.Model):
61
  extracted_features = db.Column(db.Text, nullable=True)
62
  status = db.Column(db.String(50), default='applied')
63
  date_applied = db.Column(db.DateTime, default=datetime.utcnow)
 
64
 
65
  user = db.relationship('User', backref='applications')
66
 
 
61
  extracted_features = db.Column(db.Text, nullable=True)
62
  status = db.Column(db.String(50), default='applied')
63
  date_applied = db.Column(db.DateTime, default=datetime.utcnow)
64
+ interview_log = Column(Text)
65
 
66
  user = db.relationship('User', backref='applications')
67
 
backend/routes/interview_api.py CHANGED
@@ -177,7 +177,7 @@ def download_report(application_id: int):
177
  pdf_buffer = create_pdf_report(report_text)
178
  pdf_buffer.seek(0)
179
 
180
- filename = f"interview_report_{application.id}.pdf"
181
  return send_file(
182
  pdf_buffer,
183
  download_name=filename,
@@ -215,6 +215,32 @@ def process_answer():
215
  # Evaluate the answer
216
  evaluation_result = evaluate_answer(current_question, answer)
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  # Determine the number of questions configured for this job
219
  total_questions = 3
220
  if job_id is not None:
 
177
  pdf_buffer = create_pdf_report(report_text)
178
  pdf_buffer.seek(0)
179
 
180
+ filename = f"{application.name.replace(' ', '_')}_interview_report.pdf"
181
  return send_file(
182
  pdf_buffer,
183
  download_name=filename,
 
215
  # Evaluate the answer
216
  evaluation_result = evaluate_answer(current_question, answer)
217
 
218
+ # 🔥 Save Q&A in interview_log for report
219
+ try:
220
+ application = Application.query.filter_by(
221
+ user_id=current_user.id,
222
+ job_id=job_id
223
+ ).first()
224
+
225
+ if application:
226
+ log_data = []
227
+ if application.interview_log:
228
+ try:
229
+ log_data = json.loads(application.interview_log)
230
+ except Exception:
231
+ log_data = []
232
+
233
+ log_data.append({
234
+ "question": current_question,
235
+ "answer": answer,
236
+ "evaluation": evaluation_result
237
+ })
238
+
239
+ application.interview_log = json.dumps(log_data, ensure_ascii=False)
240
+ db.session.commit()
241
+ except Exception as log_err:
242
+ logging.error(f"Error saving interview log: {log_err}")
243
+
244
  # Determine the number of questions configured for this job
245
  total_questions = 3
246
  if job_id is not None:
backend/services/report_generator.py CHANGED
@@ -118,66 +118,72 @@ def generate_llm_interview_report(application) -> str:
118
  lines.append(f' Match Ratio: {ratio * 100:.0f}%')
119
  lines.append(f' Score: {score_label}')
120
  lines.append('')
121
- lines.append('Additional Notes:')
122
- lines.append('This report was generated automatically based on the information provided in the application.')
123
- lines.append('Interview question and answer details are not currently stored on the server.')
124
- lines.append('Future versions may include a detailed breakdown of interview responses and evaluator feedback.')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  return '\n'.join(lines)
127
 
128
 
129
  def create_pdf_report(report_text: str) -> BytesIO:
130
- """Convert a plain‑text report into a PDF document.
131
-
132
- This helper uses Matplotlib's ``PdfPages`` backend to compose a PDF
133
- containing the supplied text. Lines are wrapped to a reasonable
134
- width and paginated as needed. The returned ``BytesIO`` object can
135
- be passed directly to Flask's ``send_file`` function.
136
-
137
- Parameters
138
- ----------
139
- report_text : str
140
- The full contents of the report to be included in the PDF.
141
-
142
- Returns
143
- -------
144
- BytesIO
145
- A file‑like buffer containing the PDF data. The caller is
146
- responsible for rewinding the buffer (via ``seek(0)``) before
147
- sending it over HTTP.
148
- """
149
  buffer = BytesIO()
150
 
151
- # Split the input into lines and wrap long lines for better layout
152
- raw_lines = report_text.split('\n')
153
- wrapper = textwrap.TextWrapper(width=90, break_long_words=True, replace_whitespace=False)
154
- wrapped_lines: List[str] = []
 
155
  for line in raw_lines:
156
- if not line:
157
- wrapped_lines.append('')
158
- continue
159
- # Wrap each line individually; the wrapper returns a list
160
- segments = wrapper.wrap(line)
161
- if segments:
162
- wrapped_lines.extend(segments)
 
 
 
 
 
 
 
163
  else:
164
- wrapped_lines.append(line)
165
-
166
- # Determine how many lines to place on each PDF page. The value
167
- # of 40 lines per page fits comfortably on an A4 sheet using the
168
- # selected font size and margins.
169
- lines_per_page = 40
170
 
 
 
171
  with PdfPages(buffer) as pdf:
172
- for i in range(0, len(wrapped_lines), lines_per_page):
173
- fig = plt.figure(figsize=(8.27, 11.69)) # A4 portrait in inches
174
  fig.patch.set_facecolor('white')
175
- # Compose the block of text for this page
176
- page_text = '\n'.join(wrapped_lines[i:i + lines_per_page])
177
- # Place the text near the top-left corner. ``va='top'``
178
- # ensures the first line starts at the specified y
179
- fig.text(0.1, 0.95, page_text, va='top', ha='left', fontsize=10, family='monospace')
180
- # Save and close the figure to free memory
181
  pdf.savefig(fig)
182
  plt.close(fig)
183
 
 
118
  lines.append(f' Match Ratio: {ratio * 100:.0f}%')
119
  lines.append(f' Score: {score_label}')
120
  lines.append('')
121
+ lines.append('Interview Transcript & Evaluation:')
122
+ try:
123
+ if application.interview_log:
124
+ try:
125
+ qa_log = json.loads(application.interview_log)
126
+ except Exception:
127
+ qa_log = []
128
+
129
+ if qa_log:
130
+ for idx, entry in enumerate(qa_log, 1):
131
+ q = entry.get("question", "N/A")
132
+ a = entry.get("answer", "N/A")
133
+ eval_score = entry.get("evaluation", {}).get("score", "N/A")
134
+ eval_feedback = entry.get("evaluation", {}).get("feedback", "N/A")
135
+
136
+ lines.append(f"\nQuestion {idx}: {q}")
137
+ lines.append(f"Answer: {a}")
138
+ lines.append(f"Score: {eval_score}")
139
+ lines.append(f"Feedback: {eval_feedback}")
140
+ else:
141
+ lines.append("No interview log data recorded.")
142
+ else:
143
+ lines.append("No interview log data recorded.")
144
+ except Exception as e:
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
+ """Convert a formatted report into a visually clear PDF with bold Qs and indented As."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  buffer = BytesIO()
153
 
154
+ # Prepare wrapped lines
155
+ raw_lines = report_text.split("\n")
156
+ wrapper = textwrap.TextWrapper(width=85, break_long_words=True, replace_whitespace=False)
157
+ formatted_lines: List[str] = []
158
+
159
  for line in raw_lines:
160
+ # Highlight Questions
161
+ if line.strip().startswith("Question"):
162
+ formatted_lines.append("") # Extra spacing before new question
163
+ formatted_lines.append(f"**{line.strip()}**") # Bold style marker
164
+ elif line.strip().startswith("Answer:"):
165
+ # Indent answers for clarity
166
+ answer_text = line.replace("Answer:", "").strip()
167
+ wrapped_answer = wrapper.wrap(answer_text)
168
+ formatted_lines.append(f" Answer: {wrapped_answer[0]}")
169
+ for extra in wrapped_answer[1:]:
170
+ formatted_lines.append(f" {extra}")
171
+ elif line.strip().startswith("Score:") or line.strip().startswith("Feedback:"):
172
+ # Keep score & feedback on separate lines
173
+ formatted_lines.append(f" {line.strip()}")
174
  else:
175
+ # Wrap other lines normally
176
+ wrapped = wrapper.wrap(line)
177
+ formatted_lines.extend(wrapped if wrapped else [""])
 
 
 
178
 
179
+ # PDF creation
180
+ lines_per_page = 38
181
  with PdfPages(buffer) as pdf:
182
+ for i in range(0, len(formatted_lines), lines_per_page):
183
+ fig = plt.figure(figsize=(8.27, 11.69)) # A4
184
  fig.patch.set_facecolor('white')
185
+ page_text = "\n".join(formatted_lines[i:i + lines_per_page])
186
+ fig.text(0.1, 0.95, page_text, va="top", ha="left", fontsize=10, family="monospace")
 
 
 
 
187
  pdf.savefig(fig)
188
  plt.close(fig)
189