mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
112 lines
3.5 KiB
Python
112 lines
3.5 KiB
Python
"""
|
|
backend/agents/matching_agent.py
|
|
|
|
Scores how well an applicant's preferences match an open contract's
|
|
requirements using a single Gemini call. Returns a 0-100 score and
|
|
a short explanation — used to rank applicants before the poster picks one.
|
|
"""
|
|
|
|
import json
|
|
from agents.base_agent import BaseAgent
|
|
|
|
_MATCHING_PROMPT = """You are a matching agent. Your job is to score how well an applicant's
|
|
preferences align with an open contract's requirements.
|
|
|
|
Contract type: {contract_type}
|
|
|
|
CONTRACT REQUIREMENTS (what the poster needs):
|
|
{requirements}
|
|
|
|
APPLICANT PREFERENCES (what the applicant brings / wants):
|
|
{preferences}
|
|
|
|
Evaluate on these axes:
|
|
1. Core match — does the applicant fundamentally fit what the contract needs?
|
|
2. Budget/rate alignment — are financial expectations compatible?
|
|
3. Timeline/availability — do schedules overlap?
|
|
4. Skills/criteria — do they have what is required?
|
|
5. Flexibility potential — could a negotiation bridge remaining gaps?
|
|
|
|
RESPOND ONLY with valid JSON in this exact format:
|
|
{{
|
|
"match_score": <integer 0-100>,
|
|
"match_reasoning": "<one concise sentence explaining the score>",
|
|
"key_alignments": ["<thing 1 that matches well>", "<thing 2>"],
|
|
"key_gaps": ["<gap 1>", "<gap 2>"]
|
|
}}
|
|
|
|
Scoring guide:
|
|
90-100 → Nearly perfect fit, minimal negotiation needed
|
|
70-89 → Good fit, small gaps bridgeable in negotiation
|
|
50-69 → Moderate fit, notable differences but workable
|
|
30-49 → Weak fit, significant gaps — negotiation will be hard
|
|
0-29 → Poor fit, fundamental incompatibility
|
|
"""
|
|
|
|
|
|
class MatchingAgent(BaseAgent):
|
|
def __init__(self):
|
|
super().__init__(system_prompt="You are a concise JSON-only matching agent.")
|
|
|
|
async def score_applicant(
|
|
self,
|
|
contract_requirements: dict,
|
|
applicant_preferences: dict,
|
|
contract_type: str,
|
|
) -> dict:
|
|
"""
|
|
Score how well an applicant matches a contract.
|
|
|
|
Returns:
|
|
{
|
|
"match_score": int (0-100),
|
|
"match_reasoning": str,
|
|
"key_alignments": list[str],
|
|
"key_gaps": list[str],
|
|
}
|
|
On failure returns a safe default with score=0.
|
|
"""
|
|
prompt = _MATCHING_PROMPT.format(
|
|
contract_type=contract_type,
|
|
requirements=json.dumps(contract_requirements, indent=2),
|
|
preferences=json.dumps(applicant_preferences, indent=2),
|
|
)
|
|
try:
|
|
# self.call() returns a dict — BaseAgent already handles JSON parsing
|
|
result = await self.call(prompt)
|
|
if "error" in result:
|
|
raise ValueError(result["error"])
|
|
# Clamp score to 0-100
|
|
result["match_score"] = max(0, min(100, int(result.get("match_score", 0))))
|
|
return result
|
|
except Exception as e:
|
|
print(f"[MatchingAgent] score_applicant failed: {e}")
|
|
return {
|
|
"match_score": 0,
|
|
"match_reasoning": "Could not compute score.",
|
|
"key_alignments": [],
|
|
"key_gaps": [],
|
|
}
|
|
|
|
|
|
# Module-level singleton
|
|
_agent = None
|
|
|
|
|
|
def get_matching_agent() -> MatchingAgent:
|
|
global _agent
|
|
if _agent is None:
|
|
_agent = MatchingAgent()
|
|
return _agent
|
|
|
|
|
|
async def score_applicant(
|
|
contract_requirements: dict,
|
|
applicant_preferences: dict,
|
|
contract_type: str,
|
|
) -> dict:
|
|
"""Convenience wrapper — use the module-level singleton."""
|
|
return await get_matching_agent().score_applicant(
|
|
contract_requirements, applicant_preferences, contract_type
|
|
)
|