ishaq101 commited on
Commit
abc3feb
·
1 Parent(s): 0cc75c9

[NOTICKET] Feat: calculate_score v2

Browse files
externals/databases/pg_crud.py CHANGED
@@ -1,7 +1,7 @@
1
  from typing import Optional, List
2
  from uuid import UUID
3
 
4
- from sqlalchemy import select, and_, update, delete, func
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
  from externals.databases.pg_models import (
7
  CVUser,
@@ -383,42 +383,48 @@ async def get_profiles_by_criteria_id(
383
  if filter.gpa_edu_3 is not None:
384
  conditions.append(CVProfile.gpa_edu_3 >= filter.gpa_edu_3)
385
 
386
- # --- University (case-insensitive) ---
387
  if filter.univ_edu_1:
388
  conditions.append(
389
- func.lower(CVProfile.univ_edu_1) ==
390
- filter.univ_edu_1.lower()
 
391
  )
392
 
393
  if filter.univ_edu_2:
394
  conditions.append(
395
- func.lower(CVProfile.univ_edu_2) ==
396
- filter.univ_edu_2.lower()
 
397
  )
398
 
399
  if filter.univ_edu_3:
400
  conditions.append(
401
- func.lower(CVProfile.univ_edu_3) ==
402
- filter.univ_edu_3.lower()
 
403
  )
404
 
405
- # --- Major (case-insensitive) ---
406
  if filter.major_edu_1:
407
  conditions.append(
408
- func.lower(CVProfile.major_edu_1) ==
409
- filter.major_edu_1.lower()
 
410
  )
411
 
412
  if filter.major_edu_2:
413
  conditions.append(
414
- func.lower(CVProfile.major_edu_2) ==
415
- filter.major_edu_2.lower()
 
416
  )
417
 
418
  if filter.major_edu_3:
419
  conditions.append(
420
- func.lower(CVProfile.major_edu_3) ==
421
- filter.major_edu_3.lower()
 
422
  )
423
 
424
  # --- Others ---
@@ -578,19 +584,19 @@ async def get_filter(
578
 
579
  # --- University ---
580
  if filter.univ_edu_1:
581
- conditions.append(CVFilter.univ_edu_1 == filter.univ_edu_1)
582
  if filter.univ_edu_2:
583
- conditions.append(CVFilter.univ_edu_2 == filter.univ_edu_2)
584
  if filter.univ_edu_3:
585
- conditions.append(CVFilter.univ_edu_3 == filter.univ_edu_3)
586
 
587
  # --- Major ---
588
  if filter.major_edu_1:
589
- conditions.append(CVFilter.major_edu_1 == filter.major_edu_1)
590
  if filter.major_edu_2:
591
- conditions.append(CVFilter.major_edu_2 == filter.major_edu_2)
592
  if filter.major_edu_3:
593
- conditions.append(CVFilter.major_edu_3 == filter.major_edu_3)
594
 
595
  # --- Others ---
596
  if filter.domicile:
@@ -621,6 +627,68 @@ async def get_filter(
621
 
622
  return result.scalar_one_or_none()
623
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
  @retry_db(retries=2, delay=2)
626
  async def get_filter_by_id(
 
1
  from typing import Optional, List
2
  from uuid import UUID
3
 
4
+ from sqlalchemy import select, and_, or_, update, delete, func
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
  from externals.databases.pg_models import (
7
  CVUser,
 
383
  if filter.gpa_edu_3 is not None:
384
  conditions.append(CVProfile.gpa_edu_3 >= filter.gpa_edu_3)
385
 
386
+ # --- University (case-insensitive, any match in list) ---
387
  if filter.univ_edu_1:
388
  conditions.append(
389
+ func.lower(CVProfile.univ_edu_1).in_(
390
+ [v.lower() for v in filter.univ_edu_1]
391
+ )
392
  )
393
 
394
  if filter.univ_edu_2:
395
  conditions.append(
396
+ func.lower(CVProfile.univ_edu_2).in_(
397
+ [v.lower() for v in filter.univ_edu_2]
398
+ )
399
  )
400
 
401
  if filter.univ_edu_3:
402
  conditions.append(
403
+ func.lower(CVProfile.univ_edu_3).in_(
404
+ [v.lower() for v in filter.univ_edu_3]
405
+ )
406
  )
407
 
408
+ # --- Major (case-insensitive, any match in list) ---
409
  if filter.major_edu_1:
410
  conditions.append(
411
+ func.lower(CVProfile.major_edu_1).in_(
412
+ [v.lower() for v in filter.major_edu_1]
413
+ )
414
  )
415
 
416
  if filter.major_edu_2:
417
  conditions.append(
418
+ func.lower(CVProfile.major_edu_2).in_(
419
+ [v.lower() for v in filter.major_edu_2]
420
+ )
421
  )
422
 
423
  if filter.major_edu_3:
424
  conditions.append(
425
+ func.lower(CVProfile.major_edu_3).in_(
426
+ [v.lower() for v in filter.major_edu_3]
427
+ )
428
  )
429
 
430
  # --- Others ---
 
584
 
585
  # --- University ---
586
  if filter.univ_edu_1:
587
+ conditions.append(CVFilter.univ_edu_1.overlap(filter.univ_edu_1))
588
  if filter.univ_edu_2:
589
+ conditions.append(CVFilter.univ_edu_2.overlap(filter.univ_edu_2))
590
  if filter.univ_edu_3:
591
+ conditions.append(CVFilter.univ_edu_3.overlap(filter.univ_edu_3))
592
 
593
  # --- Major ---
594
  if filter.major_edu_1:
595
+ conditions.append(CVFilter.major_edu_1.overlap(filter.major_edu_1))
596
  if filter.major_edu_2:
597
+ conditions.append(CVFilter.major_edu_2.overlap(filter.major_edu_2))
598
  if filter.major_edu_3:
599
+ conditions.append(CVFilter.major_edu_3.overlap(filter.major_edu_3))
600
 
601
  # --- Others ---
602
  if filter.domicile:
 
627
 
628
  return result.scalar_one_or_none()
629
 
630
+ from services.models.data_model import Criteria
631
+ @retry_db(retries=2, delay=2)
632
+ async def get_multi_filter(
633
+ db: AsyncSession,
634
+ filter: Criteria,
635
+ ):
636
+ conditions = []
637
+ list_conditions = []
638
+
639
+ # --- GPA (scalar: AND) ---
640
+ if filter.get("gpa_edu_1") is not None:
641
+ conditions.append(CVFilter.gpa_edu_1 >= filter.get("gpa_edu_1"))
642
+ if filter.get("gpa_edu_2") is not None:
643
+ conditions.append(CVFilter.gpa_edu_2 >= filter.get("gpa_edu_2"))
644
+ if filter.get("gpa_edu_3") is not None:
645
+ conditions.append(CVFilter.gpa_edu_3 >= filter.get("gpa_edu_3"))
646
+
647
+ # --- Others (scalar: AND) ---
648
+ if filter.get("domicile"):
649
+ conditions.append(CVFilter.domicile == filter.get("domicile"))
650
+
651
+ if filter.get("yoe") is not None:
652
+ conditions.append(CVFilter.yoe >= filter.get("yoe"))
653
+
654
+ # --- University (list: OR) ---
655
+ if filter.get("univ_edu_1"):
656
+ list_conditions.append(CVFilter.univ_edu_1.in_(filter.get("univ_edu_1")))
657
+ if filter.get("univ_edu_2"):
658
+ list_conditions.append(CVFilter.univ_edu_2.in_(filter.get("univ_edu_2")))
659
+ if filter.get("univ_edu_3"):
660
+ list_conditions.append(CVFilter.univ_edu_3.in_(filter.get("univ_edu_3")))
661
+
662
+ # --- Major (list: OR) ---
663
+ if filter.get("major_edu_1"):
664
+ list_conditions.append(CVFilter.major_edu_1.in_(filter.get("major_edu_1")))
665
+ if filter.get("major_edu_2"):
666
+ list_conditions.append(CVFilter.major_edu_2.in_(filter.get("major_edu_2")))
667
+ if filter.get("major_edu_3"):
668
+ list_conditions.append(CVFilter.major_edu_3.in_(filter.get("major_edu_3")))
669
+
670
+ # --- ARRAY fields (list: OR) ---
671
+ if filter.get("hardskills"):
672
+ list_conditions.append(CVFilter.hardskills.overlap(filter.get("hardskills")))
673
+ if filter.get("softskills"):
674
+ list_conditions.append(CVFilter.softskills.overlap(filter.get("softskills")))
675
+ if filter.get("certifications"):
676
+ list_conditions.append(CVFilter.certifications.overlap(filter.get("certifications")))
677
+ if filter.get("business_domain"):
678
+ list_conditions.append(CVFilter.business_domain.overlap(filter.get("business_domain")))
679
+
680
+ if list_conditions:
681
+ conditions.append(or_(*list_conditions))
682
+
683
+ # ⛔ Prevent full table scan
684
+ if not conditions:
685
+ return None
686
+
687
+ stmt = select(CVFilter).where(and_(*conditions))
688
+ result = await db.execute(stmt)
689
+
690
+ return result.scalar_one_or_none()
691
+
692
 
693
  @retry_db(retries=2, delay=2)
694
  async def get_filter_by_id(
externals/databases/pg_models.py CHANGED
@@ -114,7 +114,7 @@ class CVFilter(Base):
114
 
115
  created_at = Column(TIMESTAMP(timezone=True), server_default=master_config.JAKARTA_NOW)
116
 
117
-
118
  class CVWeight(Base):
119
  __tablename__ = "cv_weight"
120
 
 
114
 
115
  created_at = Column(TIMESTAMP(timezone=True), server_default=master_config.JAKARTA_NOW)
116
 
117
+
118
  class CVWeight(Base):
119
  __tablename__ = "cv_weight"
120
 
interfaces/api/agentic.py CHANGED
@@ -182,10 +182,10 @@ async def calculate_score(
182
  "data": data
183
  }
184
  except Exception as E:
185
- logger.error(f"calculate score error: {E}")
186
  raise HTTPException(
187
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
188
- detail=f"calculate score error: {E}"
189
  )
190
 
191
  # @router.get("/get_profile_table")
 
182
  "data": data
183
  }
184
  except Exception as E:
185
+ logger.error(f"calculate score v2 error: {E}")
186
  raise HTTPException(
187
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
188
+ detail=f"calculate score v2 error: {E}"
189
  )
190
 
191
  # @router.get("/get_profile_table")
services/agentic/filter.py CHANGED
@@ -6,7 +6,9 @@ from externals.databases.pg_crud import (
6
  create_filter,
7
  get_filter,
8
  get_filter_by_id,
 
9
  )
 
10
  from utils.logger import get_logger
11
 
12
  logger = get_logger("filter agentic service")
@@ -31,6 +33,49 @@ class AgenticFilterService:
31
  filter_id = await create_filter(self.db, filter)
32
  logger.info(f"Filter not existed yet, creating new filter: {filter_id}")
33
  return filter_id.criteria_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
 
36
  async def get_filter_by_id(self, criteria_id: str) -> CVFilter:
 
6
  create_filter,
7
  get_filter,
8
  get_filter_by_id,
9
+ get_multi_filter,
10
  )
11
+ from services.models.data_model import Criteria
12
  from utils.logger import get_logger
13
 
14
  logger = get_logger("filter agentic service")
 
33
  filter_id = await create_filter(self.db, filter)
34
  logger.info(f"Filter not existed yet, creating new filter: {filter_id}")
35
  return filter_id.criteria_id
36
+
37
+
38
+ async def create_filter_v2(self, filter: Criteria) -> str:
39
+ """Return criteria_id:str"""
40
+
41
+ # check filter existence
42
+ existing_filter = await get_multi_filter(self.db, filter)
43
+
44
+ if existing_filter:
45
+ logger.info(f"Filter already exists: {existing_filter}")
46
+ return existing_filter.criteria_id
47
+
48
+ # Convert Criteria dict → CVFilter ORM object
49
+ univ_edu_1_list = filter.get("univ_edu_1") or []
50
+ univ_edu_2_list = filter.get("univ_edu_2") or []
51
+ univ_edu_3_list = filter.get("univ_edu_3") or []
52
+ major_edu_1_list = filter.get("major_edu_1") or []
53
+ major_edu_2_list = filter.get("major_edu_2") or []
54
+ major_edu_3_list = filter.get("major_edu_3") or []
55
+
56
+ cv_filter = CVFilter(
57
+ gpa_edu_1=filter.get("gpa_edu_1"),
58
+ gpa_edu_2=filter.get("gpa_edu_2"),
59
+ gpa_edu_3=filter.get("gpa_edu_3"),
60
+ univ_edu_1=univ_edu_1_list[0] if univ_edu_1_list else None,
61
+ univ_edu_2=univ_edu_2_list[0] if univ_edu_2_list else None,
62
+ univ_edu_3=univ_edu_3_list[0] if univ_edu_3_list else None,
63
+ major_edu_1=major_edu_1_list[0] if major_edu_1_list else None,
64
+ major_edu_2=major_edu_2_list[0] if major_edu_2_list else None,
65
+ major_edu_3=major_edu_3_list[0] if major_edu_3_list else None,
66
+ domicile=filter.get("domicile"),
67
+ yoe=filter.get("yoe"),
68
+ hardskills=filter.get("hardskills") or [],
69
+ softskills=filter.get("softskills") or [],
70
+ certifications=filter.get("certifications") or [],
71
+ business_domain=filter.get("business_domain") or [],
72
+ )
73
+ logger.info(f">>> cv_filter: {cv_filter}")
74
+
75
+ # create filter
76
+ filter_id = await create_filter(self.db, cv_filter)
77
+ logger.info(f"Filter not existed yet, creating new filter: {filter_id.criteria_id}")
78
+ return filter_id.criteria_id
79
 
80
 
81
  async def get_filter_by_id(self, criteria_id: str) -> CVFilter:
services/agentic/profile_scoring.py CHANGED
@@ -15,6 +15,7 @@ from services.models.data_model import (
15
  Criteria,
16
  CriteriaWeight,
17
  InputScoringBulk,
 
18
  LOGIC_NUMERIC,
19
  LOGIC_CATEGORICAL,
20
  # InputScoring,
@@ -85,12 +86,12 @@ def helper_judge_scoring(a_profile, b_criteria, rules) -> bool:
85
  # return comparison
86
 
87
 
88
- def comparison_parser(profile:AIProfile, criteria:Criteria):
89
- comparison = "| parameter | candidate_profile | criteria | true_if_rules | draft_matching_score |"
90
  comparison += "\n| --- | --- | --- | --- | --- |"
91
 
92
  for k, v in criteria.items():
93
- print(f" key comparison: {k}")
94
  op = helper_get_operator(k)
95
  if ('gpa' in k or 'yoe' in k) and (profile.get(k) is not None) and (v is not None):
96
  judges = helper_judge_scoring(a_profile=profile.get(k),
@@ -99,7 +100,7 @@ def comparison_parser(profile:AIProfile, criteria:Criteria):
99
  comparison += f"\n| {k} | {profile.get(k)} | {v} | {op} | {judges} |"
100
  else:
101
  judges = '???'
102
- comparison += f"\n| {k} | {profile.get(k)} | NA | NA | False |"
103
  return comparison
104
 
105
 
@@ -380,7 +381,7 @@ class AgenticScoringService:
380
  self.weight_service = AgenticWeightService(db=db, user=user)
381
  self.profile_service = KnowledgeGetProfileService(db=db, user=user)
382
 
383
- async def _ai_matching(self, profile:AIProfile, criteria:Criteria) -> AIMatchProfile:
384
  "Helper function to matching between a profile and criteria"
385
 
386
  comparison_text = comparison_parser(profile=profile, criteria=criteria)
@@ -424,13 +425,14 @@ class AgenticScoringService:
424
  return matching_result
425
 
426
  async def _ai_matching_bulk(self,
427
- profiles:List,
428
  criteria:Criteria) -> List:
429
  try:
430
  results = []
431
  for i, p in enumerate(profiles):
432
- # print(f"==> {i+1} profile: {p} vs criteria: {criteria}")
433
  tmp_matching:AIMatchProfile = await self._ai_matching(profile=p, criteria=criteria)
 
 
434
  results.append(tmp_matching)
435
  return results
436
  except Exception as E:
@@ -487,8 +489,11 @@ class AgenticScoringService:
487
  temp.profile_id = match_result.profile_id
488
  temp.matching_id = match_result.matching_id
489
  temp.score = self._calculate_score(match_result=match_result, weight_data=weight_data)
490
- # print(f"{i+1} match_result: {match_result} vs weight_data: {weight_data}")
491
- # score = self._calculate_score(match_result=match_result, weight_data=weight_data)
 
 
 
492
  scores.append(temp)
493
  return scores
494
 
@@ -587,32 +592,13 @@ class AgenticScoringService:
587
  # Create criteria_id
588
  filter_svc = AgenticFilterService(db=self.db, user=self.user)
589
  criteria = requirements.get("criteria")
590
- weight = requirements.get("criteria_weight")
591
-
592
- cv_filter = CVFilter(
593
- criteria_id=uuid4(),
594
-
595
- gpa_edu_1=filter.get("gpa_edu_1"),
596
- gpa_edu_2=filter.get("gpa_edu_2"),
597
- gpa_edu_3=filter.get("gpa_edu_3"),
598
-
599
- univ_edu_1=filter.get("univ_edu_1"),
600
- univ_edu_2=filter.get("univ_edu_2"),
601
- univ_edu_3=filter.get("univ_edu_3"),
602
 
603
- major_edu_1=filter.get("major_edu_1"),
604
- major_edu_2=filter.get("major_edu_2"),
605
- major_edu_3=filter.get("major_edu_3"),
606
-
607
- domicile=filter.get("domicile"),
608
- yoe=filter.get("yoe"),
609
 
610
- hardskills=filter.get("hardskills"),
611
- softskills=filter.get("softskills"),
612
- certifications=filter.get("certifications"),
613
- business_domain=filter.get("business_domain")
614
- )
615
- criteria_id = await filter_svc.create_filter(filter=cv_filter)
616
 
617
 
618
  weight_svc = AgenticWeightService(db=self.db, user=self.user)
@@ -648,9 +634,9 @@ class AgenticScoringService:
648
  all_profiles = await get_profiles_by_user_id(db=self.db, current_user=self.user)
649
  print(f"🫡 Found {len(all_profiles)} profiles to be scored")
650
 
651
- all_tobe_scored = []
652
  for p in all_profiles:
653
- tmp_profile = AIProfile(
654
  fullname=p.fullname,
655
  gpa_edu_1=p.gpa_edu_1,
656
  univ_edu_1=p.univ_edu_1,
@@ -684,6 +670,6 @@ class AgenticScoringService:
684
 
685
  return scores
686
  except Exception as E:
687
- logger.error(f"profile scoring error, {E}")
688
  traceback.print_exc()
689
  raise
 
15
  Criteria,
16
  CriteriaWeight,
17
  InputScoringBulk,
18
+ AIProfileTbScore,
19
  LOGIC_NUMERIC,
20
  LOGIC_CATEGORICAL,
21
  # InputScoring,
 
86
  # return comparison
87
 
88
 
89
+ def comparison_parser(profile:AIProfileTbScore, criteria:Criteria):
90
+ comparison = "| parameter | candidate_profile | job criteria | true_if_rules | draft_matching_score |"
91
  comparison += "\n| --- | --- | --- | --- | --- |"
92
 
93
  for k, v in criteria.items():
94
+ # print(f" key comparison: {k}")
95
  op = helper_get_operator(k)
96
  if ('gpa' in k or 'yoe' in k) and (profile.get(k) is not None) and (v is not None):
97
  judges = helper_judge_scoring(a_profile=profile.get(k),
 
100
  comparison += f"\n| {k} | {profile.get(k)} | {v} | {op} | {judges} |"
101
  else:
102
  judges = '???'
103
+ comparison += f"\n| {k} | {profile.get(k)} | {v} | related | False |"
104
  return comparison
105
 
106
 
 
381
  self.weight_service = AgenticWeightService(db=db, user=user)
382
  self.profile_service = KnowledgeGetProfileService(db=db, user=user)
383
 
384
+ async def _ai_matching(self, profile:AIProfileTbScore, criteria:Criteria) -> AIMatchProfile:
385
  "Helper function to matching between a profile and criteria"
386
 
387
  comparison_text = comparison_parser(profile=profile, criteria=criteria)
 
425
  return matching_result
426
 
427
  async def _ai_matching_bulk(self,
428
+ profiles:List[AIProfileTbScore],
429
  criteria:Criteria) -> List:
430
  try:
431
  results = []
432
  for i, p in enumerate(profiles):
 
433
  tmp_matching:AIMatchProfile = await self._ai_matching(profile=p, criteria=criteria)
434
+ print(f"==> {i+1} profile: {p} vs criteria: {criteria}")
435
+ logger.info(f">>> _ai_matching_bulk/tmp_matching: {tmp_matching}")
436
  results.append(tmp_matching)
437
  return results
438
  except Exception as E:
 
489
  temp.profile_id = match_result.profile_id
490
  temp.matching_id = match_result.matching_id
491
  temp.score = self._calculate_score(match_result=match_result, weight_data=weight_data)
492
+
493
+ match_result_dict = {c.name: getattr(match_result, c.name) for c in CVMatching.__table__.columns}
494
+ print(f"{i+1} match_result: {match_result_dict} vs weight_data: {weight_data}")
495
+ print(f"temp.score: {temp.score}")
496
+
497
  scores.append(temp)
498
  return scores
499
 
 
592
  # Create criteria_id
593
  filter_svc = AgenticFilterService(db=self.db, user=self.user)
594
  criteria = requirements.get("criteria")
595
+ print(f"DEBUG: scoring_v2/criteria: {criteria}")
 
 
 
 
 
 
 
 
 
 
 
596
 
597
+ weight = requirements.get("criteria_weight")
598
+ print(f"DEBUG: scoring_v2/weight: {weight}")
 
 
 
 
599
 
600
+ criteria_id = await filter_svc.create_filter_v2(filter=criteria)
601
+ print(f"DEBUG: scoring_v2/criteria_id: {criteria_id}")
 
 
 
 
602
 
603
 
604
  weight_svc = AgenticWeightService(db=self.db, user=self.user)
 
634
  all_profiles = await get_profiles_by_user_id(db=self.db, current_user=self.user)
635
  print(f"🫡 Found {len(all_profiles)} profiles to be scored")
636
 
637
+ all_tobe_scored:list[AIProfileTbScore] = []
638
  for p in all_profiles:
639
+ tmp_profile = AIProfileTbScore(
640
  fullname=p.fullname,
641
  gpa_edu_1=p.gpa_edu_1,
642
  univ_edu_1=p.univ_edu_1,
 
670
 
671
  return scores
672
  except Exception as E:
673
+ logger.error(f"profile scoring v2 error, {E}")
674
  traceback.print_exc()
675
  raise
services/models/data_model.py CHANGED
@@ -52,6 +52,31 @@ class AIProfile(TypedDict):
52
  certifications: Optional[List[str]] = Field(description="List of the candidate's certifications", default_factory=list)
53
  business_domain: Optional[List[str]] = Field(description="List of the candidate's business domain experience based on working experience or project", default_factory=list)
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  class Profile(AIProfile):
56
  profile_id: str
57
  created_at: datetime = datetime.now().replace(tzinfo=tzinfo)
 
52
  certifications: Optional[List[str]] = Field(description="List of the candidate's certifications", default_factory=list)
53
  business_domain: Optional[List[str]] = Field(description="List of the candidate's business domain experience based on working experience or project", default_factory=list)
54
 
55
+ class AIProfileTbScore(TypedDict):
56
+ fullname: str = Field(description="Fullname of the candidate", default="-")
57
+ # gender: str = Field(description="Gender of the candidate, if available", default="null")
58
+ # age: int = Field(description="Age in number")
59
+ gpa_edu_1: float = Field(description="""GPA of candidate's bachelor degree (same like sarjana, s1, undergradute), if exists.""", default=0)
60
+ univ_edu_1: list = Field(description="""University where candidate take bachelor degree, if exists.""", default="-")
61
+ major_edu_1: list = Field(description="""Major of candidate's bachelor degree, if exists.""", default="-")
62
+
63
+ gpa_edu_2: float = Field(description="""GPA of candidate's master degree (same like master, s2, postgraduate), if exists.""", default=0)
64
+ univ_edu_2: list = Field(description="""University where candidate take master degree, if exists.""", default="-")
65
+ major_edu_2: list = Field(description="""Major of candidate's master degree, if exists.""", default="-")
66
+
67
+ gpa_edu_3: float = Field(description="""GPA of candidate's doctoral or phd degree (same like phd, s3, doctoral), if exists.""", default=0)
68
+ univ_edu_3: list = Field(description="""University where candidate take doctoral or phd degree, if exists.""", default="-")
69
+ major_edu_3: list = Field(description="""Major of candidate's doctoral or phd degree, if exists.""", default="-")
70
+
71
+ domicile: str = Field(description="Current domicile of the candidate", default="-")
72
+
73
+ yoe: float = Field(description="The candidate's total years of experience (as an float)", default=0)
74
+ hardskills: Optional[List[str]] = Field(description="List of the candidate's hard skills",default_factory=list)
75
+ softskills: Optional[List[str]] = Field(description="List of the candidate's soft skills", default_factory=list)
76
+ certifications: Optional[List[str]] = Field(description="List of the candidate's certifications", default_factory=list)
77
+ business_domain: Optional[List[str]] = Field(description="List of the candidate's business domain experience based on working experience or project", default_factory=list)
78
+
79
+
80
  class Profile(AIProfile):
81
  profile_id: str
82
  created_at: datetime = datetime.now().replace(tzinfo=tzinfo)