Kaadan commited on
Commit
29ada2b
·
1 Parent(s): 070287e

fix question generation

Browse files
backend/QWEN.md CHANGED
@@ -10,6 +10,7 @@ The application follows a clean architecture with proper separation of concerns:
10
  - **Database Layer**: Manages database connections and sessions
11
  - **Model Layer**: Defines database models using SQLAlchemy
12
  - **Schema Layer**: Defines Pydantic schemas for request/response validation
 
13
 
14
  ## Technologies Used
15
 
@@ -20,6 +21,9 @@ The application follows a clean architecture with proper separation of concerns:
20
  - **Alembic**: Database migration tool
21
  - **Pydantic**: Data validation and settings management
22
  - **UUID**: For generating unique identifiers
 
 
 
23
 
24
  ## Architecture Components
25
 
@@ -34,6 +38,8 @@ backend/
34
  │ └── routes.py # Root and health check endpoints
35
  ├── database/ # Database connection utilities
36
  │ └── database.py # Database engine and session management
 
 
37
  ├── models/ # SQLAlchemy models
38
  │ ├── user.py # User model
39
  │ ├── job.py # Job model
@@ -45,13 +51,17 @@ backend/
45
  │ ├── job.py # Job schemas
46
  │ ├── assessment.py # Assessment schemas
47
  │ ├── application.py # Application schemas
 
48
  │ └── base.py # Base schema class
49
  ├── services/ # Business logic layer
50
  │ ├── user_service.py # User-related services
51
  │ ├── job_service.py # Job-related services
52
  │ ├── assessment_service.py # Assessment-related services
53
  │ ├── application_service.py # Application-related services
 
54
  │ └── base_service.py # Generic service functions
 
 
55
  ├── alembic/ # Database migration files
56
  ├── config.py # Application configuration
57
  ├── logging_config.py # Logging configuration
@@ -63,21 +73,31 @@ backend/
63
  ### Key Features
64
 
65
  1. **User Management**:
66
- - Registration and authentication
67
- - Role-based access (HR vs Applicant)
 
68
 
69
  2. **Job Management**:
70
  - Create, update, delete job postings
71
  - Manage job details and requirements
 
72
 
73
- 3. **Assessment Management**:
74
- - Create assessments linked to jobs
75
- - Define questions and passing scores
76
- - Regenerate assessments with new questions
 
 
77
 
78
  4. **Application Management**:
79
  - Submit applications with answers
 
80
  - Track application results and scores
 
 
 
 
 
81
 
82
  ### API Endpoints
83
 
@@ -99,14 +119,20 @@ backend/
99
  #### Assessments
100
  - `GET /assessments/jobs/{jid}` - List assessments for a job
101
  - `GET /assessments/jobs/{jid}/{aid}` - Get assessment details
102
- - `POST /assessments/jobs/{id}` - Create assessment
103
- - `PATCH /assessments/jobs/{jid}/{aid}/regenerate` - Regenerate assessment
104
  - `PATCH /assessments/jobs/{jid}/{aid}` - Update assessment
105
  - `DELETE /assessments/jobs/{jid}/{aid}` - Delete assessment
106
 
107
  #### Applications
108
- - `GET /applications/jobs/{jid}/assessments/{aid}` - List applications
 
109
  - `POST /applications/jobs/{jid}/assessments/{aid}` - Create application
 
 
 
 
 
110
 
111
  #### Health Check
112
  - `GET /` - Root endpoint
@@ -130,11 +156,14 @@ LOG_LEVEL=INFO
130
  LOG_FILE=app.log
131
  LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
132
 
133
- # JWT Configuration (for future use)
134
  SECRET_KEY=your-secret-key-here
135
  ALGORITHM=HS256
136
  ACCESS_TOKEN_EXPIRE_MINUTES=30
137
 
 
 
 
138
  # Application Configuration
139
  APP_NAME=AI-Powered Hiring Assessment Platform
140
  APP_VERSION=0.1.0
@@ -146,6 +175,7 @@ APP_DESCRIPTION=MVP for managing hiring assessments using AI
146
  ### Prerequisites
147
  - Python 3.11+
148
  - pip package manager
 
149
 
150
  ### Setup Instructions
151
 
@@ -153,7 +183,7 @@ APP_DESCRIPTION=MVP for managing hiring assessments using AI
153
  ```bash
154
  pip install -r requirements.txt
155
  ```
156
-
157
  2. **Set Up Environment Variables**:
158
  Copy the `.env.example` file to `.env` and adjust the values as needed.
159
 
@@ -166,7 +196,7 @@ APP_DESCRIPTION=MVP for managing hiring assessments using AI
166
  ```bash
167
  python main.py
168
  ```
169
-
170
  Or using uvicorn directly:
171
  ```bash
172
  uvicorn main:app --host 0.0.0.0 --port 8000 --reload
@@ -180,9 +210,9 @@ uvicorn main:app --reload --host 0.0.0.0 --port 8000
180
 
181
  ## Testing
182
 
183
- To run tests (when available):
184
  ```bash
185
- pytest
186
  ```
187
 
188
  ## Logging
@@ -213,28 +243,56 @@ The application uses Alembic for database migrations:
213
  - Log errors appropriately
214
 
215
  3. **Security**:
216
- - Passwords should be hashed (currently using placeholder)
217
  - Input validation through Pydantic schemas
218
  - SQL injection prevention through SQLAlchemy ORM
 
219
 
220
  4. **Architecture**:
221
  - Keep business logic in service layer
222
  - Use dependency injection for database sessions
223
  - Separate API routes by domain/model
224
  - Maintain clear separation between layers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  ## Future Enhancements
227
 
228
- - JWT token-based authentication
229
- - Password hashing implementation
230
- - Advanced assessment features
231
- - Admin dashboard endpoints
232
- - More sophisticated logging and monitoring
233
- - Unit and integration tests
234
-
235
- # TODO:
236
- - when creating an assessment we should pass the questions of the assessment.
237
- - all APIs input and output should have a cleare schema, even the enums should be clear and apear in the swagger apis (when visiting /docs)
238
- - the validation of the inputs should be done by pydantic and in the model level, not in the model level only!
239
- - the answers is not a model itself, so the services/answer functions should be aware of that.
 
 
 
 
240
 
 
10
  - **Database Layer**: Manages database connections and sessions
11
  - **Model Layer**: Defines database models using SQLAlchemy
12
  - **Schema Layer**: Defines Pydantic schemas for request/response validation
13
+ - **Integration Layer**: Handles external services like AI providers
14
 
15
  ## Technologies Used
16
 
 
21
  - **Alembic**: Database migration tool
22
  - **Pydantic**: Data validation and settings management
23
  - **UUID**: For generating unique identifiers
24
+ - **Mistral AI**: AI provider for generating questions and scoring answers
25
+ - **JWT**: For authentication and authorization
26
+ - **bcrypt**: For password hashing
27
 
28
  ## Architecture Components
29
 
 
38
  │ └── routes.py # Root and health check endpoints
39
  ├── database/ # Database connection utilities
40
  │ └── database.py # Database engine and session management
41
+ ├── integrations/ # External service integrations
42
+ │ └── ai_integration/ # AI provider implementations
43
  ├── models/ # SQLAlchemy models
44
  │ ├── user.py # User model
45
  │ ├── job.py # Job model
 
51
  │ ├── job.py # Job schemas
52
  │ ├── assessment.py # Assessment schemas
53
  │ ├── application.py # Application schemas
54
+ │ ├── enums.py # Enum definitions
55
  │ └── base.py # Base schema class
56
  ├── services/ # Business logic layer
57
  │ ├── user_service.py # User-related services
58
  │ ├── job_service.py # Job-related services
59
  │ ├── assessment_service.py # Assessment-related services
60
  │ ├── application_service.py # Application-related services
61
+ │ ├── ai_service.py # AI-related services
62
  │ └── base_service.py # Generic service functions
63
+ ├── utils/ # Utility functions
64
+ │ └── dependencies.py # Dependency injection functions
65
  ├── alembic/ # Database migration files
66
  ├── config.py # Application configuration
67
  ├── logging_config.py # Logging configuration
 
73
  ### Key Features
74
 
75
  1. **User Management**:
76
+ - Registration and authentication with role-based access (HR vs Applicant)
77
+ - JWT-based secure session management
78
+ - Password hashing using bcrypt
79
 
80
  2. **Job Management**:
81
  - Create, update, delete job postings
82
  - Manage job details and requirements
83
+ - Track applicant counts
84
 
85
+ 3. **AI-Powered Assessment Management**:
86
+ - Create assessments with AI-generated questions based on job requirements
87
+ - Define question types (multiple choice single answer, multiple choice multiple answers, text-based)
88
+ - Regenerate assessments with new AI-generated questions
89
+ - Automatic duration estimation based on content using AI
90
+ - Passing score configuration (range 20-80)
91
 
92
  4. **Application Management**:
93
  - Submit applications with answers
94
+ - AI-powered scoring of text-based answers with rationales
95
  - Track application results and scores
96
+ - Detailed feedback with AI-generated rationales
97
+
98
+ 5. **Dashboard Features**:
99
+ - View application scores with sorting options
100
+ - Monitor assessment performance
101
 
102
  ### API Endpoints
103
 
 
119
  #### Assessments
120
  - `GET /assessments/jobs/{jid}` - List assessments for a job
121
  - `GET /assessments/jobs/{jid}/{aid}` - Get assessment details
122
+ - `POST /assessments/jobs/{id}` - Create assessment with AI-generated questions
123
+ - `PATCH /assessments/jobs/{jid}/{aid}/regenerate` - Regenerate assessment with new AI-generated questions
124
  - `PATCH /assessments/jobs/{jid}/{aid}` - Update assessment
125
  - `DELETE /assessments/jobs/{jid}/{aid}` - Delete assessment
126
 
127
  #### Applications
128
+ - `GET /applications/jobs/{jid}/assessments/{aid}` - List applications for an assessment
129
+ - `GET /applications/jobs/{jid}/assessment_id/{aid}/applications/{id}` - Get detailed application
130
  - `POST /applications/jobs/{jid}/assessments/{aid}` - Create application
131
+ - `GET /applications/my-applications` - Get current user's applications
132
+ - `GET /applications/my-applications/{id}` - Get specific application for current user
133
+
134
+ #### Dashboard
135
+ - `GET /dashboard/applications/scores` - Get application scores with sorting options
136
 
137
  #### Health Check
138
  - `GET /` - Root endpoint
 
156
  LOG_FILE=app.log
157
  LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
158
 
159
+ # JWT Configuration
160
  SECRET_KEY=your-secret-key-here
161
  ALGORITHM=HS256
162
  ACCESS_TOKEN_EXPIRE_MINUTES=30
163
 
164
+ # AI Provider Configuration
165
+ MISTRAL_API_KEY=your-mistral-api-key-here
166
+
167
  # Application Configuration
168
  APP_NAME=AI-Powered Hiring Assessment Platform
169
  APP_VERSION=0.1.0
 
175
  ### Prerequisites
176
  - Python 3.11+
177
  - pip package manager
178
+ - Mistral AI API key (optional, for AI features)
179
 
180
  ### Setup Instructions
181
 
 
183
  ```bash
184
  pip install -r requirements.txt
185
  ```
186
+
187
  2. **Set Up Environment Variables**:
188
  Copy the `.env.example` file to `.env` and adjust the values as needed.
189
 
 
196
  ```bash
197
  python main.py
198
  ```
199
+
200
  Or using uvicorn directly:
201
  ```bash
202
  uvicorn main:app --host 0.0.0.0 --port 8000 --reload
 
210
 
211
  ## Testing
212
 
213
+ To run tests:
214
  ```bash
215
+ python -m pytest
216
  ```
217
 
218
  ## Logging
 
243
  - Log errors appropriately
244
 
245
  3. **Security**:
246
+ - Passwords are hashed using bcrypt
247
  - Input validation through Pydantic schemas
248
  - SQL injection prevention through SQLAlchemy ORM
249
+ - JWT-based authentication and authorization
250
 
251
  4. **Architecture**:
252
  - Keep business logic in service layer
253
  - Use dependency injection for database sessions
254
  - Separate API routes by domain/model
255
  - Maintain clear separation between layers
256
+ - Use enums for fixed values to ensure consistency
257
+
258
+ 5. **AI Integration**:
259
+ - Abstract AI provider implementations behind interfaces
260
+ - Use factory pattern for AI provider selection
261
+ - Implement fallback mechanisms for AI services
262
+
263
+ ## Implemented Features
264
+
265
+ - ✅ JWT token-based authentication
266
+ - ✅ Password hashing implementation using bcrypt
267
+ - ✅ AI-powered question generation based on job requirements
268
+ - ✅ AI-powered scoring of text-based answers with rationales
269
+ - ✅ Assessment duration estimation using AI
270
+ - ✅ Comprehensive API input/output validation with Pydantic schemas
271
+ - ✅ Proper enum definitions for consistent API contracts
272
+ - ✅ Role-based access control (HR vs Applicant)
273
+ - ✅ Detailed application feedback with AI-generated rationales
274
+ - ✅ My Applications endpoint for candidates to track their submissions
275
+ - ✅ Dashboard endpoints for viewing application scores
276
+ - ✅ Assessment regeneration functionality
277
+ - ✅ Proper handling of answers as JSON data within applications
278
+ - ✅ Comprehensive logging throughout the application
279
 
280
  ## Future Enhancements
281
 
282
+ - Enhanced AI scoring with more sophisticated models
283
+ - Advanced analytics and reporting features
284
+ - More sophisticated assessment types
285
+ - Integration with additional AI providers
286
+ - Performance optimizations for large datasets
287
+ - Unit and integration tests coverage
288
+ - Enhanced error handling and retry mechanisms
289
+ - Rate limiting for API endpoints
290
+ - Audit logging for compliance requirements
291
+
292
+ ## Completed TODO Items
293
+
294
+ - ✅ When creating an assessment, questions are now generated using AI based on job requirements and specified question types
295
+ - ✅ All APIs now have clear input/output schemas with enums properly defined and visible in Swagger documentation
296
+ - ✅ Input validation is now done at both the Pydantic schema level and model level
297
+ - ✅ Answers are properly handled as part of the application model rather than as a separate model
298
 
backend/integrations/ai_integration/mistral_generator.py CHANGED
@@ -193,18 +193,42 @@ Job Information:
193
  if additional_note:
194
  job_details += f"- Additional Note: {additional_note}\n"
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  prompt = f"""
197
  You are an assessment generator.
198
 
199
- Generate EXACTLY {len(questions_types)} questions for the following job.
200
 
201
  {job_details}
202
 
203
  MANDATORY RULES:
204
- 1. Output MUST be a JSON ARRAY with EXACTLY {len(questions_types)} objects.
205
- 2. The list MUST contain:
206
- - {mcq_count} MCQ questions (multiple choice)
207
- - {text_count} TEXT questions (text-based)
208
  3. Do NOT include explanations or markdown.
209
  4. Follow the schema EXACTLY.
210
 
@@ -223,6 +247,12 @@ Rules per type:
223
  - MCQ → 4 choices + correct_answer as the text of the correct choice
224
  - TEXT → correct_answer = null
225
 
 
 
 
 
 
 
226
  Return ONLY the JSON array.
227
  """
228
 
@@ -240,8 +270,9 @@ Return ONLY the JSON array.
240
 
241
  # Determine the question type based on the response
242
  if q_data.get("type") == "MCQ":
243
- # For multiple choice questions
244
- question_type = QuestionType.choose_one # Default to choose_one
 
245
 
246
  # Create options
247
  options = []
@@ -249,7 +280,7 @@ Return ONLY the JSON array.
249
  option = AssessmentQuestionOption(text=choice, value=choice)
250
  options.append(option)
251
 
252
- # Find the correct option
253
  correct_options = []
254
  correct_answer = q_data.get("correct_answer")
255
  if correct_answer:
 
193
  if additional_note:
194
  job_details += f"- Additional Note: {additional_note}\n"
195
 
196
+ # Determine the recommended number of questions based on job complexity
197
+ if job_info:
198
+ # Adjust the number of questions based on job seniority and skills
199
+ seniority = job_info.get('seniority', '').lower()
200
+ skill_count = len(job_info.get('skill_categories', []))
201
+
202
+ # Base number of questions based on complexity
203
+ if seniority in ['senior', 'lead']:
204
+ base_questions = 15 # More questions for senior roles
205
+ elif seniority in ['mid', 'intermediate']:
206
+ base_questions = 12
207
+ else: # intern, junior
208
+ base_questions = 10
209
+
210
+ # Adjust based on number of skills to cover
211
+ adjusted_questions = base_questions + (skill_count // 2)
212
+
213
+ # Ensure we have at least one of each requested type if specified
214
+ min_questions = len(questions_types) # At least one per type requested
215
+ total_questions = max(adjusted_questions, min_questions)
216
+ else:
217
+ # Default if no job info is provided
218
+ total_questions = max(10, len(questions_types)) # At least 10 or requested types count
219
+
220
  prompt = f"""
221
  You are an assessment generator.
222
 
223
+ Generate approximately {total_questions} questions for the following job. The number of questions should be appropriate for the job complexity and seniority level.
224
 
225
  {job_details}
226
 
227
  MANDATORY RULES:
228
+ 1. Output MUST be a JSON ARRAY with approximately {total_questions} objects.
229
+ 2. Distribute the questions among the requested types proportionally:
230
+ - Include MCQ questions (multiple choice) - both single and multiple answer types
231
+ - Include TEXT questions (text-based)
232
  3. Do NOT include explanations or markdown.
233
  4. Follow the schema EXACTLY.
234
 
 
247
  - MCQ → 4 choices + correct_answer as the text of the correct choice
248
  - TEXT → correct_answer = null
249
 
250
+ Consider the following when generating questions:
251
+ - For senior positions, include more complex and scenario-based questions
252
+ - For junior positions, focus on fundamental concepts
253
+ - Ensure questions cover the skill categories mentioned in the job description
254
+ - Mix difficulty levels appropriately for the role
255
+
256
  Return ONLY the JSON array.
257
  """
258
 
 
270
 
271
  # Determine the question type based on the response
272
  if q_data.get("type") == "MCQ":
273
+ # For multiple choice questions, determine if it's single or multiple choice
274
+ # For now, default to choose_one, but we could enhance this logic later
275
+ question_type = QuestionType.choose_one
276
 
277
  # Create options
278
  options = []
 
280
  option = AssessmentQuestionOption(text=choice, value=choice)
281
  options.append(option)
282
 
283
+ # Find the correct option(s)
284
  correct_options = []
285
  correct_answer = q_data.get("correct_answer")
286
  if correct_answer:
backend/integrations/ai_integration/mock_ai_generator.py CHANGED
@@ -13,39 +13,65 @@ class MockAIGenerator(AIGeneratorInterface):
13
  """
14
 
15
  def generate_questions(
16
- self,
17
- title: str,
18
- questions_types: List[str],
19
- additional_note: str = None,
20
  job_info: Dict[str, Any] = None
21
  ) -> List[AssessmentQuestion]:
22
  """
23
  Generate questions using mock AI logic based on job information.
24
  """
25
- num_questions = len(questions_types)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  generated_questions = []
27
-
28
- for i, q_type in enumerate(questions_types):
 
 
 
29
  # Create a question ID
30
  question_id = str(uuid.uuid4())
31
-
32
  # Generate question text based on the assessment title, job info and question type
33
  question_text = self._generate_question_text(title, q_type, i+1, additional_note, job_info)
34
-
35
  # Determine weight (random between 1-5)
36
  weight = random.randint(1, 5)
37
-
38
  # Generate skill categories based on the assessment title and job info
39
  skill_categories = self._generate_skill_categories(title, job_info)
40
-
41
  # Generate options and correct options based on the question type
42
  options = []
43
  correct_options = []
44
-
45
  if q_type in [QuestionType.choose_one.value, QuestionType.choose_many.value]:
46
  options = self._generate_multiple_choice_options(q_type, question_text)
47
  correct_options = self._select_correct_options(options, q_type)
48
-
49
  # Create the AssessmentQuestion object
50
  question = AssessmentQuestion(
51
  id=question_id,
@@ -56,9 +82,9 @@ class MockAIGenerator(AIGeneratorInterface):
56
  options=options,
57
  correct_options=correct_options
58
  )
59
-
60
  generated_questions.append(question)
61
-
62
  return generated_questions
63
 
64
  def _generate_question_text(self, title: str, q_type: str, question_number: int, additional_note: str = None, job_info: Dict[str, Any] = None) -> str:
 
13
  """
14
 
15
  def generate_questions(
16
+ self,
17
+ title: str,
18
+ questions_types: List[str],
19
+ additional_note: str = None,
20
  job_info: Dict[str, Any] = None
21
  ) -> List[AssessmentQuestion]:
22
  """
23
  Generate questions using mock AI logic based on job information.
24
  """
25
+ # Determine the recommended number of questions based on job complexity
26
+ if job_info:
27
+ # Adjust the number of questions based on job seniority and skills
28
+ seniority = job_info.get('seniority', '').lower()
29
+ skill_count = len(job_info.get('skill_categories', []))
30
+
31
+ # Base number of questions based on complexity
32
+ if seniority in ['senior', 'lead']:
33
+ base_questions = 15 # More questions for senior roles
34
+ elif seniority in ['mid', 'intermediate']:
35
+ base_questions = 12
36
+ else: # intern, junior
37
+ base_questions = 10
38
+
39
+ # Adjust based on number of skills to cover
40
+ adjusted_questions = base_questions + (skill_count // 2)
41
+
42
+ # Ensure we have at least one of each requested type if specified
43
+ min_questions = len(questions_types) # At least one per type requested
44
+ total_questions = max(adjusted_questions, min_questions)
45
+ else:
46
+ # Default if no job info is provided
47
+ total_questions = max(10, len(questions_types)) # At least 10 or requested types count
48
+
49
  generated_questions = []
50
+
51
+ for i in range(total_questions):
52
+ # Cycle through the requested question types to ensure variety
53
+ q_type = questions_types[i % len(questions_types)]
54
+
55
  # Create a question ID
56
  question_id = str(uuid.uuid4())
57
+
58
  # Generate question text based on the assessment title, job info and question type
59
  question_text = self._generate_question_text(title, q_type, i+1, additional_note, job_info)
60
+
61
  # Determine weight (random between 1-5)
62
  weight = random.randint(1, 5)
63
+
64
  # Generate skill categories based on the assessment title and job info
65
  skill_categories = self._generate_skill_categories(title, job_info)
66
+
67
  # Generate options and correct options based on the question type
68
  options = []
69
  correct_options = []
70
+
71
  if q_type in [QuestionType.choose_one.value, QuestionType.choose_many.value]:
72
  options = self._generate_multiple_choice_options(q_type, question_text)
73
  correct_options = self._select_correct_options(options, q_type)
74
+
75
  # Create the AssessmentQuestion object
76
  question = AssessmentQuestion(
77
  id=question_id,
 
82
  options=options,
83
  correct_options=correct_options
84
  )
85
+
86
  generated_questions.append(question)
87
+
88
  return generated_questions
89
 
90
  def _generate_question_text(self, title: str, q_type: str, question_number: int, additional_note: str = None, job_info: Dict[str, Any] = None) -> str: