husseinelsaadi commited on
Commit
27994de
·
1 Parent(s): 9039967
app.py CHANGED
@@ -37,7 +37,7 @@ app = Flask(
37
  instance_path=safe_instance_path # ✅ points to writable '/tmp/flask_instance'
38
  )
39
 
40
- app.config['SECRET_KEY'] = 'your-secret-key'
41
 
42
  # -----------------------------------------------------------------------------
43
  # Cookie configuration for Hugging Face Spaces
@@ -109,31 +109,54 @@ def apply(job_id):
109
  # Retrieve the uploaded resume file from the request. The ``name``
110
  # attribute in the HTML form is ``resume``.
111
  file = request.files.get('resume')
112
- # Use our safe upload helper to store the resume and obtain an empty
113
- # features dictionary. ``filepath`` contains the location where the
114
- # file was saved, allowing us to persist a reference in the database.
 
115
  features, error, filepath = handle_resume_upload(file)
116
 
117
  # If there was an error saving the resume, notify the user. We no
118
- # longer attempt to parse the resume contents, so an empty
119
- # features dictionary is considered valid.
120
  if error:
121
  flash("Resume upload failed. Please try again.", "danger")
122
  return render_template('apply.html', job=job)
123
 
124
- # Ensure features is a dictionary for JSON serialization. An empty
125
- # dictionary results in a non-empty JSON string ("{}"), which is
126
- # truthy and enables the interview feature on the applications page.
127
- if not features:
128
- features = {}
129
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  application = Application(
131
  job_id=job_id,
132
  user_id=current_user.id,
133
  name=current_user.username,
134
  email=current_user.email,
135
  resume_path=filepath,
136
- extracted_features=json.dumps(features)
137
  )
138
 
139
  db.session.add(application)
 
37
  instance_path=safe_instance_path # ✅ points to writable '/tmp/flask_instance'
38
  )
39
 
40
+ app.config['SECRET_KEY'] = 'saadi'
41
 
42
  # -----------------------------------------------------------------------------
43
  # Cookie configuration for Hugging Face Spaces
 
109
  # Retrieve the uploaded resume file from the request. The ``name``
110
  # attribute in the HTML form is ``resume``.
111
  file = request.files.get('resume')
112
+ # Use our safe upload helper to store the resume. ``filepath``
113
+ # contains the location where the file was saved so that recruiters
114
+ # can download it later. Resume parsing has been disabled, so
115
+ # ``features`` will always be an empty dictionary.
116
  features, error, filepath = handle_resume_upload(file)
117
 
118
  # If there was an error saving the resume, notify the user. We no
119
+ # longer attempt to parse the resume contents, so the manual fields
120
+ # collected below will form the entire feature set.
121
  if error:
122
  flash("Resume upload failed. Please try again.", "danger")
123
  return render_template('apply.html', job=job)
124
 
125
+ # Collect the manually entered fields for skills, experience and education.
126
+ # Users can separate entries with commas, semicolons or newlines; we
127
+ # normalise the input into lists of trimmed strings.
128
+ def parse_entries(raw_value: str):
129
+ import re
130
+ entries = []
131
+ if raw_value:
132
+ # Split on commas, semicolons or newlines
133
+ for item in re.split(r'[\n,;]+', raw_value):
134
+ item = item.strip()
135
+ if item:
136
+ entries.append(item)
137
+ return entries
138
+
139
+ skills_input = request.form.get('skills', '')
140
+ experience_input = request.form.get('experience', '')
141
+ education_input = request.form.get('education', '')
142
+
143
+ manual_features = {
144
+ "skills": parse_entries(skills_input),
145
+ "experience": parse_entries(experience_input),
146
+ "education": parse_entries(education_input)
147
+ }
148
+
149
+ # Prepare the application record. We ignore the empty ``features``
150
+ # returned by ``handle_resume_upload`` and instead persist the
151
+ # manually collected attributes. The extracted_features column
152
+ # expects a JSON string; json.dumps handles proper serialization.
153
  application = Application(
154
  job_id=job_id,
155
  user_id=current_user.id,
156
  name=current_user.username,
157
  email=current_user.email,
158
  resume_path=filepath,
159
+ extracted_features=json.dumps(manual_features)
160
  )
161
 
162
  db.session.add(application)
backend/routes/interview_api.py CHANGED
@@ -148,12 +148,25 @@ def process_answer():
148
  audio_url = None
149
 
150
  if not is_complete:
151
- # Generate next question based on question index
 
 
 
 
 
 
 
 
152
  if question_idx == 0:
153
  next_question_text = "Can you describe a challenging project you've worked on and how you overcame the difficulties?"
154
  elif question_idx == 1:
155
- next_question_text = "What are your career goals and how does this position align with them?"
 
 
 
 
156
  else:
 
157
  next_question_text = "Do you have any questions about the role or our company?"
158
 
159
  # Try to generate audio for the next question
 
148
  audio_url = None
149
 
150
  if not is_complete:
151
+ # Generate the next question based on the current question index.
152
+ #
153
+ # Question indices are zero‑based: 0 for the first follow‑up question,
154
+ # 1 for the second, and so on. We want the final (third) question
155
+ # delivered by this route to always probe the candidate's salary
156
+ # expectations and preferred working arrangement. After the user
157
+ # answers this question (i.e. when ``question_idx`` becomes 2), the
158
+ # interview is considered complete and no further questions are
159
+ # generated.
160
  if question_idx == 0:
161
  next_question_text = "Can you describe a challenging project you've worked on and how you overcame the difficulties?"
162
  elif question_idx == 1:
163
+ # Salary expectations question for the final interview round
164
+ next_question_text = (
165
+ "What are your salary expectations? Are you looking for a full-time or part-time role, "
166
+ "and do you prefer remote or on-site work?"
167
+ )
168
  else:
169
+ # Fallback for unexpected indices; ask if the candidate has any questions.
170
  next_question_text = "Do you have any questions about the role or our company?"
171
 
172
  # Try to generate audio for the next question
backend/services/interview_engine.py CHANGED
@@ -106,6 +106,8 @@ def generate_first_question(profile, job):
106
 
107
  Generate an appropriate opening interview question that is professional and relevant.
108
  Keep it concise and clear. Respond with ONLY the question text, no additional formatting.
 
 
109
  """
110
 
111
  response = groq_llm.invoke(prompt)
 
106
 
107
  Generate an appropriate opening interview question that is professional and relevant.
108
  Keep it concise and clear. Respond with ONLY the question text, no additional formatting.
109
+ If the interview is for a technical role, focus on technical skills. Make the question related
110
+ to the job role and the candidate's background and the previous question.
111
  """
112
 
113
  response = groq_llm.invoke(prompt)
backend/templates/apply.html CHANGED
@@ -33,9 +33,29 @@
33
  <form method="POST" enctype="multipart/form-data">
34
  <div class="form-group">
35
  <label for="resume">Upload Resume</label>
 
36
  <input type="file" name="resume" id="resume" class="form-control" required accept=".pdf,.doc,.docx">
37
  </div>
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  <div class="application-actions" style="margin-top: 2rem;">
40
  <button type="submit" class="btn btn-primary">Submit Application</button>
41
  </div>
 
33
  <form method="POST" enctype="multipart/form-data">
34
  <div class="form-group">
35
  <label for="resume">Upload Resume</label>
36
+ <!-- Resume upload remains mandatory. The file will be stored for recruiter review but is no longer parsed automatically. -->
37
  <input type="file" name="resume" id="resume" class="form-control" required accept=".pdf,.doc,.docx">
38
  </div>
39
 
40
+ <!--
41
+ Collect the candidate's skills, experience and education manually.
42
+ These fields allow applicants to highlight their background even when resume
43
+ parsing is disabled. Entries can be separated by commas or newlines; the
44
+ backend will normalise them into lists.
45
+ -->
46
+ <div class="form-group">
47
+ <label for="skills">Skills</label>
48
+ <textarea name="skills" id="skills" class="form-control" rows="3" placeholder="e.g. Python, Data Analysis, Project Management" required></textarea>
49
+ </div>
50
+ <div class="form-group">
51
+ <label for="experience">Experience</label>
52
+ <textarea name="experience" id="experience" class="form-control" rows="3" placeholder="e.g. 3 years at TechCorp as a Backend Developer" required></textarea>
53
+ </div>
54
+ <div class="form-group">
55
+ <label for="education">Education</label>
56
+ <textarea name="education" id="education" class="form-control" rows="3" placeholder="e.g. B.Sc. in Computer Science, M.Sc. in Data Science" required></textarea>
57
+ </div>
58
+
59
  <div class="application-actions" style="margin-top: 2rem;">
60
  <button type="submit" class="btn btn-primary">Submit Application</button>
61
  </div>