File size: 5,203 Bytes
958590b
 
 
 
69ac61f
958590b
 
 
 
 
 
 
 
 
 
 
 
 
 
69ac61f
958590b
69ac61f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
958590b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69ac61f
958590b
69ac61f
958590b
 
 
 
 
 
 
 
 
 
69ac61f
958590b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69ac61f
958590b
 
 
 
 
 
69ac61f
958590b
 
 
 
 
 
 
 
 
 
 
 
 
69ac61f
958590b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Job Application Simulator - Data Models"""

from dataclasses import dataclass, field
from typing import Optional, List, Dict
from pydantic import BaseModel, Field
from openenv.core import Action, Observation, State


# ============================================================================
# DOMAIN MODELS
# ============================================================================

@dataclass
class JobPosting:
    """A job listing from the mock job board"""
    id: str
    title: str
    company: str
    location: str
    salary_range: str  # e.g., "$120k - $150k"
    description: str
    required_skills: List[str]
    preferred_skills: List[str]
    experience_required: int
    type: str = "full-time"
    
    @property
    def remote(self) -> bool:
        return self.location.lower() == "remote"
    
    @property
    def job_type(self) -> str:
        return self.type
    
    @property
    def salary_min(self) -> int:
        """Parse salary_range to get minimum salary in thousands"""
        return self._parse_salary(min)
    
    @property
    def salary_max(self) -> int:
        """Parse salary_range to get maximum salary in thousands"""
        return self._parse_salary(max)
    
    def _parse_salary(self, min_or_max) -> int:
        """Parse salary range like '$120k - $150k' to integer (120 or 150)"""
        import re
        nums = re.findall(r'\$?(\d+)k?', self.salary_range)
        if nums:
            return min_or_max(int(n) for n in nums)
        return 0


@dataclass
class ApplicantProfile:
    """The job seeker's profile and preferences"""
    name: str
    skills: List[str]
    experience_years: int
    education: str
    current_role: str
    target_roles: List[str]
    preferred_locations: List[str]
    salary_min: int
    salary_max: int
    resume_sections: Dict[str, str] = field(default_factory=dict)


@dataclass
class JobAnalysis:
    """Result of analyzing a job posting"""
    job_id: str
    match_score: float  # 0.0 to 1.0
    matching_skills: List[str]
    missing_skills: List[str]
    key_requirements: List[str]
    recommended_focus: str  # What to highlight in application


@dataclass
class SubmittedApplication:
    """Record of a submitted application"""
    job_id: str
    cover_letter: str
    tailored_sections: Dict[str, str]
    submission_time: str
    match_score: float
    status: str = "pending"  # pending, accepted, rejected


# ============================================================================
# ENVIRONMENT STATE
# ============================================================================

class JobAppState(State):
    """Episode state for Job Application Simulator"""
    current_job: Optional[JobPosting] = None
    applicant_profile: Optional[ApplicantProfile] = None
    applications_submitted: List[SubmittedApplication] = Field(default_factory=list)
    budget_remaining: int = 10  # Max applications per episode
    last_search_results: List[JobPosting] = Field(default_factory=list)
    last_analysis: Optional[JobAnalysis] = None
    total_reward: float = 0.0


# ============================================================================
# ACTIONS
# ============================================================================

class SearchJobs(Action):
    """Search for jobs matching criteria"""
    keywords: List[str] = Field(default_factory=list)
    location: Optional[str] = None
    salary_min: Optional[int] = None
    job_type: Optional[str] = None  # full-time, contract, part-time
    remote_only: bool = False


class AnalyzeJob(Action):
    """Analyze a job posting for fit"""
    job_id: str = ""


class WriteCoverLetter(Action):
    """Generate a tailored cover letter"""
    job_id: str = ""
    tone: str = "professional"  # professional, friendly, technical
    highlight_skills: List[str] = Field(default_factory=list)


class SubmitApplication(Action):
    """Submit application to a job"""
    job_id: str = ""
    cover_letter: str = ""
    tailored_resume_sections: Dict[str, str] = Field(default_factory=dict)


class NextJob(Action):
    """Move to the next job in search results"""
    pass


# ============================================================================
# OBSERVATIONS
# ============================================================================

class JobAppObservation(Observation):
    """Observation returned after each action"""
    job_listings: List[JobPosting] = Field(default_factory=list)
    current_job: Optional[JobPosting] = None
    analysis_result: Optional[JobAnalysis] = None
    cover_letter: Optional[str] = None
    cover_letter_quality: Optional[float] = None  # 0.0 to 1.0
    application_status: Optional[str] = None
    budget_remaining: int = 10
    applications_count: int = 0
    message: str = ""
    done: bool = False
    reward: float = 0.0


# ============================================================================
# STEP RESULT
# ============================================================================

@dataclass
class StepResult:
    """Result of executing an action"""
    observation: JobAppObservation
    reward: float
    done: bool
    info: Dict = field(default_factory=dict)