File size: 14,263 Bytes
6d2c000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
"""
Intent Parser for Workforce Optimization
Extracts headcount reduction targets and adjusts skill requirements based on strategic intent
"""

import json
from typing import Dict, Any, Optional, Tuple
from openai import OpenAI
import os
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from typing import List
from typing import Literal, Union

# Load environment variables
load_dotenv()

# Default engineering skills and required levels
DEFAULT_ENGINEERING_SKILLS = {
    # Technical Skills
    "python": 3.0,
    "javascript": 2.5,
    "react": 2.5,
    "backend_design": 3.0,
    "system_architecture": 3.0,
    "api_development": 3.0,
    "database_management": 2.5,
    "devops": 2.5,
    "cloud_infrastructure": 2.5,
    "security": 2.5,
    
    # AI/ML Skills
    "machine_learning": 2.0,
    "pytorch": 2.0,
    "tensorflow": 1.5,
    "data_engineering": 2.5,
    "data_analysis": 2.5,
    
    # Soft Skills
    "communication": 3.0,
    "problem_solving": 3.5,
    "team_collaboration": 3.0,
    "leadership": 2.0,
    "project_management": 2.0,
    
    # Specialized Skills
    "frontend_optimization": 2.0,
    "mobile_development": 1.5,
    "testing": 2.5,
    "documentation": 2.5,
    "code_review": 3.0
}

# Prompt for extracting reduction target and intent
EXTRACTION_PROMPT = """You are an expert at understanding workforce optimization requests.

Given a user's input, extract TWO things:
1. The headcount reduction target (either as a percentage or absolute number)
2. The strategic intent or initiative (what the company is focusing on)

Return a JSON with this exact structure:
{
    "reduction_target": {
        "type": "percentage" or "absolute",
        "value": <number>
    },
    "intent": {
        "detected": true/false,
        "description": "<brief description of the strategic intent>",
        "focus_areas": ["<area1>", "<area2>", ...],
        "de_emphasis_areas": ["<area1>", "<area2>", ...]
    }
}

Examples:
- Input: "Reduce headcount by 20%"
  Output: {"reduction_target": {"type": "percentage", "value": 20}, "intent": {"detected": false}}

- Input: "Cut 5 engineers and focus on AI initiatives"
  Output: {"reduction_target": {"type": "absolute", "value": 5}, "intent": {"detected": true, "description": "Focus on AI initiatives", "focus_areas": ["artificial intelligence", "machine learning"], "de_emphasis_areas": []}}

- Input: "We need to reduce costs by 15% and shift from web to mobile development"
  Output: {"reduction_target": {"type": "percentage", "value": 15}, "intent": {"detected": true, "description": "Shift from web to mobile development", "focus_areas": ["mobile development"], "de_emphasis_areas": ["web development"]}}

User Input: {user_input}

Return ONLY valid JSON, no additional text."""

# Prompt for adjusting skill requirements based on intent
SKILL_ADJUSTMENT_PROMPT = """You are an expert at translating strategic business initiatives into technical skill requirements.

Given:
1. Current skill requirements for an engineering team
2. A strategic intent/initiative

Adjust the skill requirement levels to align with the strategic direction.

Rules:
- You can ONLY adjust the levels of existing skills (range: 0.0 to 5.0)
- You CANNOT add new skills
- Increase levels for skills critical to the initiative (max increase: +2.0)
- Decrease levels for skills less relevant to the initiative (max decrease: -2.0)
- Most adjustments should be in the 0.5-1.0 range
- Keep at least a minimum level (1.0) for basic skills even if de-emphasized

Current skill requirements:
{current_skills}

Strategic Intent: {intent_description}
Focus Areas: {focus_areas}
De-emphasis Areas: {de_emphasis_areas}

Return ONLY a JSON object with the SAME skill names and adjusted levels.
Provide brief reasoning for significant changes (>1.0 change).

Example output format:
{
    "adjusted_skills": {
        "python": 3.5,
        "javascript": 2.0,
        ...all skills must be included...
    },
    "adjustments_reasoning": {
        "python": "Increased by 0.5 as it's critical for the initiative",
        "javascript": "Decreased by 0.5 as frontend is de-emphasized"
    }
}"""

class ReductionTarget(BaseModel):
    type: Union[Literal["percentage"], Literal["absolute"]] = Field(description="The type of reduction target")
    value: float = Field(description="The value of the reduction target")

class Intent(BaseModel):
    detected: bool = Field(description="Whether the intent was detected")
    description: str = Field(description="The description of the intent")
    focus_areas: List[str] = Field(description="The areas of focus for the intent")
    de_emphasis_areas: List[str] = Field(description="The areas of de-emphasis for the intent")


class ExtactionUnderstanding(BaseModel):
    reduction_target: ReductionTarget = Field(description="The reduction target")
    intent: Intent = Field(description="The intent")

class AdjustedSkills(BaseModel):
    skill: str = Field(description="The skill name")
    level: float = Field(description="The level of the skill")

class AdjustmentsReasoning(BaseModel):
    skill: str = Field(description="The skill name")
    reasoning: str = Field(description="The reasoning for the adjustment")


class SkillAdjustment(BaseModel):
    adjusted_skills: List[AdjustedSkills] = Field(description="The adjusted skill levels")
    adjustments_reasoning: List[AdjustmentsReasoning] = Field(description="The reasoning for the adjustments")


class IntentParser:
    def __init__(self, api_key: Optional[str] = None):
        """Initialize the Intent Parser with OpenAI client"""
        self.api_key = api_key or os.getenv("OPENAI_API_KEY")
        if not self.api_key:
            raise ValueError("OpenAI API key not found. Set OPENAI_API_KEY environment variable.")
        
        self.client = OpenAI(api_key=self.api_key)
        self.default_skills = DEFAULT_ENGINEERING_SKILLS.copy()
    
    def extract_intent_and_target(self, user_input: str) -> Dict[str, Any]:
        """
        Extract headcount reduction target and strategic intent from user input
        
        Args:
            user_input: Natural language input from user
            
        Returns:
            Dictionary with reduction target and intent information
        """
        prompt = EXTRACTION_PROMPT.replace("{user_input}", user_input)
        
        try:
            response = self.client.responses.parse(
                model="gpt-4o",
                input=[
                    {"role": "system", "content": "You are a precise JSON generator. Only output valid JSON."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.1,  # Low temperature for consistent extraction
                text_format=ExtactionUnderstanding
            )

            parsed: ExtactionUnderstanding = response.output_parsed
            return parsed.model_dump()
            
        except json.JSONDecodeError as e:
            print(f"Error parsing JSON response: {e}")
            return {
                "reduction_target": None,
                "intent": {"detected": False},
                "error": "Failed to parse response"
            }
        except Exception as e:
            print(f"Error calling OpenAI API: {e}")
            return {
                "reduction_target": None,
                "intent": {"detected": False},
                "error": str(e)
            }
    
    def adjust_skills_for_intent(self, intent_info: Dict[str, Any]) -> Dict[str, float]:
        """
        Adjust skill requirements based on detected intent
        
        Args:
            intent_info: Intent information from extraction
            
        Returns:
            Dictionary with adjusted skill levels
        """
        # If no intent detected, return default skills
        if not intent_info.get("detected", False):
            return self.default_skills.copy()
        
        # Prepare the prompt
        # prompt = SKILL_ADJUSTMENT_PROMPT.format(
        #     current_skills=json.dumps(self.default_skills, indent=2),
        #     intent_description=intent_info.get("description", ""),
        #     focus_areas=json.dumps(intent_info.get("focus_areas", [])),
        #     de_emphasis_areas=json.dumps(intent_info.get("de_emphasis_areas", []))

        prompt = SKILL_ADJUSTMENT_PROMPT.replace("{current_skills}", json.dumps(self.default_skills, indent=2))
        prompt = prompt.replace("{intent_description}", intent_info.get("description", ""))
        prompt = prompt.replace("{focus_areas}", json.dumps(intent_info.get("focus_areas", [])))
        prompt = prompt.replace("{de_emphasis_areas}", json.dumps(intent_info.get("de_emphasis_areas", [])))
        
        try:
            response = self.client.responses.parse(
                model="gpt-4o",
                input=[
                    {"role": "system", "content": "You are an expert at workforce skill optimization. Adjust skill levels based on strategic initiatives."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.2,
                text_format=SkillAdjustment
            )
            
            result: SkillAdjustment = response.output_parsed
            result = result.model_dump()
            adjusted_skills = result.get("adjusted_skills", self.default_skills)
            adjusted_skills = {skill['skill']: skill['level'] for skill in adjusted_skills}
            print(f"Adjusted skills: {adjusted_skills}")
            
            # Validate that all skills are present and within range
            validated_skills = {}
            for skill in self.default_skills.keys():
                if skill in adjusted_skills:
                    # Ensure skill level is within valid range
                    level = max(0.0, min(5.0, float(adjusted_skills[skill])))
                    validated_skills[skill] = level
                else:
                    validated_skills[skill] = self.default_skills[skill]
            result["adjustments_reasoning"] = {skill['skill']: skill['reasoning'] for skill in result["adjustments_reasoning"]}
            # Print reasoning if available
            if "adjustments_reasoning" in result:
                print("\nAdjustment Reasoning:")
                for skill, reason in result["adjustments_reasoning"].items():
                    print(f"  - {skill}: {reason}")
            
            return validated_skills
            
        except Exception as e:
            print(f"Error adjusting skills: {e}")
            return self.default_skills.copy()
    
    def process_user_request(self, user_input: str) -> Dict[str, Any]:
        """
        Complete pipeline: extract intent and adjust skills
        
        Args:
            user_input: Natural language input from user
            
        Returns:
            Complete optimization parameters
        """
        print(f"\n{'='*60}")
        print(f"Processing: {user_input}")
        print(f"{'='*60}")
        
        # Step 1: Extract intent and target
        print("\n1. Extracting intent and target...")
        extraction_result = self.extract_intent_and_target(user_input)
        
        print(f"\nExtracted:")
        print(f"  Reduction Target: {extraction_result.get('reduction_target')}")
        print(f"  Intent Detected: {extraction_result.get('intent', {}).get('detected')}")
        if extraction_result.get('intent', {}).get('detected'):
            print(f"  Intent: {extraction_result.get('intent', {}).get('description')}")
            print(f"  Focus Areas: {extraction_result.get('intent', {}).get('focus_areas')}")
            print(f"  De-emphasis Areas: {extraction_result.get('intent', {}).get('de_emphasis_areas')}")
        
        # Step 2: Adjust skills based on intent
        print("\n2. Adjusting skill requirements...")
        adjusted_skills = self.adjust_skills_for_intent(extraction_result.get('intent', {}))
        
        # Show significant changes
        print("\nSignificant Skill Changes:")
        for skill, new_level in adjusted_skills.items():
            old_level = self.default_skills[skill]
            change = new_level - old_level
            if abs(change) >= 0.5:
                direction = "↑" if change > 0 else "↓"
                print(f"  {skill}: {old_level:.1f} → {new_level:.1f} ({direction}{abs(change):.1f})")
        
        return {
            "user_input": user_input,
            "reduction_target": extraction_result.get('reduction_target'),
            "intent": extraction_result.get('intent'),
            "default_skills": self.default_skills,
            "adjusted_skills": adjusted_skills,
            "skill_changes": {
                skill: adjusted_skills[skill] - self.default_skills[skill]
                for skill in self.default_skills.keys()
            }
        }


def main():
    """Test the intent parser with various examples"""
    
    # Initialize parser
    parser = IntentParser()
    
    # Test cases
    test_cases = [
        "Reduce engineering headcount by 20%",
        "Cut 15% of the team and focus on AI and machine learning capabilities",
        "We need to reduce costs by 25%. We're shutting down mobile development and focusing entirely on backend infrastructure",
        "Reduce headcount by 30% while transitioning from a product company to a services company",
        "Cut 10 engineers. We're moving all development offshore except for architecture and security",
        "Optimize team size by 15% and double down on our data platform initiatives"
    ]
    
    results = []
    for test_input in test_cases:
        result = parser.process_user_request(test_input)
        results.append(result)
        print("\n" + "="*60)
    
    # Save results to file for review
    with open("intent_parsing_results.json", "w") as f:
        json.dump(results, f, indent=2)
    
    print("\n\nResults saved to intent_parsing_results.json")


if __name__ == "__main__":
    # For testing without API key, use mock mode
    import sys
    if "--mock" in sys.argv:
        print("Running in mock mode (no actual API calls)")
        # Add mock implementation here if needed
    else:
        main()