ishaq101 commited on
Commit
e4b8ed7
·
1 Parent(s): 1249d8b

[NOTICKET] Feat: Matching and Scoring

Browse files
externals/databases/pg_crud.py CHANGED
@@ -267,6 +267,12 @@ async def get_profile_by_filename(
267
  result = await db.execute(stmt)
268
  return result.scalar_one_or_none()
269
 
 
 
 
 
 
 
270
 
271
  async def get_profile_by_id(
272
  db: AsyncSession,
@@ -382,7 +388,7 @@ async def get_filter_and_weight(
382
  .where(CVFilter.criteria_id == criteria_id)
383
  )
384
  result = await db.execute(stmt)
385
- return result.first()
386
 
387
 
388
  async def get_weight_by_id(
@@ -391,11 +397,10 @@ async def get_weight_by_id(
391
  ):
392
  stmt = (
393
  select(CVWeight)
394
- .where(CVFilter.weight_id == weight_id)
395
  )
396
  result = await db.execute(stmt)
397
- return result.first()
398
-
399
 
400
  async def create_filter_and_weight(
401
  db: AsyncSession,
@@ -427,6 +432,27 @@ async def create_matching(
427
  return matching
428
 
429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  async def get_matching_by_profile_and_criteria(
431
  db: AsyncSession,
432
  profile_id: str,
@@ -459,6 +485,21 @@ async def create_score(
459
  return score
460
 
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  async def get_scores_by_criteria(
463
  db: AsyncSession,
464
  criteria_id: str,
 
267
  result = await db.execute(stmt)
268
  return result.scalar_one_or_none()
269
 
270
+ async def get_profiles(
271
+ db: AsyncSession,
272
+ ) -> List[CVProfile]:
273
+ stmt = select(CVProfile)
274
+ result = await db.execute(stmt)
275
+ return result.scalars().all()
276
 
277
  async def get_profile_by_id(
278
  db: AsyncSession,
 
388
  .where(CVFilter.criteria_id == criteria_id)
389
  )
390
  result = await db.execute(stmt)
391
+ return result.scalar_one_or_none()
392
 
393
 
394
  async def get_weight_by_id(
 
397
  ):
398
  stmt = (
399
  select(CVWeight)
400
+ .where(CVWeight.weight_id == weight_id)
401
  )
402
  result = await db.execute(stmt)
403
+ return result.scalar_one_or_none()
 
404
 
405
  async def create_filter_and_weight(
406
  db: AsyncSession,
 
432
  return matching
433
 
434
 
435
+ from services.models.data_model import AIMatchProfile
436
+
437
+ async def create_matchings(
438
+ db: AsyncSession,
439
+ matchings: list,
440
+ ) -> list[CVMatching]:
441
+ try:
442
+ orm_objects = [
443
+ CVMatching(**matching)
444
+ for matching in matchings
445
+ ]
446
+ db.add_all(orm_objects)
447
+ await db.commit()
448
+ for obj in orm_objects:
449
+ await db.refresh(obj)
450
+ return orm_objects
451
+ except Exception as E:
452
+ print(f"Error creating matchings: {E}")
453
+ raise
454
+
455
+
456
  async def get_matching_by_profile_and_criteria(
457
  db: AsyncSession,
458
  profile_id: str,
 
485
  return score
486
 
487
 
488
+ async def create_scores(
489
+ db: AsyncSession,
490
+ scores: list[CVScore],
491
+ ) -> list[CVScore]:
492
+ try:
493
+ db.add_all(scores)
494
+ await db.commit()
495
+ for score in scores:
496
+ await db.refresh(score)
497
+ return scores
498
+ except Exception as E:
499
+ print(f"Error creating scores: {E}")
500
+ raise
501
+
502
+
503
  async def get_scores_by_criteria(
504
  db: AsyncSession,
505
  criteria_id: str,
externals/databases/pg_models.py CHANGED
@@ -104,7 +104,7 @@ class CVFilter(Base):
104
  class CVWeight(Base):
105
  __tablename__ = "cv_weight"
106
 
107
- weight_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
108
  criteria_id = Column(UUID(as_uuid=True), primary_key=False, nullable=False)
109
 
110
  gpa_edu_1 = Column(DOUBLE_PRECISION)
@@ -135,7 +135,7 @@ class CVMatching(Base):
135
 
136
  matching_id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
137
  profile_id = Column(UUID(as_uuid=True))
138
- criteria_id = Column(UUID(as_uuid=True))
139
 
140
  gpa_edu_1 = Column(BOOLEAN)
141
  gpa_edu_2 = Column(BOOLEAN)
@@ -165,6 +165,7 @@ class CVScore(Base):
165
 
166
  scoring_id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
167
  matching_id = Column(UUID(as_uuid=True))
 
168
  score = Column(Integer)
169
 
170
  created_at = Column(TIMESTAMP(timezone=True), server_default=master_config.JAKARTA_NOW)
 
104
  class CVWeight(Base):
105
  __tablename__ = "cv_weight"
106
 
107
+ weight_id = Column(UUID(as_uuid=True), primary_key=True, server_default=master_config.JAKARTA_NOW)
108
  criteria_id = Column(UUID(as_uuid=True), primary_key=False, nullable=False)
109
 
110
  gpa_edu_1 = Column(DOUBLE_PRECISION)
 
135
 
136
  matching_id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
137
  profile_id = Column(UUID(as_uuid=True))
138
+ weight_id = Column(UUID(as_uuid=True))
139
 
140
  gpa_edu_1 = Column(BOOLEAN)
141
  gpa_edu_2 = Column(BOOLEAN)
 
165
 
166
  scoring_id = Column(UUID(as_uuid=True), primary_key=True, server_default=func.uuid_generate_v4())
167
  matching_id = Column(UUID(as_uuid=True))
168
+ profile_id = Column(UUID(as_uuid=True))
169
  score = Column(Integer)
170
 
171
  created_at = Column(TIMESTAMP(timezone=True), server_default=master_config.JAKARTA_NOW)
interfaces/api/agentic.py CHANGED
@@ -147,9 +147,21 @@ async def calculate_score(
147
  db=Depends(get_db),
148
  current_user: CVUser = Depends(get_current_user),
149
  ):
150
- # TODO: endpoint to calculate profile matching
151
- pass
152
-
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  # @router.get("/get_profile_table")
155
  # async def get_profile_table(
 
147
  db=Depends(get_db),
148
  current_user: CVUser = Depends(get_current_user),
149
  ):
150
+ try:
151
+ agentic_service = AgenticService(db=db,
152
+ user=current_user)
153
+ data = await agentic_service.score.scoring(weight_id=weight_id)
154
+ return {
155
+ "status": "success",
156
+ "message": "Score calculated successfully",
157
+ "data": data
158
+ }
159
+ except Exception as E:
160
+ logger.error(f"calculate score error: {E}")
161
+ raise HTTPException(
162
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
163
+ detail=f"calculate score error: {E}"
164
+ )
165
 
166
  # @router.get("/get_profile_table")
167
  # async def get_profile_table(
interfaces/handlers/agentic_handlers.py DELETED
File without changes
interfaces/handlers/handlers.py DELETED
@@ -1,2 +0,0 @@
1
-
2
-
 
 
 
interfaces/handlers/knowledge_handlers.py DELETED
File without changes
services/agentic/agentic_setup.py CHANGED
@@ -2,8 +2,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
2
  from externals.databases.pg_models import CVUser
3
  from services.agentic.filter import AgenticFilterService
4
  from services.agentic.weight import AgenticWeightService
 
5
  # from services.agentic.matching import AgenticMatchingService
6
- # from services.agentic.profile_scoring import AgenticScoringService
7
 
8
 
9
  from utils.logger import get_logger
@@ -21,6 +21,8 @@ class AgenticService:
21
 
22
  self.filter = AgenticFilterService(db, user)
23
  self.weight = AgenticWeightService(db, user)
 
 
24
  # self.filter = AgenticMatchingService(db, user)
25
  # self.filter = AgenticScoringService(db, user)
26
 
 
2
  from externals.databases.pg_models import CVUser
3
  from services.agentic.filter import AgenticFilterService
4
  from services.agentic.weight import AgenticWeightService
5
+ from services.agentic.profile_scoring import AgenticScoringService
6
  # from services.agentic.matching import AgenticMatchingService
 
7
 
8
 
9
  from utils.logger import get_logger
 
21
 
22
  self.filter = AgenticFilterService(db, user)
23
  self.weight = AgenticWeightService(db, user)
24
+ self.score = AgenticScoringService(db, user)
25
+
26
  # self.filter = AgenticMatchingService(db, user)
27
  # self.filter = AgenticScoringService(db, user)
28
 
services/agentic/profile_scoring.py CHANGED
@@ -1,30 +1,32 @@
1
- # from typing import List
 
2
  from langchain_core.prompts import ChatPromptTemplate
3
 
4
  from config.constant import ProfileFieldTypes
5
- from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
6
- from externals.databases.pg_models import CVProfile, CVWeight, CVFilter
7
  from services.llms.LLM import model_4o_2
8
  from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
9
- from models.data_model import AIProfile
10
  from services.models.data_model import (
11
  AIMatchProfile,
12
  Criteria,
13
  CriteriaWeight,
14
  LOGIC_NUMERIC,
15
  LOGIC_CATEGORICAL,
16
- InputScoring,
17
- DataResponseMatchOne,
18
- ResponseMatchOne,
19
- InputScoringBulk,
20
- DataResponseMatchBulk,
21
- ResponseMatchBulk
22
  )
23
  # from services.knowledge.knowledge_setup import KnowledgeService
24
  from services.agentic.weight import AgenticWeightService
25
  from services.agentic.filter import AgenticFilterService
26
  from services.knowledge.get_profile import KnowledgeGetProfileService
27
  from sqlalchemy.ext.asyncio import AsyncSession
 
28
  from utils.logger import get_logger
29
 
30
 
@@ -41,18 +43,22 @@ def helper_get_operator(col_name: str):
41
  return op
42
 
43
  def helper_judge_scoring(a_profile, b_criteria, rules) -> bool:
44
- if rules == "greater than":
45
- return int(a_profile > b_criteria)
46
- elif rules == "less than":
47
- return int(a_profile < b_criteria)
48
- elif rules == "equal":
49
- return int(a_profile == b_criteria)
50
- elif rules == "greater than or equal":
51
- return int(a_profile >= b_criteria)
52
- elif rules == "less than or equal":
53
- return int(a_profile <= b_criteria)
54
- return 0
55
-
 
 
 
 
56
 
57
  # @deprecated
58
  # def comparison_parser(input_scoring: InputScoring):
@@ -81,10 +87,11 @@ def comparison_parser(profile:AIProfile, criteria:Criteria):
81
  comparison += "\n| --- | --- | --- | --- | --- |"
82
 
83
  for k, v in criteria.items():
 
84
  op = helper_get_operator(k)
85
  if 'gpa' in k or 'yoe' in k:
86
  judges = helper_judge_scoring(a_profile=profile.get(k),
87
- b_criteria=v,
88
  rules=op)
89
  else:
90
  judges = '???'
@@ -409,6 +416,20 @@ class AgenticScoringService:
409
 
410
  matching_result = await gen_ai.agenerate()
411
  return matching_result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
  def _calculate_score(self, match_result:AIMatchProfile, weight_data:CriteriaWeight) -> float:
414
  """Returns matching score (float) with min-max is 0-100.0
@@ -427,99 +448,128 @@ class AgenticScoringService:
427
  # weight_data = self.weight_service.get_weight_by_weight_id(weight_id)
428
 
429
  for k, v in weight_data.items():
430
- if k.startswith("w_") and v:
431
- total_weight += v
432
 
433
- # logger.info(f"👁️ helper_calculate_score/total_weight: {total_weight}")
434
 
435
  if total_weight > 1.0:
436
  # normalized weight
437
  for k, v in weight_data.items():
438
- if k.startswith("w_") and v:
439
- norm_criteria[k] = v/total_weight
440
  else:
441
  norm_criteria = weight_data.copy()
442
 
443
  # logger.info(f"👁️ helper_calculate_score/norm_criteria: {norm_criteria}")
 
 
 
 
444
 
445
- for k, value_comparison in match_result.items():
446
- if f"w_{k}" in norm_criteria and value_comparison:
447
- temp_score = norm_criteria[f"w_{k}"] * value_comparison * 100
448
- # logger.info(f"👁️ w_{k}: {temp_score}")
449
-
450
  score += temp_score
451
 
452
  # logger.info(f"👁️ helper_calculate_score/score: {score}")
453
  return score
454
 
455
- async def scoring(self, profile_id: str, criteria_id: str, weight_id: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  try:
457
- _profile:CVProfile = self.profile_service.get_profile(profile_id=profile_id)
458
- _criteria:CVFilter = self.criteria_service.get_filter_by_id(criteria_id=criteria_id)
459
- _weight:CVWeight = self.weight_service.get_weight_by_weight_id(weight_id=weight_id)
 
460
 
461
- profile = AIProfile(
462
- fullname=_profile.get("fullname"),
463
- gpa_edu_1=_profile.get("gpa_edu_1"),
464
- univ_edu_1=_profile.get("univ_edu_1"),
465
- major_edu_1=_profile.get("major_edu_1"),
466
- gpa_edu_2=_profile.get("gpa_edu_2"),
467
- univ_edu_2=_profile.get("univ_edu_2"),
468
- major_edu_2=_profile.get("major_edu_2"),
469
- gpa_edu_3=_profile.get("gpa_edu_3"),
470
- univ_edu_3=_profile.get("univ_edu_3"),
471
- major_edu_3=_profile.get("major_edu_3"),
472
- domicile=_profile.get("domicile"),
473
- yoe=_profile.get("yoe"),
474
- hardskills=_profile.get("hardskills"),
475
- softskills=_profile.get("softskills"),
476
- certifications=_profile.get("certifications"),
477
- business_domain=_profile.get("business_domain")
478
- )
 
 
 
 
 
 
 
 
 
479
 
480
  criteria = Criteria(
481
- gpa_edu_1=_criteria.get("gpa_edu_1"),
482
- univ_edu_1=_criteria.get("univ_edu_1"),
483
- major_edu_1=_criteria.get("major_edu_1"),
484
- gpa_edu_2=_criteria.get("gpa_edu_2"),
485
- univ_edu_2=_criteria.get("univ_edu_2"),
486
- major_edu_2=_criteria.get("major_edu_2"),
487
- gpa_edu_3=_criteria.get("gpa_edu_3"),
488
- univ_edu_3=_criteria.get("univ_edu_3"),
489
- major_edu_3=_criteria.get("major_edu_3"),
490
- domicile=_criteria.get("domicile"),
491
- yoe=_criteria.get("yoe"),
492
- hardskills=_criteria.get("hardskills"),
493
- softskills=_criteria.get("softskills"),
494
- certifications=_criteria.get("certifications"),
495
- business_domain=_criteria.get("business_domain")
496
  )
497
 
498
  weight = CriteriaWeight(
499
- gpa_edu_1=_weight.get("gpa_edu_1"),
500
- univ_edu_1=_weight.get("univ_edu_1"),
501
- major_edu_1=_weight.get("major_edu_1"),
502
- gpa_edu_2=_weight.get("gpa_edu_2"),
503
- univ_edu_2=_weight.get("univ_edu_2"),
504
- major_edu_2=_weight.get("major_edu_2"),
505
- gpa_edu_3=_weight.get("gpa_edu_3"),
506
- univ_edu_3=_weight.get("univ_edu_3"),
507
- major_edu_3=_weight.get("major_edu_3"),
508
- domicile=_weight.get("domicile"),
509
- yoe=_weight.get("yoe"),
510
- hardskills=_weight.get("hardskills"),
511
- softskills=_weight.get("softskills"),
512
- certifications=_weight.get("certifications"),
513
- business_domain=_weight.get("business_domain")
514
  )
515
 
516
- match_result:AIMatchProfile = await self._ai_matching(profile, criteria)
517
- score = self._calculate_score(match_result=match_result, weight_data=weight)
518
-
519
- return score
 
 
 
 
 
 
 
 
520
  except Exception as E:
521
  logger.error(f"profile scoring error, {E}")
 
522
  raise
523
-
524
-
525
-
 
1
+ import traceback
2
+ from typing import List
3
  from langchain_core.prompts import ChatPromptTemplate
4
 
5
  from config.constant import ProfileFieldTypes
6
+ # from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
7
+ from externals.databases.pg_models import CVProfile, CVWeight, CVFilter, CVScore, CVMatching
8
  from services.llms.LLM import model_4o_2
9
  from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
10
+ from services.models.data_model import AIProfile
11
  from services.models.data_model import (
12
  AIMatchProfile,
13
  Criteria,
14
  CriteriaWeight,
15
  LOGIC_NUMERIC,
16
  LOGIC_CATEGORICAL,
17
+ # InputScoring,
18
+ # DataResponseMatchOne,
19
+ # ResponseMatchOne,
20
+ # InputScoringBulk,
21
+ # DataResponseMatchBulk,
22
+ # ResponseMatchBulk
23
  )
24
  # from services.knowledge.knowledge_setup import KnowledgeService
25
  from services.agentic.weight import AgenticWeightService
26
  from services.agentic.filter import AgenticFilterService
27
  from services.knowledge.get_profile import KnowledgeGetProfileService
28
  from sqlalchemy.ext.asyncio import AsyncSession
29
+ from externals.databases.pg_crud import get_profiles, create_matchings, create_scores
30
  from utils.logger import get_logger
31
 
32
 
 
43
  return op
44
 
45
  def helper_judge_scoring(a_profile, b_criteria, rules) -> bool:
46
+ try:
47
+ if rules == "greater than":
48
+ return int(a_profile > b_criteria)
49
+ elif rules == "less than":
50
+ return int(a_profile < b_criteria)
51
+ elif rules == "equal":
52
+ return int(a_profile == b_criteria)
53
+ elif rules == "greater than or equal":
54
+ return int(a_profile >= b_criteria)
55
+ elif rules == "less than or equal":
56
+ return int(a_profile <= b_criteria)
57
+ return 0
58
+ except Exception as E:
59
+ logger.error(f"❌ error in helper_judge_scoring: {E}")
60
+ logger.error(f"a_profile={a_profile}, b_criteria={b_criteria}")
61
+ return 0
62
 
63
  # @deprecated
64
  # def comparison_parser(input_scoring: InputScoring):
 
87
  comparison += "\n| --- | --- | --- | --- | --- |"
88
 
89
  for k, v in criteria.items():
90
+ print(f" key comparison: {k}")
91
  op = helper_get_operator(k)
92
  if 'gpa' in k or 'yoe' in k:
93
  judges = helper_judge_scoring(a_profile=profile.get(k),
94
+ b_criteria=v,
95
  rules=op)
96
  else:
97
  judges = '???'
 
416
 
417
  matching_result = await gen_ai.agenerate()
418
  return matching_result
419
+
420
+ async def _ai_matching_bulk(self,
421
+ profiles:List,
422
+ criteria:Criteria) -> List:
423
+ try:
424
+ results = []
425
+ for i, p in enumerate(profiles):
426
+ print(f"==> {i+1} profile: {p} vs criteria: {criteria}")
427
+ tmp_matching:AIMatchProfile = await self._ai_matching(profile=p, criteria=criteria)
428
+ results.append(tmp_matching)
429
+ return results
430
+ except Exception as E:
431
+ print(f"❌ error in _ai_matching_bulk: {E}")
432
+ raise
433
 
434
  def _calculate_score(self, match_result:AIMatchProfile, weight_data:CriteriaWeight) -> float:
435
  """Returns matching score (float) with min-max is 0-100.0
 
448
  # weight_data = self.weight_service.get_weight_by_weight_id(weight_id)
449
 
450
  for k, v in weight_data.items():
451
+ total_weight += v
 
452
 
453
+ logger.info(f"👁️ helper_calculate_score/total_weight: {total_weight}")
454
 
455
  if total_weight > 1.0:
456
  # normalized weight
457
  for k, v in weight_data.items():
458
+ norm_criteria[k] = v/total_weight
 
459
  else:
460
  norm_criteria = weight_data.copy()
461
 
462
  # logger.info(f"👁️ helper_calculate_score/norm_criteria: {norm_criteria}")
463
+ match_dict = {
464
+ column.name: getattr(match_result, column.name)
465
+ for column in match_result.__table__.columns
466
+ }
467
 
468
+ for k, value_comparison in match_dict.items():
469
+ if k in norm_criteria and value_comparison:
470
+ temp_score = norm_criteria[k] * value_comparison * 100
 
 
471
  score += temp_score
472
 
473
  # logger.info(f"👁️ helper_calculate_score/score: {score}")
474
  return score
475
 
476
+
477
+ def _calculate_score_bulk(self, match_results:List[CVMatching], weight_data:CriteriaWeight) -> List[CVScore]:
478
+ scores = []
479
+ for i, match_result in enumerate(match_results):
480
+ temp = CVScore()
481
+ temp.profile_id = match_result.profile_id
482
+ temp.matching_id = match_result.matching_id
483
+ temp.score = self._calculate_score(match_result=match_result, weight_data=weight_data)
484
+ # print(f"{i+1} match_result: {match_result} vs weight_data: {weight_data}")
485
+ # score = self._calculate_score(match_result=match_result, weight_data=weight_data)
486
+ scores.append(temp)
487
+ return scores
488
+
489
+ async def scoring(self, weight_id: str):
490
  try:
491
+ # Get profile data all
492
+ all_profiles = await get_profiles(self.db)
493
+ print(f"Found {len(all_profiles)} profiles to be scored")
494
+ all_profiles = all_profiles[:5] # FIXME: DELETE LATER
495
 
496
+ _weight:CVWeight = await self.weight_service.get_weight_by_weight_id(weight_id=weight_id)
497
+ print(f"Found weight: {_weight}")
498
+ print(f"--> criteria id: {_weight.criteria_id}")
499
+
500
+ _criteria:CVFilter = await self.criteria_service.get_filter_by_id(criteria_id=_weight.criteria_id)
501
+
502
+ all_tobe_scored = []
503
+ for p in all_profiles:
504
+ tmp_profile = AIProfile(
505
+ fullname=p.fullname,
506
+ gpa_edu_1=p.gpa_edu_1,
507
+ univ_edu_1=p.univ_edu_1,
508
+ major_edu_1=p.major_edu_1,
509
+ gpa_edu_2=p.gpa_edu_2,
510
+ univ_edu_2=p.univ_edu_2,
511
+ major_edu_2=p.major_edu_2,
512
+ gpa_edu_3=p.gpa_edu_3,
513
+ univ_edu_3=p.univ_edu_3,
514
+ major_edu_3=p.major_edu_3,
515
+ domicile=p.domicile,
516
+ yoe=p.yoe,
517
+ hardskills=p.hardskills,
518
+ softskills=p.softskills,
519
+ certifications=p.certifications,
520
+ business_domain=p.business_domain
521
+ )
522
+ all_tobe_scored.append(tmp_profile)
523
 
524
  criteria = Criteria(
525
+ gpa_edu_1=_criteria.gpa_edu_1,
526
+ univ_edu_1=_criteria.univ_edu_1,
527
+ major_edu_1=_criteria.major_edu_1,
528
+ gpa_edu_2=_criteria.gpa_edu_2,
529
+ univ_edu_2=_criteria.univ_edu_2,
530
+ major_edu_2=_criteria.major_edu_2,
531
+ gpa_edu_3=_criteria.gpa_edu_3,
532
+ univ_edu_3=_criteria.univ_edu_3,
533
+ major_edu_3=_criteria.major_edu_3,
534
+ domicile=_criteria.domicile,
535
+ yoe=_criteria.yoe,
536
+ hardskills=_criteria.hardskills,
537
+ softskills=_criteria.softskills,
538
+ certifications=_criteria.certifications,
539
+ business_domain=_criteria.business_domain
540
  )
541
 
542
  weight = CriteriaWeight(
543
+ gpa_edu_1=_weight.gpa_edu_1,
544
+ univ_edu_1=_weight.univ_edu_1,
545
+ major_edu_1=_weight.major_edu_1,
546
+ gpa_edu_2=_weight.gpa_edu_2,
547
+ univ_edu_2=_weight.univ_edu_2,
548
+ major_edu_2=_weight.major_edu_2,
549
+ gpa_edu_3=_weight.gpa_edu_3,
550
+ univ_edu_3=_weight.univ_edu_3,
551
+ major_edu_3=_weight.major_edu_3,
552
+ domicile=_weight.domicile,
553
+ yoe=_weight.yoe,
554
+ hardskills=_weight.hardskills,
555
+ softskills=_weight.softskills,
556
+ certifications=_weight.certifications,
557
+ business_domain=_weight.business_domain
558
  )
559
 
560
+ match_results:List[AIMatchProfile] = await self._ai_matching_bulk(all_tobe_scored, criteria)
561
+ # match_results kurang profile_id dan criteria id
562
+ match_results = [{"profile_id": p.profile_id, "weight_id": weight_id, **match_results[i]} for i, p in enumerate(all_profiles)]
563
+ # Insert Match Result to DB
564
+ matchings = await create_matchings(self.db, match_results)
565
+
566
+ score_results = self._calculate_score_bulk(match_results=matchings, weight_data=weight)
567
+
568
+ # Insert Score Result to DB
569
+ scores = await create_scores(self.db, score_results)
570
+
571
+ return scores
572
  except Exception as E:
573
  logger.error(f"profile scoring error, {E}")
574
+ traceback.print_exc()
575
  raise
 
 
 
services/agentic/score.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys
2
+
3
+ from fastapi import HTTPException, status
4
+ from sqlalchemy.ext.asyncio import AsyncSession
5
+
6
+ from externals.databases.pg_models import CVWeight
7
+ from externals.databases.pg_crud import (
8
+ create_filter,
9
+ get_filter_by_id,
10
+ create_weight,
11
+ get_weight_by_id
12
+ )
13
+ from utils.logger import get_logger
14
+
15
+ logger = get_logger("weight agentic service")
16
+
17
+
18
+ class AgenticScoreService:
19
+ def __init__(self, db: AsyncSession, user):
20
+ self.db = db
21
+ self.user = user
22
+
23
+ async def calculate_score(self, weight_id: str) -> dict:
24
+ """Return criteria_id:str"""
25
+
26
+ try:
27
+ weight_data = await get_weight_by_id(db=self.db, weight_id=weight_id)
28
+
29
+ filters = await get_filter_by_id(db=self.db, criteria_id=weight_data.criteria_id)
30
+ weights = {field: getattr(weight_data, field) for field in weight_data.__table__.columns.keys() if field not in ["created_at", "_sa_instance_state", "criteria_id", "weight_id"]}
31
+
32
+ # Calculate Matching
33
+
34
+ # Calculate Scoring
35
+
36
+
37
+ except Exception as E:
38
+ logger.error(f"get weight by weight id error, {E}")
39
+ raise HTTPException(
40
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
41
+ detail=f"get weight by weight id error: {E}"
42
+ )
services/models/data_model.py CHANGED
@@ -47,10 +47,10 @@ class AIProfile(TypedDict):
47
  domicile: str = Field(description="Current domicile of the candidate", default="-")
48
 
49
  yoe: float = Field(description="The candidate's total years of experience (as an float)", default=0)
50
- hardskills: Optional[List[str]] = Field(description="List of the candidate's hard skills", default=[])
51
- softskills: Optional[List[str]] = Field(description="List of the candidate's soft skills", default=[])
52
- certifications: Optional[List[str]] = Field(description="List of the candidate's certifications", default=[])
53
- business_domain: Optional[List[str]] = Field(description="List of the candidate's business domain experience based on working experience or project", default=[])
54
 
55
  class Profile(AIProfile):
56
  profile_id: str
 
47
  domicile: str = Field(description="Current domicile of the candidate", default="-")
48
 
49
  yoe: float = Field(description="The candidate's total years of experience (as an float)", default=0)
50
+ hardskills: Optional[List[str]] = Field(description="List of the candidate's hard skills",default_factory=list)
51
+ softskills: Optional[List[str]] = Field(description="List of the candidate's soft skills", default_factory=list)
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