ishaq101 commited on
Commit
6a0f2f3
·
1 Parent(s): b3a6c78

[NOTICKET] Add new endpoint /v2/calculate_score

Browse files
externals/databases/pg_crud.py CHANGED
@@ -338,6 +338,20 @@ async def get_profiles(
338
  result = await db.execute(stmt)
339
  return result.scalars().all()
340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
  from sqlalchemy import select, and_, func, cast, String
343
  from sqlalchemy.ext.asyncio import AsyncSession
 
338
  result = await db.execute(stmt)
339
  return result.scalars().all()
340
 
341
+ @retry_db(retries=2, delay=2)
342
+ async def get_profiles_by_user_id(
343
+ db: AsyncSession,
344
+ current_user: CVUser,
345
+ ) -> List[CVProfile]:
346
+ stmt = (
347
+ select(CVProfile)
348
+ .join(CVFile, CVProfile.file_id == CVFile.file_id)
349
+ .where(CVFile.user_id == current_user.user_id)
350
+ )
351
+ result = await db.execute(stmt)
352
+ return result.scalars().all()
353
+
354
+
355
 
356
  from sqlalchemy import select, and_, func, cast, String
357
  from sqlalchemy.ext.asyncio import AsyncSession
interfaces/api/agentic.py CHANGED
@@ -7,6 +7,7 @@ from services.agentic.agentic_setup import AgenticService
7
  from services.knowledge.knowledge_setup import KnowledgeService
8
  from services.models.data_model import (Criteria,
9
  CriteriaWeight,
 
10
  PayloadMatchOne,
11
  ResponseMatchOne,
12
  DataResponseMatchOne,
@@ -163,6 +164,30 @@ async def calculate_score(
163
  detail=f"calculate score error: {E}"
164
  )
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  # @router.get("/get_profile_table")
167
  # async def get_profile_table(
168
  # limit: int,
 
7
  from services.knowledge.knowledge_setup import KnowledgeService
8
  from services.models.data_model import (Criteria,
9
  CriteriaWeight,
10
+ InputScoringBulk,
11
  PayloadMatchOne,
12
  ResponseMatchOne,
13
  DataResponseMatchOne,
 
164
  detail=f"calculate score error: {E}"
165
  )
166
 
167
+
168
+ @router.post("/v2/calculate_score")
169
+ async def calculate_score(
170
+ requirements: InputScoringBulk,
171
+ current_user: CVUser = Depends(get_current_user),
172
+ db=Depends(get_db),
173
+ ):
174
+ try:
175
+ agentic_service = AgenticService(db=db,
176
+ user=current_user)
177
+
178
+ data = await agentic_service.score.scoring_v2(requirements=requirements)
179
+ return {
180
+ "status": "success",
181
+ "message": "Score calculated successfully",
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")
192
  # async def get_profile_table(
193
  # limit: int,
services/agentic/profile_scoring.py CHANGED
@@ -6,7 +6,7 @@ from langchain_core.prompts import ChatPromptTemplate
6
  from config.constant import ProfileFieldTypes
7
  # from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
8
  from externals.databases.pg_models import CVProfile, CVWeight, CVFilter, CVScore, CVMatching
9
- from services.llms.LLM import model_4o_2
10
  # from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
11
  from services.base.BaseGenerator_v2 import BaseAIGenerator, MetadataObservability
12
  from services.models.data_model import AIProfile
@@ -14,6 +14,7 @@ from services.models.data_model import (
14
  AIMatchProfile,
15
  Criteria,
16
  CriteriaWeight,
 
17
  LOGIC_NUMERIC,
18
  LOGIC_CATEGORICAL,
19
  # InputScoring,
@@ -28,7 +29,7 @@ from services.agentic.weight import AgenticWeightService
28
  from services.agentic.filter import AgenticFilterService
29
  from services.knowledge.get_profile import KnowledgeGetProfileService
30
  from sqlalchemy.ext.asyncio import AsyncSession
31
- from externals.databases.pg_crud import get_profiles, create_matchings, create_scores, get_profiles_by_criteria_id, get_weight_by_id
32
  from utils.logger import get_logger
33
 
34
 
@@ -403,7 +404,8 @@ class AgenticScoringService:
403
  "comparison": comparison_text
404
  }
405
 
406
- llm = model_4o_2.with_structured_output(AIMatchProfile)
 
407
 
408
  gen_ai = BaseAIGenerator(
409
  task_name="ai matching",
@@ -573,6 +575,86 @@ class AgenticScoringService:
573
  # Insert Score Result to DB
574
  scores = await create_scores(self.db, score_results)
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  return scores
577
  except Exception as E:
578
  logger.error(f"profile scoring error, {E}")
 
6
  from config.constant import ProfileFieldTypes
7
  # from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
8
  from externals.databases.pg_models import CVProfile, CVWeight, CVFilter, CVScore, CVMatching
9
+ from services.llms.LLM import model_4o_2, model_5_1
10
  # from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
11
  from services.base.BaseGenerator_v2 import BaseAIGenerator, MetadataObservability
12
  from services.models.data_model import AIProfile
 
14
  AIMatchProfile,
15
  Criteria,
16
  CriteriaWeight,
17
+ InputScoringBulk,
18
  LOGIC_NUMERIC,
19
  LOGIC_CATEGORICAL,
20
  # InputScoring,
 
29
  from services.agentic.filter import AgenticFilterService
30
  from services.knowledge.get_profile import KnowledgeGetProfileService
31
  from sqlalchemy.ext.asyncio import AsyncSession
32
+ from externals.databases.pg_crud import get_profiles, create_matchings, create_scores, get_profiles_by_criteria_id, get_weight_by_id, get_profiles_by_user_id
33
  from utils.logger import get_logger
34
 
35
 
 
404
  "comparison": comparison_text
405
  }
406
 
407
+ # llm = model_4o_2.with_structured_output(AIMatchProfile)
408
+ llm = model_5_1.with_structured_output(AIMatchProfile)
409
 
410
  gen_ai = BaseAIGenerator(
411
  task_name="ai matching",
 
575
  # Insert Score Result to DB
576
  scores = await create_scores(self.db, score_results)
577
 
578
+ return scores
579
+ except Exception as E:
580
+ logger.error(f"profile scoring error, {E}")
581
+ traceback.print_exc()
582
+ raise
583
+
584
+
585
+ async def scoring_v2(self, requirements: InputScoringBulk):
586
+ try:
587
+ # Create criteria_id
588
+ filter_svc = AgenticFilterService(db=self.db, user=self.user)
589
+ criteria_id = await filter_svc.create_filter(filter=requirements.criteria)
590
+ weight_svc = AgenticWeightService(db=self.db, user=self.user)
591
+ cv_weight = CVWeight(
592
+ criteria_id=criteria_id,
593
+ weight_id=uuid4(),
594
+
595
+ gpa_edu_1=weight.get("gpa_edu_1"),
596
+ gpa_edu_2=weight.get("gpa_edu_2"),
597
+ gpa_edu_3=weight.get("gpa_edu_3"),
598
+
599
+ univ_edu_1=weight.get("univ_edu_1"),
600
+ univ_edu_2=weight.get("univ_edu_2"),
601
+ univ_edu_3=weight.get("univ_edu_3"),
602
+
603
+ major_edu_1=weight.get("major_edu_1"),
604
+ major_edu_2=weight.get("major_edu_2"),
605
+ major_edu_3=weight.get("major_edu_3"),
606
+
607
+ domicile=weight.get("domicile"),
608
+ yoe=weight.get("yoe"),
609
+
610
+ hardskills=weight.get("hardskills"),
611
+ softskills=weight.get("softskills"),
612
+ certifications=weight.get("certifications"),
613
+ business_domain=weight.get("business_domain")
614
+ )
615
+ weight_output = await weight_svc.create_weight(weight=cv_weight)
616
+ weight_id = weight_output.get("weight_id")
617
+
618
+ criteria = requirements.criteria
619
+ weight = requirements.criteria_weight
620
+
621
+ all_profiles = await get_profiles_by_user_id(db=self.db, current_user=self.user)
622
+ print(f"🫡 Found {len(all_profiles)} profiles to be scored")
623
+
624
+ all_tobe_scored = []
625
+ for p in all_profiles:
626
+ tmp_profile = AIProfile(
627
+ fullname=p.fullname,
628
+ gpa_edu_1=p.gpa_edu_1,
629
+ univ_edu_1=p.univ_edu_1,
630
+ major_edu_1=p.major_edu_1,
631
+ gpa_edu_2=p.gpa_edu_2,
632
+ univ_edu_2=p.univ_edu_2,
633
+ major_edu_2=p.major_edu_2,
634
+ gpa_edu_3=p.gpa_edu_3,
635
+ univ_edu_3=p.univ_edu_3,
636
+ major_edu_3=p.major_edu_3,
637
+ domicile=p.domicile,
638
+ yoe=p.yoe,
639
+ hardskills=p.hardskills,
640
+ softskills=p.softskills,
641
+ certifications=p.certifications,
642
+ business_domain=p.business_domain
643
+ )
644
+ all_tobe_scored.append(tmp_profile)
645
+
646
+
647
+ match_results:List[AIMatchProfile] = await self._ai_matching_bulk(all_tobe_scored, criteria) # TODO: refactor, use non LLM solution
648
+ # match_results kurang profile_id dan criteria id
649
+ match_results = [{"profile_id": p.profile_id, "weight_id": weight_id, **match_results[i]} for i, p in enumerate(all_profiles)]
650
+ # Insert Match Result to DB
651
+ matchings = await create_matchings(self.db, match_results)
652
+
653
+ score_results = self._calculate_score_bulk(match_results=matchings, weight_data=weight)
654
+
655
+ # Insert Score Result to DB
656
+ scores = await create_scores(self.db, score_results)
657
+
658
  return scores
659
  except Exception as E:
660
  logger.error(f"profile scoring error, {E}")
services/agentic/score.py DELETED
@@ -1,42 +0,0 @@
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/llms/LLM.py CHANGED
@@ -59,15 +59,15 @@ model_5mini = AzureChatOpenAI(
59
  disable_streaming=True
60
  )
61
 
62
- # model_5_1 = AzureChatOpenAI(
63
- # azure_endpoint=os.environ.get("azureai--endpoint--url--5.1"),
64
- # openai_api_version=os.environ.get("azureai--api--version--5.1"),
65
- # deployment_name=os.environ.get("azureai--deployment--name--5.1"),
66
- # openai_api_key=os.environ.get("azureai--api-key--5.1"),
67
- # openai_api_type="azure",
68
- # max_retries=2,
69
- # disable_streaming=True
70
- # )
71
 
72
  # model_5_1codex = AzureChatOpenAI(
73
  # azure_endpoint=os.environ.get("azureai--endpoint--url--5.1-codex"),
 
59
  disable_streaming=True
60
  )
61
 
62
+ model_5_1 = AzureChatOpenAI(
63
+ openai_api_key=os.environ.get("azureai__api_key__51"),
64
+ azure_endpoint=os.environ.get("azureai__endpoint__url__51"),
65
+ deployment_name=os.environ.get("azureai__deployment__name__51"),
66
+ openai_api_version=os.environ.get("azureai__api__version__51"),
67
+ openai_api_type="azure",
68
+ max_retries=2,
69
+ disable_streaming=True
70
+ )
71
 
72
  # model_5_1codex = AzureChatOpenAI(
73
  # azure_endpoint=os.environ.get("azureai--endpoint--url--5.1-codex"),
services/llms/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- __all__ = ['model_4omini', 'model_5mini', 'model_4o_2']
2
 
3
- from .LLM import model_4omini, model_5mini, model_4o_2
 
1
+ __all__ = ['model_4omini', 'model_5mini', 'model_4o_2', 'model_5_1']
2
 
3
+ from .LLM import model_4omini, model_5mini, model_4o_2, model_5_1
services/models/data_model.py CHANGED
@@ -60,39 +60,39 @@ class Profiles(TypedDict):
60
  profiles: List[Profile]
61
 
62
  class Criteria(TypedDict):
63
- gpa_edu_1: Optional[float] = None
64
- univ_edu_1: Optional[str] = None
65
- major_edu_1: Optional[str] = None
66
- gpa_edu_2: Optional[float] = None
67
- univ_edu_2: Optional[str] = None
68
- major_edu_2: Optional[str] = None
69
- gpa_edu_3: Optional[float] = None
70
- univ_edu_3: Optional[str] = None
71
- major_edu_3: Optional[str] = None
72
  domicile: Optional[str] = None
73
- yoe: Optional[int] = None
74
- hardskills: Optional[List] = None
75
- softskills: Optional[List] = None
76
- certifications: Optional[List] = None
77
- business_domain: Optional[List] = None
78
 
79
 
80
  class CriteriaWeight(TypedDict):
81
- gpa_edu_1: Optional[float] = None
82
- univ_edu_1: Optional[float] = None
83
- major_edu_1: Optional[float] = None
84
- gpa_edu_2: Optional[float] = None
85
- univ_edu_2: Optional[float] = None
86
- major_edu_2: Optional[float] = None
87
- gpa_edu_3: Optional[float] = None
88
- univ_edu_3: Optional[float] = None
89
- major_edu_3: Optional[float] = None
90
- domicile: Optional[float] = None
91
- yoe: Optional[float] = None
92
- hardskills: Optional[float] = None
93
- softskills: Optional[float] = None
94
- certifications: Optional[float] = None
95
- business_domain: Optional[float] = None
96
 
97
 
98
  # class InputScoring(AIProfile):
@@ -104,8 +104,7 @@ class InputScoring(TypedDict):
104
  profile_id: str = Field(description="profile id")
105
  weight_id: str = Field(description="weight id")
106
 
107
- class InputScoringBulk(TypedDict):
108
- profile_ids: List = Field(description="list of profile id")
109
  criteria: Criteria = Field(description="Criteria to be matched with the profile")
110
  criteria_weight: CriteriaWeight = Field(description="Criteria weight to be applied when profile matching")
111
 
 
60
  profiles: List[Profile]
61
 
62
  class Criteria(TypedDict):
63
+ gpa_edu_1: Optional[float] = 0
64
+ univ_edu_1: Optional[List[str]] = []
65
+ major_edu_1: Optional[List[str]] = []
66
+ gpa_edu_2: Optional[float] = 0
67
+ univ_edu_2: Optional[List[str]] = []
68
+ major_edu_2: Optional[List[str]] = []
69
+ gpa_edu_3: Optional[float] = 0
70
+ univ_edu_3: Optional[List[str]] = []
71
+ major_edu_3: Optional[List[str]] = []
72
  domicile: Optional[str] = None
73
+ yoe: Optional[int] = 0
74
+ hardskills: Optional[List[str]] = []
75
+ softskills: Optional[List[str]] = []
76
+ certifications: Optional[List[str]] = []
77
+ business_domain: Optional[List[str]] = []
78
 
79
 
80
  class CriteriaWeight(TypedDict):
81
+ gpa_edu_1: Optional[float] = 0
82
+ univ_edu_1: Optional[float] = 0
83
+ major_edu_1: Optional[float] = 0
84
+ gpa_edu_2: Optional[float] = 0
85
+ univ_edu_2: Optional[float] = 0
86
+ major_edu_2: Optional[float] = 0
87
+ gpa_edu_3: Optional[float] = 0
88
+ univ_edu_3: Optional[float] = 0
89
+ major_edu_3: Optional[float] = 0
90
+ domicile: Optional[float] = 0
91
+ yoe: Optional[float] = 0
92
+ hardskills: Optional[float] = 0
93
+ softskills: Optional[float] = 0
94
+ certifications: Optional[float] = 0
95
+ business_domain: Optional[float] = 0
96
 
97
 
98
  # class InputScoring(AIProfile):
 
104
  profile_id: str = Field(description="profile id")
105
  weight_id: str = Field(description="weight id")
106
 
107
+ class InputScoringBulk(TypedDict): #TODO: USE THIS ON /v2/calculate_score
 
108
  criteria: Criteria = Field(description="Criteria to be matched with the profile")
109
  criteria_weight: CriteriaWeight = Field(description="Criteria weight to be applied when profile matching")
110