Soufianesejjari commited on
Commit
b39667b
·
1 Parent(s): d3528b2

Add experience management and skill categorization to profile model

Browse files
Files changed (4) hide show
  1. agents/profile_extractor.py +85 -116
  2. app.py +126 -13
  3. models.py +18 -3
  4. services/storage_service.py +24 -5
agents/profile_extractor.py CHANGED
@@ -2,7 +2,7 @@
2
  Agent for extracting profile information from resumes
3
  """
4
  import groq
5
- from models import Profile, SocialMedia, Project, Skill, Education
6
  from typing import List, Dict, Any, Optional
7
  from langchain.output_parsers import PydanticOutputParser
8
  from langchain.prompts import PromptTemplate
@@ -61,7 +61,7 @@ class ProfileExtractor:
61
  logger.error(f"LangChain extraction failed: {e}")
62
  if settings.DEBUG:
63
  print(f"LangChain extraction failed: {e}")
64
- return self._extract_with_fallback(pdf_text)
65
 
66
  def _extract_with_langchain(self, pdf_text: str) -> Profile:
67
  """Extract profile with structured LangChain approach"""
@@ -75,8 +75,9 @@ class ProfileExtractor:
75
  5. Tagline (a short 5-10 word catchy phrase summarizing professional identity)
76
  6. Social media links (LinkedIn, GitHub, Instagram)
77
  7. Projects (with title, description, and tech stack)
78
- 8. Skills
79
  9. Education history (with school, degree, field of study, start date and end date)
 
80
 
81
  Return the information in the following JSON format:
82
  {
@@ -98,8 +99,9 @@ class ProfileExtractor:
98
  }
99
  ],
100
  "skills": [
101
- {"name": "Skill 1"},
102
- {"name": "Skill 2"}
 
103
  ],
104
  "educations": [
105
  {
@@ -109,6 +111,15 @@ class ProfileExtractor:
109
  "startDate": "Start Year",
110
  "endDate": "End Year or Present"
111
  }
 
 
 
 
 
 
 
 
 
112
  ]
113
  }
114
 
@@ -136,7 +147,7 @@ class ProfileExtractor:
136
  json_start = response_text.find('{')
137
  json_end = response_text.rfind('}') + 1
138
 
139
- if json_start >= 0 and json_end > json_start:
140
  json_str = response_text[json_start:json_end]
141
  profile_dict = json.loads(json_str)
142
  profile = Profile.model_validate(profile_dict)
@@ -217,122 +228,80 @@ class ProfileExtractor:
217
  logger.debug(f"Added education: {education}")
218
  except Exception as e:
219
  logger.error(f"Error extracting education: {e}")
220
- if profile.skills:
 
221
  try:
222
- response = self.llm.invoke("Extract a top 8 of skills from this resume text, separated by commas. Respond with just the skills: " + ", ".join([skill.name for skill in profile.skills]))
223
- skills = response.content.split(",")
224
- for skill in skills:
225
- if skill:
226
- profile.topSkills.append(Skill(name=skill.strip()))
227
- logger.debug(f"Added skill: {skill}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  except Exception as e:
229
- logger.error(f"Error extracting skills: {e}")
230
-
231
- return profile
232
-
233
- def _extract_with_fallback(self, pdf_text: str) -> Profile:
234
- """Fallback method for profile extraction using direct API calls"""
235
- logger.debug("Extracting profile with fallback method")
236
- client = groq.Groq(api_key=self.groq_api_key)
237
 
238
- def get_llm_response(prompt: str) -> str:
239
- """Helper function to get a response from the LLM."""
240
  try:
241
- chat_completion = client.chat.completions.create(
242
- messages=[{"role": "user", "content": prompt}],
243
- model=self.model_name,
244
- temperature=settings.FALLBACK_TEMPERATURE,
245
- max_tokens=settings.MAX_TOKENS
246
- )
247
- return chat_completion.choices[0].message.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  except Exception as e:
249
- logger.error(f"Error during LLM call: {e}")
250
- return "" # Return empty string on failure
251
-
252
- name = get_llm_response(f"Extract the full name from the following text. If no name is present, respond with 'N/A'. Only respond with the name: {pdf_text}").strip()
253
- title = get_llm_response(f"Extract the professional title from the following text. If no title is present, respond with 'N/A'. Only respond with the title: {pdf_text}").strip()
254
- email = get_llm_response(f"Extract the email address from the following text. If no email is present, respond with 'N/A'. Only respond with the email: {pdf_text}").strip()
255
- bio = get_llm_response(f"Create a short professional biography (around 50-100 words) based on the following text. Focus on skills and experience. If no bio is possible, respond with 'N/A'. Provide only the biography itself: {pdf_text}").strip()
256
- tagline = get_llm_response(f"Create a short and catchy tagline (around 5-10 words) that summarizes the person's professional identity from the following text. If no tagline is possible, respond with 'N/A'. Provide only the tagline: {pdf_text}").strip()
257
-
258
- linkedin = get_llm_response(f"Extract the LinkedIn profile URL from the following text. If no LinkedIn URL is present, respond with 'N/A'. Only respond with the LinkedIn URL: {pdf_text}").strip()
259
- github = get_llm_response(f"Extract the GitHub profile URL from the following text. If no GitHub URL is present, respond with 'N/A'. Only respond with the GitHub URL: {pdf_text}").strip()
260
- instagram = get_llm_response(f"Extract the Instagram profile URL from the following text. If no Instagram URL is present, respond with 'N/A'. Only respond with the Instagram URL: {pdf_text}").strip()
261
-
262
- project_info = get_llm_response(f"Extract information about projects from the following text in this format Project Title: Project Description: Tech Stack:. If no projects are present, respond with 'N/A': {pdf_text}").strip()
263
- skills_info = get_llm_response(f"Extract a list of skills from the following text, separated by commas. If no skills are present, respond with 'N/A'. Only respond with the skills: {pdf_text}").strip()
264
-
265
- education_info = get_llm_response(f"Extract education history from the following resume. For each education entry, provide the school name, degree type, field of study, start date, and end date. Format as 'School: Degree: Field: StartDate: EndDate' with each education on a new line. If no education is found, respond with 'N/A': {pdf_text}").strip()
266
-
267
- social_media = SocialMedia(
268
- linkedin=linkedin if linkedin != 'N/A' else None,
269
- github=github if github != 'N/A' else None,
270
- instagram=instagram if instagram != 'N/A' else None
271
- )
272
-
273
- projects = []
274
- if project_info != "N/A":
275
- project_lines = project_info.split("\n")
276
- for line in project_lines:
277
- if ":" in line:
278
- try:
279
- project_title, project_description_techstack = line.split(":", 1)
280
- project_description, tech_stack = project_description_techstack.split("Tech Stack:", 1)
281
-
282
- projects.append(Project(
283
- title=project_title.strip(),
284
- description=project_description.strip(),
285
- techStack=tech_stack.strip()
286
- ))
287
- logger.debug(f"Added project: {project_title.strip()}")
288
- except ValueError as e:
289
- logger.error(f"Error parsing project: {line}. Error: {e}")
290
-
291
- skills = []
292
- if skills_info != "N/A":
293
- skill_list = [skill.strip() for skill in skills_info.split(",")]
294
- for skill_name in skill_list:
295
- if skill_name:
296
- skills.append(Skill(name=skill_name))
297
- logger.debug(f"Added skill: {skill_name}")
298
-
299
- educations = []
300
- if education_info != "N/A":
301
- education_lines = education_info.split("\n")
302
- for line in education_lines:
303
- if ":" in line:
304
- try:
305
- parts = line.split(":")
306
- if len(parts) >= 5:
307
- educations.append(Education(
308
- school=parts[0].strip(),
309
- degree=parts[1].strip(),
310
- fieldOfStudy=parts[2].strip(),
311
- startDate=parts[3].strip(),
312
- endDate=parts[4].strip()
313
- ))
314
- logger.debug(f"Added education: {parts[0].strip()}")
315
- except Exception as e:
316
- logger.error(f"Error parsing education: {line}. Error: {e}")
317
-
318
- profile = Profile(
319
- name=name if name != 'N/A' else "N/A",
320
- title=title if title != 'N/A' else "N/A",
321
- email=email if email != 'N/A' else "N/A",
322
- bio=bio if bio != 'N/A' else "N/A",
323
- tagline=tagline if tagline != 'N/A' else None,
324
- social=social_media if (social_media.github or social_media.instagram or social_media.linkedin) else None,
325
- chatbot=None,
326
- profileImg=None,
327
- heroImg=None,
328
- projects=projects,
329
- skills=skills,
330
- educations=educations
331
- )
332
-
333
- logger.info("Profile extracted successfully with fallback method")
334
  return profile
335
-
336
 
337
  # Create module-level instance for easier imports
338
  profile_extractor = ProfileExtractor()
 
2
  Agent for extracting profile information from resumes
3
  """
4
  import groq
5
+ from models import Profile, SocialMedia, Project, Skill, Education, Experience, Category
6
  from typing import List, Dict, Any, Optional
7
  from langchain.output_parsers import PydanticOutputParser
8
  from langchain.prompts import PromptTemplate
 
61
  logger.error(f"LangChain extraction failed: {e}")
62
  if settings.DEBUG:
63
  print(f"LangChain extraction failed: {e}")
64
+ return Profile(name="N/A", title="N/A", email="N/A", bio="N/A")
65
 
66
  def _extract_with_langchain(self, pdf_text: str) -> Profile:
67
  """Extract profile with structured LangChain approach"""
 
75
  5. Tagline (a short 5-10 word catchy phrase summarizing professional identity)
76
  6. Social media links (LinkedIn, GitHub, Instagram)
77
  7. Projects (with title, description, and tech stack)
78
+ 8. Skills (with category only one of : Technical, Soft Skills, or Domain Knowledge)
79
  9. Education history (with school, degree, field of study, start date and end date)
80
+ 10. Work experience (with company, position, start date, end date, and description)
81
 
82
  Return the information in the following JSON format:
83
  {
 
99
  }
100
  ],
101
  "skills": [
102
+ {"name": "Skill 1", "category": "Technical"},
103
+ {"name": "Skill 2", "category": "Soft Skills"},
104
+ {"name": "Skill 3", "category": "Domain Knowledge"}
105
  ],
106
  "educations": [
107
  {
 
111
  "startDate": "Start Year",
112
  "endDate": "End Year or Present"
113
  }
114
+ ],
115
+ "experiences": [
116
+ {
117
+ "company": "Company Name",
118
+ "position": "Job Title",
119
+ "startDate": "Start Date",
120
+ "endDate": "End Date or Present",
121
+ "description": "Job Description"
122
+ }
123
  ]
124
  }
125
 
 
147
  json_start = response_text.find('{')
148
  json_end = response_text.rfind('}') + 1
149
 
150
+ if (json_start >= 0 and json_end > json_start):
151
  json_str = response_text[json_start:json_end]
152
  profile_dict = json.loads(json_str)
153
  profile = Profile.model_validate(profile_dict)
 
228
  logger.debug(f"Added education: {education}")
229
  except Exception as e:
230
  logger.error(f"Error extracting education: {e}")
231
+
232
+ if not profile.skills:
233
  try:
234
+ skills_prompt = """
235
+ Extract skills from this resume text and categorize them.
236
+ For each skill, determine if it's a Technical skill, Soft Skill, or Domain Knowledge.
237
+ Format the response as a JSON array of objects with 'name' and 'category' fields.
238
+ Example: [{"name": "Python", "category": "Technical"}, {"name": "Communication", "category": "Soft Skills"}]
239
+ """
240
+ response = self.llm.invoke(skills_prompt + "\n\n" + pdf_text)
241
+ skills_text = response.content.strip()
242
+
243
+ json_start = skills_text.find('[')
244
+ json_end = skills_text.rfind(']') + 1
245
+
246
+ if json_start >= 0 and json_end > json_start:
247
+ skills_json = skills_text[json_start:json_end]
248
+ skills_list = json.loads(skills_json)
249
+
250
+ for skill_data in skills_list:
251
+ category = None
252
+ skill_name = skill_data.get("name", "").strip()
253
+ category_str = skill_data.get("category", "").strip()
254
+
255
+ # Map the category string to our Category enum
256
+ if category_str.lower() == "technical":
257
+ category = Category.TECHNICAL
258
+ elif category_str.lower() in ["soft skills", "soft skill"]:
259
+ category = Category.SOFT_SKILLS
260
+ elif category_str.lower() in ["domain knowledge", "domain"]:
261
+ category = Category.DOMAIN_KNOWLEDGE
262
+
263
+ if skill_name:
264
+ profile.skills.append(Skill(name=skill_name, category=category))
265
+ logger.debug(f"Added categorized skill: {skill_name} ({category})")
266
  except Exception as e:
267
+ logger.error(f"Error extracting categorized skills: {e}")
 
 
 
 
 
 
 
268
 
269
+ if not profile.experiences:
 
270
  try:
271
+ experience_prompt = """
272
+ Extract work experience from this resume. For each position, provide:
273
+ - Company name
274
+ - Position/job title
275
+ - Start date
276
+ - End date (or "Present" if current)
277
+ - Job description (summarize responsibilities and achievements)
278
+ Format the response as a list of JSON objects.
279
+ """
280
+ response = self.llm.invoke(experience_prompt + "\n\n" + pdf_text)
281
+ exp_text = response.content.strip()
282
+
283
+ json_start = exp_text.find('[')
284
+ json_end = exp_text.rfind(']') + 1
285
+
286
+ if json_start >= 0 and json_end > json_start:
287
+ exp_json = exp_text[json_start:json_end]
288
+ experiences = json.loads(exp_json)
289
+
290
+ for exp in experiences:
291
+ experience = Experience(
292
+ company=exp.get("company", "Unknown"),
293
+ position=exp.get("position", ""),
294
+ startDate=exp.get("startDate", ""),
295
+ endDate=exp.get("endDate", ""),
296
+ description=exp.get("description", "")
297
+ )
298
+ profile.experiences.append(experience)
299
+ logger.debug(f"Added experience: {experience.company} - {experience.position}")
300
  except Exception as e:
301
+ logger.error(f"Error extracting work experience: {e}")
302
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  return profile
304
+
305
 
306
  # Create module-level instance for easier imports
307
  profile_extractor = ProfileExtractor()
app.py CHANGED
@@ -13,7 +13,7 @@ from typing import Dict, Any
13
  from agents import profile_extractor as pe, grammar_corrector as gc
14
  from utils import extract_text_from_pdf, save_temp_pdf
15
  from services import storage_service
16
- from models import Skill, Project, Education, SocialMedia
17
  from config import get_settings
18
 
19
  # Get settings
@@ -182,22 +182,103 @@ def collect_missing_data(profile):
182
  if proj_data["title"] and proj_data["description"]: # Only add if title and description are provided
183
  profile.projects.append(Project(**proj_data))
184
 
185
- # Skills
186
  st.subheader("Skills")
187
 
188
- # Create a comma-separated list of existing skills
189
- existing_skills = ", ".join([skill.name for skill in profile.skills]) if profile.skills else ""
 
 
 
190
 
191
- # Allow editing of the skills
192
- skills_input = st.text_area("Enter your skills (comma separated):", value=existing_skills)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  # Update profile with skills data
195
  profile.skills = []
196
- if skills_input:
197
- skills_list = [skill.strip() for skill in skills_input.split(",")]
198
- for skill_name in skills_list:
199
- if skill_name:
200
- profile.skills.append(Skill(name=skill_name))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  return profile
203
 
@@ -264,10 +345,42 @@ def display_profile(profile):
264
  st.subheader("Projects")
265
  st.table(projects_data)
266
 
267
- # Display skills as a comma separated list if available
 
 
 
 
 
 
 
 
 
 
 
 
268
  if profile.skills:
269
  st.subheader("Skills")
270
- st.write(", ".join([skill.name for skill in profile.skills]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  def main():
273
  """Main application function"""
 
13
  from agents import profile_extractor as pe, grammar_corrector as gc
14
  from utils import extract_text_from_pdf, save_temp_pdf
15
  from services import storage_service
16
+ from models import Skill, Project, Education, SocialMedia, Experience, Category
17
  from config import get_settings
18
 
19
  # Get settings
 
182
  if proj_data["title"] and proj_data["description"]: # Only add if title and description are provided
183
  profile.projects.append(Project(**proj_data))
184
 
185
+ # Skills with categories
186
  st.subheader("Skills")
187
 
188
+ # Group existing skills by category
189
+ technical_skills = [skill.name for skill in profile.skills if skill.category == Category.TECHNICAL]
190
+ soft_skills = [skill.name for skill in profile.skills if skill.category == Category.SOFT_SKILLS]
191
+ domain_skills = [skill.name for skill in profile.skills if skill.category == Category.DOMAIN_KNOWLEDGE]
192
+ uncategorized = [skill.name for skill in profile.skills if skill.category is None]
193
 
194
+ # Allow editing of the skills by category
195
+ st.write("Technical Skills")
196
+ tech_skills_input = st.text_area("Enter your technical skills (comma separated):",
197
+ value=", ".join(technical_skills) if technical_skills else "")
198
+
199
+ st.write("Soft Skills")
200
+ soft_skills_input = st.text_area("Enter your soft skills (comma separated):",
201
+ value=", ".join(soft_skills) if soft_skills else "")
202
+
203
+ st.write("Domain Knowledge")
204
+ domain_skills_input = st.text_area("Enter your domain knowledge skills (comma separated):",
205
+ value=", ".join(domain_skills) if domain_skills else "")
206
+
207
+ st.write("Uncategorized Skills")
208
+ uncategorized_input = st.text_area("Enter any other skills (comma separated):",
209
+ value=", ".join(uncategorized) if uncategorized else "")
210
 
211
  # Update profile with skills data
212
  profile.skills = []
213
+
214
+ def add_skills_by_category(skills_input, category):
215
+ if skills_input:
216
+ skills_list = [skill.strip() for skill in skills_input.split(",")]
217
+ for skill_name in skills_list:
218
+ if skill_name:
219
+ profile.skills.append(Skill(name=skill_name, category=category))
220
+
221
+ add_skills_by_category(tech_skills_input, Category.TECHNICAL)
222
+ add_skills_by_category(soft_skills_input, Category.SOFT_SKILLS)
223
+ add_skills_by_category(domain_skills_input, Category.DOMAIN_KNOWLEDGE)
224
+ add_skills_by_category(uncategorized_input, None)
225
+
226
+ # Work Experience
227
+ st.subheader("Work Experience")
228
+ experience_data = []
229
+
230
+ # Display existing experience entries with edit options
231
+ if profile.experiences:
232
+ for i, exp in enumerate(profile.experiences):
233
+ st.write(f"Experience #{i+1}")
234
+ company = st.text_input(f"Company {i+1}:", value=exp.company, key=f"company_{i}")
235
+ position = st.text_input(f"Position {i+1}:", value=exp.position, key=f"position_{i}")
236
+ start = st.text_input(f"Start Date {i+1}:", value=exp.startDate, key=f"exp_start_{i}")
237
+ end = st.text_input(f"End Date {i+1}:", value=exp.endDate if exp.endDate else "", key=f"exp_end_{i}")
238
+ description = st.text_area(f"Description {i+1}:", value=exp.description if exp.description else "", key=f"exp_desc_{i}")
239
+
240
+ if company and position: # Only add if company and position are provided
241
+ experience_data.append({
242
+ "company": company,
243
+ "position": position,
244
+ "startDate": start,
245
+ "endDate": end if end else None,
246
+ "description": description if description else None
247
+ })
248
+
249
+ # Option to add new experience entries
250
+ add_experience = st.checkbox("Add more work experience", key="add_exp")
251
+ if add_experience:
252
+ num_new_exp = st.number_input("Number of additional experiences:", min_value=1, max_value=10, value=1)
253
+ offset = len(profile.experiences) if profile.experiences else 0
254
+
255
+ for i in range(int(num_new_exp)):
256
+ st.write(f"Additional Experience #{i+1}")
257
+ company = st.text_input(f"Company:", key=f"new_company_{offset+i}")
258
+ position = st.text_input(f"Position:", key=f"new_position_{offset+i}")
259
+ start = st.text_input(f"Start Date:", key=f"new_exp_start_{offset+i}")
260
+ end = st.text_input(f"End Date:", key=f"new_exp_end_{offset+i}")
261
+ description = st.text_area(f"Description:", key=f"new_exp_desc_{offset+i}")
262
+
263
+ if company and position: # Only add if company and position are provided
264
+ correct_grammar_btn = st.button(f"Correct Grammar for Experience #{i+1}")
265
+ if correct_grammar_btn and description:
266
+ description = grammar_corrector.correct_grammar(description)
267
+ st.success("Grammar corrected!")
268
+
269
+ experience_data.append({
270
+ "company": company,
271
+ "position": position,
272
+ "startDate": start,
273
+ "endDate": end if end else None,
274
+ "description": description if description else None
275
+ })
276
+
277
+ # Update profile with experience data
278
+ profile.experiences = []
279
+ for exp_data in experience_data:
280
+ if exp_data["company"] and exp_data["position"]: # Only add if company and position are provided
281
+ profile.experiences.append(Experience(**exp_data))
282
 
283
  return profile
284
 
 
345
  st.subheader("Projects")
346
  st.table(projects_data)
347
 
348
+ # Display work experience in a table if available
349
+ if profile.experiences:
350
+ experiences_data = {
351
+ "Company": [exp.company for exp in profile.experiences],
352
+ "Position": [exp.position for exp in profile.experiences],
353
+ "Start Date": [exp.startDate for exp in profile.experiences],
354
+ "End Date": [exp.endDate if exp.endDate else "Present" for exp in profile.experiences],
355
+ "Description": [exp.description if exp.description else "" for exp in profile.experiences]
356
+ }
357
+ st.subheader("Work Experience")
358
+ st.table(experiences_data)
359
+
360
+ # Display skills categorized by type if available
361
  if profile.skills:
362
  st.subheader("Skills")
363
+
364
+ technical = [skill.name for skill in profile.skills if skill.category == Category.TECHNICAL]
365
+ soft = [skill.name for skill in profile.skills if skill.category == Category.SOFT_SKILLS]
366
+ domain = [skill.name for skill in profile.skills if skill.category == Category.DOMAIN_KNOWLEDGE]
367
+ other = [skill.name for skill in profile.skills if skill.category is None]
368
+
369
+ if technical:
370
+ st.write("**Technical Skills:**")
371
+ st.write(", ".join(technical))
372
+
373
+ if soft:
374
+ st.write("**Soft Skills:**")
375
+ st.write(", ".join(soft))
376
+
377
+ if domain:
378
+ st.write("**Domain Knowledge:**")
379
+ st.write(", ".join(domain))
380
+
381
+ if other:
382
+ st.write("**Other Skills:**")
383
+ st.write(", ".join(other))
384
 
385
  def main():
386
  """Main application function"""
models.py CHANGED
@@ -1,9 +1,16 @@
1
  from typing import List, Optional
2
  from pydantic import BaseModel
 
 
 
 
 
 
3
 
4
  class Skill(BaseModel):
5
  img: Optional[str] = None # Allow null values
6
  name: str
 
7
 
8
  class Project(BaseModel):
9
  img: Optional[str] = None
@@ -21,13 +28,20 @@ class SocialMedia(BaseModel):
21
  class Chatbot(BaseModel):
22
  token: str
23
  apiBaseURL: str
24
-
25
  class Education(BaseModel):
26
  school: str
27
  degree: str
28
  fieldOfStudy: str
29
  startDate: str
30
- endDate: str
 
 
 
 
 
 
 
31
 
32
  class Profile(BaseModel):
33
  name: str
@@ -42,4 +56,5 @@ class Profile(BaseModel):
42
  projects: List[Project] = []
43
  skills: List[Skill] = []
44
  topSkills: List[str] = []
45
- educations: List[Education] = []
 
 
1
  from typing import List, Optional
2
  from pydantic import BaseModel
3
+ from enum import Enum
4
+
5
+ class Category(str, Enum):
6
+ TECHNICAL = "Technical"
7
+ SOFT_SKILLS = "Soft Skills"
8
+ DOMAIN_KNOWLEDGE = "Domain Knowledge"
9
 
10
  class Skill(BaseModel):
11
  img: Optional[str] = None # Allow null values
12
  name: str
13
+ category: Optional[Category] = None
14
 
15
  class Project(BaseModel):
16
  img: Optional[str] = None
 
28
  class Chatbot(BaseModel):
29
  token: str
30
  apiBaseURL: str
31
+
32
  class Education(BaseModel):
33
  school: str
34
  degree: str
35
  fieldOfStudy: str
36
  startDate: str
37
+ endDate: str
38
+
39
+ class Experience(BaseModel):
40
+ company: str
41
+ position: str
42
+ startDate: str
43
+ endDate: Optional[str] = None
44
+ description: Optional[str] = None
45
 
46
  class Profile(BaseModel):
47
  name: str
 
56
  projects: List[Project] = []
57
  skills: List[Skill] = []
58
  topSkills: List[str] = []
59
+ educations: List[Education] = []
60
+ experiences: List[Experience] = []
services/storage_service.py CHANGED
@@ -38,6 +38,7 @@ class StorageService:
38
  projects TEXT,
39
  skills TEXT,
40
  educations TEXT
 
41
  )
42
  """)
43
  conn.commit()
@@ -70,7 +71,22 @@ class StorageService:
70
  "demoUrl": project.demoUrl
71
  } for project in profile.projects
72
  ]),
73
- "skills": json.dumps([skill.name for skill in profile.skills]),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  "educations": json.dumps([
75
  {
76
  "school": edu.school,
@@ -147,8 +163,8 @@ class StorageService:
147
  conn = sqlite3.connect(self.db_path)
148
  cursor = conn.cursor()
149
  cursor.execute("""
150
- INSERT INTO profiles (name, title, email, bio, tagline, social, profileImg, projects, skills, educations)
151
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
152
  """, (
153
  profile_dict["name"],
154
  profile_dict["title"],
@@ -159,7 +175,8 @@ class StorageService:
159
  profile_dict["profileImg"],
160
  profile_dict["projects"],
161
  profile_dict["skills"],
162
- profile_dict["educations"]
 
163
  ))
164
  conn.commit()
165
  profile_id = str(cursor.lastrowid)
@@ -211,7 +228,9 @@ class StorageService:
211
  "profileImg": row[7],
212
  "projects": json.loads(row[8]) if row[8] else [],
213
  "skills": json.loads(row[9]) if row[9] else [],
214
- "educations": json.loads(row[10]) if row[10] else []
 
 
215
  }
216
  logger.debug(f"Retrieved profile: {profile_id}")
217
  return profile
 
38
  projects TEXT,
39
  skills TEXT,
40
  educations TEXT
41
+ experiences TEXT
42
  )
43
  """)
44
  conn.commit()
 
71
  "demoUrl": project.demoUrl
72
  } for project in profile.projects
73
  ]),
74
+ "skills": json.dumps([
75
+ {
76
+ "name": skill.name,
77
+ "category": skill.category.value if skill.category else None,
78
+ "img": skill.img
79
+ } for skill in profile.skills
80
+ ]),
81
+ "experiences": json.dumps([
82
+ {
83
+ "company": exp.company,
84
+ "position": exp.position,
85
+ "startDate": exp.startDate,
86
+ "endDate": exp.endDate,
87
+ "description": exp.description
88
+ } for exp in profile.experiences
89
+ ]),
90
  "educations": json.dumps([
91
  {
92
  "school": edu.school,
 
163
  conn = sqlite3.connect(self.db_path)
164
  cursor = conn.cursor()
165
  cursor.execute("""
166
+ INSERT INTO profiles (name, title, email, bio, tagline, social, profileImg, projects, skills, educations, experiences)
167
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
168
  """, (
169
  profile_dict["name"],
170
  profile_dict["title"],
 
175
  profile_dict["profileImg"],
176
  profile_dict["projects"],
177
  profile_dict["skills"],
178
+ profile_dict["educations"],
179
+ profile_dict["experiences"]
180
  ))
181
  conn.commit()
182
  profile_id = str(cursor.lastrowid)
 
228
  "profileImg": row[7],
229
  "projects": json.loads(row[8]) if row[8] else [],
230
  "skills": json.loads(row[9]) if row[9] else [],
231
+ "educations": json.loads(row[10]) if row[10] else [],
232
+ "experiences": json.loads(row[11]) if row[11] else []
233
+
234
  }
235
  logger.debug(f"Retrieved profile: {profile_id}")
236
  return profile