anky2002 commited on
Commit
b23ca18
·
verified ·
1 Parent(s): fa0a99b

feat: Add salary negotiation AI coach endpoint

Browse files
Files changed (1) hide show
  1. backend/app/api/routes/negotiate.py +110 -0
backend/app/api/routes/negotiate.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Salary Negotiation Coach - AI-powered negotiation strategy and scripts.
3
+ """
4
+ import json
5
+ import logging
6
+
7
+ import litellm
8
+ from fastapi import APIRouter
9
+ from pydantic import BaseModel
10
+
11
+ from app.api.deps import CurrentUser, DBSession
12
+ from app.core.config import settings
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ router = APIRouter(prefix="/ai/negotiate", tags=["salary-negotiation"])
17
+
18
+ NEGOTIATION_PROMPT = """You are an expert salary negotiation coach. Given the context about an offer and the candidate's situation, provide a comprehensive negotiation strategy.
19
+
20
+ Return JSON:
21
+ {
22
+ "assessment": "Is this offer competitive? Brief analysis.",
23
+ "should_negotiate": true/false,
24
+ "target_range": {"min": number, "max": number, "stretch": number},
25
+ "strategy": {
26
+ "approach": "collaborative|assertive|data-driven",
27
+ "timing": "When to negotiate",
28
+ "leverage_points": ["What gives you negotiation power"],
29
+ "risks": ["Potential downsides to be aware of"]
30
+ },
31
+ "scripts": {
32
+ "initial_response": "What to say when receiving the offer",
33
+ "counter_offer": "How to present your counter",
34
+ "if_they_push_back": "Response to resistance",
35
+ "closing": "How to close the deal"
36
+ },
37
+ "non_salary_items": [
38
+ {"item": "Signing bonus", "how_to_ask": "...", "typical_value": "..."},
39
+ {"item": "Equity/RSUs", "how_to_ask": "...", "typical_value": "..."},
40
+ {"item": "Remote flexibility", "how_to_ask": "...", "typical_value": "..."}
41
+ ],
42
+ "mistakes_to_avoid": ["..."],
43
+ "confidence_rating": "How strong your position is (1-10)"
44
+ }"""
45
+
46
+
47
+ class NegotiationRequest(BaseModel):
48
+ offer_salary: int
49
+ offer_equity: str | None = None
50
+ offer_bonus: str | None = None
51
+ role_title: str
52
+ company_name: str
53
+ your_current_salary: int | None = None
54
+ years_experience: int | None = None
55
+ competing_offers: bool = False
56
+ market_rate_min: int | None = None
57
+ market_rate_max: int | None = None
58
+ additional_context: str | None = None
59
+
60
+
61
+ @router.post("")
62
+ async def get_negotiation_strategy(data: NegotiationRequest, db: DBSession, user: CurrentUser):
63
+ """Get AI-powered salary negotiation strategy and scripts."""
64
+ context = f"""
65
+ Offer Details:
66
+ - Role: {data.role_title} at {data.company_name}
67
+ - Base Salary: ${data.offer_salary:,}
68
+ - Equity: {data.offer_equity or 'Not mentioned'}
69
+ - Bonus: {data.offer_bonus or 'Not mentioned'}
70
+
71
+ Candidate Context:
72
+ - Current Salary: ${data.your_current_salary:,} if {data.your_current_salary} else 'Not disclosed'
73
+ - Years Experience: {data.years_experience or 'Not specified'}
74
+ - Competing Offers: {'Yes' if data.competing_offers else 'No'}
75
+ - Market Rate: ${data.market_rate_min:,} - ${data.market_rate_max:,} if {data.market_rate_min and data.market_rate_max} else 'Unknown'
76
+
77
+ Additional Context: {data.additional_context or 'None'}
78
+
79
+ Provide a negotiation strategy."""
80
+
81
+ model = "ollama/llama3.2"
82
+ if settings.OPENAI_API_KEY:
83
+ model = "openai/gpt-4o"
84
+ elif settings.ANTHROPIC_API_KEY:
85
+ model = "anthropic/claude-sonnet-4-20250514"
86
+ elif settings.DEEPSEEK_API_KEY:
87
+ model = "deepseek/deepseek-chat"
88
+
89
+ try:
90
+ response = await litellm.acompletion(
91
+ model=model,
92
+ messages=[
93
+ {"role": "system", "content": NEGOTIATION_PROMPT},
94
+ {"role": "user", "content": context},
95
+ ],
96
+ response_format={"type": "json_object"},
97
+ temperature=0.4,
98
+ max_tokens=2500,
99
+ )
100
+ result = json.loads(response.choices[0].message.content)
101
+ result["offer_analyzed"] = {
102
+ "salary": data.offer_salary,
103
+ "company": data.company_name,
104
+ "role": data.role_title,
105
+ }
106
+ return result
107
+
108
+ except Exception as e:
109
+ logger.error(f"Negotiation coach failed: {e}")
110
+ return {"error": str(e)}