Spaces:
Sleeping
Sleeping
[NOTICKET] Feat: calculate_score v2
Browse files- externals/databases/pg_crud.py +89 -21
- externals/databases/pg_models.py +1 -1
- interfaces/api/agentic.py +2 -2
- services/agentic/filter.py +45 -0
- services/agentic/profile_scoring.py +22 -36
- services/models/data_model.py +25 -0
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 |
-
|
|
|
|
| 391 |
)
|
| 392 |
|
| 393 |
if filter.univ_edu_2:
|
| 394 |
conditions.append(
|
| 395 |
-
func.lower(CVProfile.univ_edu_2)
|
| 396 |
-
|
|
|
|
| 397 |
)
|
| 398 |
|
| 399 |
if filter.univ_edu_3:
|
| 400 |
conditions.append(
|
| 401 |
-
func.lower(CVProfile.univ_edu_3)
|
| 402 |
-
|
|
|
|
| 403 |
)
|
| 404 |
|
| 405 |
-
# --- Major (case-insensitive) ---
|
| 406 |
if filter.major_edu_1:
|
| 407 |
conditions.append(
|
| 408 |
-
func.lower(CVProfile.major_edu_1)
|
| 409 |
-
|
|
|
|
| 410 |
)
|
| 411 |
|
| 412 |
if filter.major_edu_2:
|
| 413 |
conditions.append(
|
| 414 |
-
func.lower(CVProfile.major_edu_2)
|
| 415 |
-
|
|
|
|
| 416 |
)
|
| 417 |
|
| 418 |
if filter.major_edu_3:
|
| 419 |
conditions.append(
|
| 420 |
-
func.lower(CVProfile.major_edu_3)
|
| 421 |
-
|
|
|
|
| 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
|
| 582 |
if filter.univ_edu_2:
|
| 583 |
-
conditions.append(CVFilter.univ_edu_2
|
| 584 |
if filter.univ_edu_3:
|
| 585 |
-
conditions.append(CVFilter.univ_edu_3
|
| 586 |
|
| 587 |
# --- Major ---
|
| 588 |
if filter.major_edu_1:
|
| 589 |
-
conditions.append(CVFilter.major_edu_1
|
| 590 |
if filter.major_edu_2:
|
| 591 |
-
conditions.append(CVFilter.major_edu_2
|
| 592 |
if filter.major_edu_3:
|
| 593 |
-
conditions.append(CVFilter.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:
|
| 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)} |
|
| 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:
|
| 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 |
-
|
| 491 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 604 |
-
|
| 605 |
-
major_edu_3=filter.get("major_edu_3"),
|
| 606 |
-
|
| 607 |
-
domicile=filter.get("domicile"),
|
| 608 |
-
yoe=filter.get("yoe"),
|
| 609 |
|
| 610 |
-
|
| 611 |
-
|
| 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 =
|
| 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)
|