aiondtech 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiondtech/__init__.py +79 -0
- aiondtech/client.py +603 -0
- aiondtech/models.py +428 -0
- aiondtech/py.typed +0 -0
- aiondtech-2.0.0.dist-info/METADATA +285 -0
- aiondtech-2.0.0.dist-info/RECORD +9 -0
- aiondtech-2.0.0.dist-info/WHEEL +5 -0
- aiondtech-2.0.0.dist-info/licenses/LICENSE +21 -0
- aiondtech-2.0.0.dist-info/top_level.txt +1 -0
aiondtech/models.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models and exceptions for the AiondTech SDK v2.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import List, Optional, Dict, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# EXCEPTIONS
|
|
12
|
+
# ============================================================================
|
|
13
|
+
|
|
14
|
+
class APIError(Exception):
|
|
15
|
+
"""Base exception for all API errors."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
message: str,
|
|
20
|
+
status_code: Optional[int] = None,
|
|
21
|
+
response: Optional[Dict[str, Any]] = None
|
|
22
|
+
):
|
|
23
|
+
self.message = message
|
|
24
|
+
self.status_code = status_code
|
|
25
|
+
self.response = response or {}
|
|
26
|
+
super().__init__(self.message)
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
if self.status_code:
|
|
30
|
+
return f"[{self.status_code}] {self.message}"
|
|
31
|
+
return self.message
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AuthenticationError(APIError):
|
|
35
|
+
"""Raised when API key is invalid or missing."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RateLimitError(APIError):
|
|
40
|
+
"""Raised when rate limit is exceeded."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
message: str = "Rate limit exceeded",
|
|
45
|
+
retry_after: Optional[int] = None,
|
|
46
|
+
**kwargs
|
|
47
|
+
):
|
|
48
|
+
self.retry_after = retry_after
|
|
49
|
+
super().__init__(message, **kwargs)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ValidationError(APIError):
|
|
53
|
+
"""Raised when request validation fails."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NotFoundError(APIError):
|
|
58
|
+
"""Raised when a resource is not found."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class InsufficientCreditsError(APIError):
|
|
63
|
+
"""Raised when user has insufficient API credits."""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
message: str = "Insufficient API credits",
|
|
68
|
+
credits_remaining: int = 0,
|
|
69
|
+
credits_required: int = 0,
|
|
70
|
+
**kwargs
|
|
71
|
+
):
|
|
72
|
+
self.credits_remaining = credits_remaining
|
|
73
|
+
self.credits_required = credits_required
|
|
74
|
+
super().__init__(message, **kwargs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ============================================================================
|
|
78
|
+
# RESPONSE MODELS
|
|
79
|
+
# ============================================================================
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ResumeUploadResult:
|
|
83
|
+
"""Result from uploading a resume (upload only, no parsing)."""
|
|
84
|
+
resume_id: int
|
|
85
|
+
message: str
|
|
86
|
+
credits_used: int = 1
|
|
87
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def from_response(cls, data: Dict[str, Any]) -> "ResumeUploadResult":
|
|
91
|
+
return cls(
|
|
92
|
+
resume_id=data.get("resume_id"),
|
|
93
|
+
message=data.get("message", "Resume uploaded successfully"),
|
|
94
|
+
credits_used=data.get("credits_used", 1),
|
|
95
|
+
_raw=data
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
return f"<ResumeUploadResult resume_id={self.resume_id}>"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ResumeAnalysisResult:
|
|
104
|
+
"""Result from uploading and analyzing a resume."""
|
|
105
|
+
resume_id: int
|
|
106
|
+
parsed_data: Dict[str, Any]
|
|
107
|
+
credits_used: int = 3
|
|
108
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_response(cls, data: Dict[str, Any]) -> "ResumeAnalysisResult":
|
|
112
|
+
return cls(
|
|
113
|
+
resume_id=data.get("resume_id"),
|
|
114
|
+
parsed_data=data.get("parsed_data", {}),
|
|
115
|
+
credits_used=data.get("credits_used", 3),
|
|
116
|
+
_raw=data
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def full_name(self) -> Optional[str]:
|
|
121
|
+
return self.parsed_data.get("full_name")
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def email(self) -> Optional[str]:
|
|
125
|
+
return self.parsed_data.get("email")
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def skills(self) -> List[str]:
|
|
129
|
+
skills = self.parsed_data.get("skills", [])
|
|
130
|
+
if isinstance(skills, str):
|
|
131
|
+
return [s.strip() for s in skills.split(",") if s.strip()]
|
|
132
|
+
return skills or []
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def job_titles(self) -> List[str]:
|
|
136
|
+
titles = self.parsed_data.get("job_titles", [])
|
|
137
|
+
if isinstance(titles, str):
|
|
138
|
+
return [t.strip() for t in titles.split(",") if t.strip()]
|
|
139
|
+
return titles or []
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def total_experience(self) -> Optional[str]:
|
|
143
|
+
return self.parsed_data.get("total_experience")
|
|
144
|
+
|
|
145
|
+
def __repr__(self):
|
|
146
|
+
name = self.full_name or "Unknown"
|
|
147
|
+
return f"<ResumeAnalysisResult resume_id={self.resume_id} name='{name}'>"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class ResumeComparisonResult:
|
|
152
|
+
"""Result from comparing a resume to a job posting."""
|
|
153
|
+
resume_id: int
|
|
154
|
+
job_id: int
|
|
155
|
+
comparison_score: float
|
|
156
|
+
comparison_reason: str
|
|
157
|
+
parsed_data: Optional[Dict[str, Any]] = None
|
|
158
|
+
matched_keywords: List[str] = field(default_factory=list)
|
|
159
|
+
missing_keywords: List[str] = field(default_factory=list)
|
|
160
|
+
suggestions: List[str] = field(default_factory=list)
|
|
161
|
+
credits_used: int = 3
|
|
162
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_response(cls, data: Dict[str, Any]) -> "ResumeComparisonResult":
|
|
166
|
+
return cls(
|
|
167
|
+
resume_id=data.get("resume_id"),
|
|
168
|
+
job_id=data.get("job_id"),
|
|
169
|
+
comparison_score=data.get("comparison_score", 0) or data.get("ats_score", 0),
|
|
170
|
+
comparison_reason=data.get("comparison_reason", "") or data.get("reason", ""),
|
|
171
|
+
parsed_data=data.get("parsed_data"),
|
|
172
|
+
matched_keywords=data.get("matched_keywords", []),
|
|
173
|
+
missing_keywords=data.get("missing_keywords", []),
|
|
174
|
+
suggestions=data.get("suggestions", []),
|
|
175
|
+
credits_used=data.get("credits_used", 3),
|
|
176
|
+
_raw=data
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def is_good_match(self) -> bool:
|
|
181
|
+
"""Returns True if score is 70% or higher."""
|
|
182
|
+
return self.comparison_score >= 70
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def match_level(self) -> str:
|
|
186
|
+
"""Returns match level: 'excellent', 'good', 'fair', 'poor'."""
|
|
187
|
+
if self.comparison_score >= 85:
|
|
188
|
+
return "excellent"
|
|
189
|
+
elif self.comparison_score >= 70:
|
|
190
|
+
return "good"
|
|
191
|
+
elif self.comparison_score >= 50:
|
|
192
|
+
return "fair"
|
|
193
|
+
return "poor"
|
|
194
|
+
|
|
195
|
+
def __repr__(self):
|
|
196
|
+
return f"<ResumeComparisonResult resume_id={self.resume_id} score={self.comparison_score}%>"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class JobResult:
|
|
201
|
+
"""Result from creating a job posting."""
|
|
202
|
+
job_id: int
|
|
203
|
+
title: str
|
|
204
|
+
description: str
|
|
205
|
+
created_by: str
|
|
206
|
+
created_at: Optional[str] = None
|
|
207
|
+
credits_used: int = 1
|
|
208
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def from_response(cls, data: Dict[str, Any]) -> "JobResult":
|
|
212
|
+
return cls(
|
|
213
|
+
job_id=data.get("job_id"),
|
|
214
|
+
title=data.get("title", ""),
|
|
215
|
+
description=data.get("description", ""),
|
|
216
|
+
created_by=data.get("created_by", ""),
|
|
217
|
+
created_at=data.get("created_at"),
|
|
218
|
+
credits_used=data.get("credits_used", 1),
|
|
219
|
+
_raw=data
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def __repr__(self):
|
|
223
|
+
return f"<JobResult job_id={self.job_id} title='{self.title}'>"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class ParsedResumeResult:
|
|
228
|
+
"""Result from parsing/analyzing a resume by ID."""
|
|
229
|
+
resume_id: int
|
|
230
|
+
partner: str
|
|
231
|
+
parsed_data: Dict[str, Any]
|
|
232
|
+
credits_used: int = 2
|
|
233
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def from_response(cls, data: Dict[str, Any]) -> "ParsedResumeResult":
|
|
237
|
+
return cls(
|
|
238
|
+
resume_id=data.get("resume_id"),
|
|
239
|
+
partner=data.get("partner", ""),
|
|
240
|
+
parsed_data=data.get("parsed_data", {}),
|
|
241
|
+
credits_used=data.get("credits_used", 2),
|
|
242
|
+
_raw=data
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def full_name(self) -> Optional[str]:
|
|
247
|
+
return self.parsed_data.get("full_name")
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def email(self) -> Optional[str]:
|
|
251
|
+
return self.parsed_data.get("email")
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def phone(self) -> Optional[str]:
|
|
255
|
+
return self.parsed_data.get("contact_number")
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def linkedin(self) -> Optional[str]:
|
|
259
|
+
return self.parsed_data.get("linkedin")
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def location(self) -> Optional[str]:
|
|
263
|
+
return self.parsed_data.get("location")
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def skills(self) -> List[str]:
|
|
267
|
+
skills = self.parsed_data.get("skills", [])
|
|
268
|
+
if isinstance(skills, str):
|
|
269
|
+
return [s.strip() for s in skills.split(",") if s.strip()]
|
|
270
|
+
return skills or []
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def job_titles(self) -> List[str]:
|
|
274
|
+
titles = self.parsed_data.get("job_titles", [])
|
|
275
|
+
if isinstance(titles, str):
|
|
276
|
+
return [t.strip() for t in titles.split(",") if t.strip()]
|
|
277
|
+
return titles or []
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def companies(self) -> List[str]:
|
|
281
|
+
companies = self.parsed_data.get("companies", [])
|
|
282
|
+
if isinstance(companies, str):
|
|
283
|
+
return [c.strip() for c in companies.split(",") if c.strip()]
|
|
284
|
+
return companies or []
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def education(self) -> List[str]:
|
|
288
|
+
edu = self.parsed_data.get("education", [])
|
|
289
|
+
if isinstance(edu, str):
|
|
290
|
+
return [e.strip() for e in edu.split(",") if e.strip()]
|
|
291
|
+
return edu or []
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def total_experience(self) -> Optional[str]:
|
|
295
|
+
return self.parsed_data.get("total_experience")
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def certifications(self) -> List[str]:
|
|
299
|
+
certs = self.parsed_data.get("certifications", [])
|
|
300
|
+
if isinstance(certs, str):
|
|
301
|
+
return [c.strip() for c in certs.split(",") if c.strip()]
|
|
302
|
+
return certs or []
|
|
303
|
+
|
|
304
|
+
def __repr__(self):
|
|
305
|
+
name = self.full_name or "Unknown"
|
|
306
|
+
return f"<ParsedResumeResult resume_id={self.resume_id} name='{name}'>"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@dataclass
|
|
310
|
+
class ResumeListItem:
|
|
311
|
+
"""Single resume in a list."""
|
|
312
|
+
id: int
|
|
313
|
+
filename: str
|
|
314
|
+
full_name: Optional[str] = None
|
|
315
|
+
email: Optional[str] = None
|
|
316
|
+
uploaded_at: Optional[str] = None
|
|
317
|
+
is_parsed: bool = False
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ResumeListItem":
|
|
321
|
+
return cls(
|
|
322
|
+
id=data.get("id"),
|
|
323
|
+
filename=data.get("filename", ""),
|
|
324
|
+
full_name=data.get("full_name"),
|
|
325
|
+
email=data.get("email"),
|
|
326
|
+
uploaded_at=data.get("uploaded_at"),
|
|
327
|
+
is_parsed=data.get("is_parsed", False)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass
|
|
332
|
+
class ResumeListResult:
|
|
333
|
+
"""Result from listing resumes."""
|
|
334
|
+
resumes: List[Dict[str, Any]]
|
|
335
|
+
total: int
|
|
336
|
+
page: int
|
|
337
|
+
limit: int
|
|
338
|
+
has_more: bool
|
|
339
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def from_response(cls, data: Dict[str, Any]) -> "ResumeListResult":
|
|
343
|
+
return cls(
|
|
344
|
+
resumes=data.get("resumes", []),
|
|
345
|
+
total=data.get("total", 0),
|
|
346
|
+
page=data.get("page", 1),
|
|
347
|
+
limit=data.get("limit", 50),
|
|
348
|
+
has_more=data.get("has_more", False),
|
|
349
|
+
_raw=data
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def __len__(self):
|
|
353
|
+
return len(self.resumes)
|
|
354
|
+
|
|
355
|
+
def __iter__(self):
|
|
356
|
+
return iter(self.resumes)
|
|
357
|
+
|
|
358
|
+
def __repr__(self):
|
|
359
|
+
return f"<ResumeListResult count={len(self.resumes)} total={self.total}>"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass
|
|
363
|
+
class JobListItem:
|
|
364
|
+
"""Single job in a list."""
|
|
365
|
+
id: int
|
|
366
|
+
title: str
|
|
367
|
+
description: str
|
|
368
|
+
created_at: Optional[str] = None
|
|
369
|
+
|
|
370
|
+
@classmethod
|
|
371
|
+
def from_dict(cls, data: Dict[str, Any]) -> "JobListItem":
|
|
372
|
+
return cls(
|
|
373
|
+
id=data.get("id"),
|
|
374
|
+
title=data.get("title", ""),
|
|
375
|
+
description=data.get("description", ""),
|
|
376
|
+
created_at=data.get("created_at")
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@dataclass
|
|
381
|
+
class JobListResult:
|
|
382
|
+
"""Result from listing jobs."""
|
|
383
|
+
jobs: List[Dict[str, Any]]
|
|
384
|
+
total: int
|
|
385
|
+
page: int
|
|
386
|
+
limit: int
|
|
387
|
+
has_more: bool
|
|
388
|
+
_raw: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def from_response(cls, data: Dict[str, Any]) -> "JobListResult":
|
|
392
|
+
return cls(
|
|
393
|
+
jobs=data.get("jobs", []),
|
|
394
|
+
total=data.get("total", 0),
|
|
395
|
+
page=data.get("page", 1),
|
|
396
|
+
limit=data.get("limit", 50),
|
|
397
|
+
has_more=data.get("has_more", False),
|
|
398
|
+
_raw=data
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
def __len__(self):
|
|
402
|
+
return len(self.jobs)
|
|
403
|
+
|
|
404
|
+
def __iter__(self):
|
|
405
|
+
return iter(self.jobs)
|
|
406
|
+
|
|
407
|
+
def __repr__(self):
|
|
408
|
+
return f"<JobListResult count={len(self.jobs)} total={self.total}>"
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass
|
|
412
|
+
class CreditBalance:
|
|
413
|
+
"""Credit balance information."""
|
|
414
|
+
total: int
|
|
415
|
+
used_mtd: int
|
|
416
|
+
remaining: int
|
|
417
|
+
plan_name: str
|
|
418
|
+
resets_at: Optional[str] = None
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def from_response(cls, data: Dict[str, Any]) -> "CreditBalance":
|
|
422
|
+
return cls(
|
|
423
|
+
total=data.get("total", 0),
|
|
424
|
+
used_mtd=data.get("used_mtd", 0),
|
|
425
|
+
remaining=data.get("remaining", 0),
|
|
426
|
+
plan_name=data.get("plan_name", ""),
|
|
427
|
+
resets_at=data.get("resets_at")
|
|
428
|
+
)
|
aiondtech/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiondtech
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Official Python SDK for AiondTech Resume Analyser API
|
|
5
|
+
Author-email: AiondTech <support@aiondtech.com>
|
|
6
|
+
Maintainer-email: AiondTech <support@aiondtech.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://aiondtech.com
|
|
9
|
+
Project-URL: Documentation, https://docs.aiondtech.com
|
|
10
|
+
Project-URL: Repository, https://github.com/aiondtech/aiondtech-python
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/aiondtech/aiondtech-python/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/aiondtech/aiondtech-python/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: aiondtech,resume,cv,parser,analyzer,ats,recruitment,hr,api,sdk
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: requests>=2.25.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# AiondTech Resume Analyser SDK
|
|
38
|
+
|
|
39
|
+
Official Python SDK for the [AiondTech Resume Analyser API](https://aiondtech.com).
|
|
40
|
+
|
|
41
|
+
[](https://badge.fury.io/py/aiondtech)
|
|
42
|
+
[](https://pypi.org/project/aiondtech/)
|
|
43
|
+
[](https://opensource.org/licenses/MIT)
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install aiondtech
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from aiondtech import ResumeAnalyser
|
|
55
|
+
|
|
56
|
+
# Initialize client
|
|
57
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
58
|
+
|
|
59
|
+
# Or use environment variable
|
|
60
|
+
# export AIONDTECH_API_KEY="your-api-key"
|
|
61
|
+
client = ResumeAnalyser()
|
|
62
|
+
|
|
63
|
+
# Upload a resume (1 credit)
|
|
64
|
+
result = client.resumes.upload("resume.pdf")
|
|
65
|
+
print(f"Resume ID: {result.resume_id}")
|
|
66
|
+
|
|
67
|
+
# Upload and analyze (3 credits)
|
|
68
|
+
analysis = client.resumes.upload_and_analyze("resume.pdf")
|
|
69
|
+
print(f"Name: {analysis.full_name}")
|
|
70
|
+
print(f"Skills: {analysis.skills}")
|
|
71
|
+
|
|
72
|
+
# Create a job posting (1 credit)
|
|
73
|
+
job = client.jobs.create(
|
|
74
|
+
title="Senior Python Developer",
|
|
75
|
+
description="We are looking for an experienced Python developer..."
|
|
76
|
+
)
|
|
77
|
+
print(f"Job ID: {job.job_id}")
|
|
78
|
+
|
|
79
|
+
# Compare resume to job (3 credits)
|
|
80
|
+
comparison = client.matching.compare(
|
|
81
|
+
resume_id=result.resume_id,
|
|
82
|
+
job_id=job.job_id
|
|
83
|
+
)
|
|
84
|
+
print(f"Match Score: {comparison.comparison_score}%")
|
|
85
|
+
print(f"Reason: {comparison.comparison_reason}")
|
|
86
|
+
|
|
87
|
+
# Check credit balance (free)
|
|
88
|
+
balance = client.credits.balance()
|
|
89
|
+
print(f"Remaining: {balance['remaining_credits']}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API Endpoints
|
|
93
|
+
|
|
94
|
+
| Method | Endpoint | Credits | Description |
|
|
95
|
+
|--------|----------|---------|-------------|
|
|
96
|
+
| `resumes.upload()` | POST /external/upload-resume | 1 | Upload PDF |
|
|
97
|
+
| `resumes.upload_and_analyze()` | POST /external/upload-resume-analyze | 3 | Upload + AI parsing |
|
|
98
|
+
| `resumes.upload_analyze_compare()` | POST /external/upload-resume-analyze-compare | 5 | Upload + parse + compare |
|
|
99
|
+
| `resumes.analyze()` | POST /external/analyze-resume-by-id | 2 | Parse existing resume |
|
|
100
|
+
| `resumes.list()` | GET /external/list-resumes | 0 | List all resumes |
|
|
101
|
+
| `jobs.create()` | POST /external/create-job | 1 | Create job posting |
|
|
102
|
+
| `jobs.list()` | GET /external/list-jobs | 0 | List all jobs |
|
|
103
|
+
| `matching.compare()` | POST /external/compare-resumes | 3 | Compare resume to job |
|
|
104
|
+
| `credits.balance()` | GET /external/credits/balance | 0 | Check balance |
|
|
105
|
+
| `credits.usage()` | GET /external/credits/usage | 0 | View history |
|
|
106
|
+
|
|
107
|
+
## Detailed Examples
|
|
108
|
+
|
|
109
|
+
### Upload and Analyze Resume
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from aiondtech import ResumeAnalyser
|
|
113
|
+
|
|
114
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
115
|
+
|
|
116
|
+
# Upload and get structured data
|
|
117
|
+
analysis = client.resumes.upload_and_analyze("john_doe_resume.pdf")
|
|
118
|
+
|
|
119
|
+
print(f"Name: {analysis.full_name}")
|
|
120
|
+
print(f"Email: {analysis.email}")
|
|
121
|
+
print(f"Skills: {', '.join(analysis.skills)}")
|
|
122
|
+
print(f"Experience: {analysis.total_experience}")
|
|
123
|
+
print(f"Job Titles: {', '.join(analysis.job_titles)}")
|
|
124
|
+
|
|
125
|
+
# Access raw parsed data
|
|
126
|
+
print(analysis.parsed_data)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Full Recruitment Workflow
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from aiondtech import ResumeAnalyser
|
|
133
|
+
|
|
134
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
135
|
+
|
|
136
|
+
# 1. Create a job posting
|
|
137
|
+
job = client.jobs.create(
|
|
138
|
+
title="Senior Python Developer",
|
|
139
|
+
description="""
|
|
140
|
+
Requirements:
|
|
141
|
+
- 5+ years Python experience
|
|
142
|
+
- FastAPI or Django expertise
|
|
143
|
+
- PostgreSQL knowledge
|
|
144
|
+
- AWS experience preferred
|
|
145
|
+
"""
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# 2. Upload and compare multiple resumes
|
|
149
|
+
resumes = ["candidate1.pdf", "candidate2.pdf", "candidate3.pdf"]
|
|
150
|
+
results = []
|
|
151
|
+
|
|
152
|
+
for resume_path in resumes:
|
|
153
|
+
result = client.resumes.upload_analyze_compare(
|
|
154
|
+
file_path=resume_path,
|
|
155
|
+
job_id=job.job_id
|
|
156
|
+
)
|
|
157
|
+
results.append({
|
|
158
|
+
"resume_id": result.resume_id,
|
|
159
|
+
"name": result.parsed_data.get("full_name"),
|
|
160
|
+
"score": result.comparison_score,
|
|
161
|
+
"matched": result.matched_keywords,
|
|
162
|
+
"missing": result.missing_keywords
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
# 3. Rank candidates by score
|
|
166
|
+
ranked = sorted(results, key=lambda x: x["score"], reverse=True)
|
|
167
|
+
|
|
168
|
+
for i, candidate in enumerate(ranked, 1):
|
|
169
|
+
print(f"{i}. {candidate['name']} - {candidate['score']}% match")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Error Handling
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from aiondtech import (
|
|
176
|
+
ResumeAnalyser,
|
|
177
|
+
AuthenticationError,
|
|
178
|
+
RateLimitError,
|
|
179
|
+
InsufficientCreditsError,
|
|
180
|
+
ValidationError,
|
|
181
|
+
NotFoundError,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
client = ResumeAnalyser(api_key="your-api-key")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
result = client.resumes.upload_and_analyze("resume.pdf")
|
|
188
|
+
except AuthenticationError:
|
|
189
|
+
print("Invalid API key")
|
|
190
|
+
except InsufficientCreditsError as e:
|
|
191
|
+
print(f"Not enough credits. Remaining: {e.credits_remaining}")
|
|
192
|
+
except RateLimitError as e:
|
|
193
|
+
print(f"Rate limited. Retry after {e.retry_after} seconds")
|
|
194
|
+
except ValidationError as e:
|
|
195
|
+
print(f"Invalid input: {e.message}")
|
|
196
|
+
except NotFoundError:
|
|
197
|
+
print("Resource not found")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Using Environment Variables
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Set your API key
|
|
204
|
+
export AIONDTECH_API_KEY="your-api-key"
|
|
205
|
+
|
|
206
|
+
# Optional: Use production URL
|
|
207
|
+
export AIONDTECH_BASE_URL="https://api.aiondtech.com"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from aiondtech import ResumeAnalyser
|
|
212
|
+
|
|
213
|
+
# Client automatically uses environment variables
|
|
214
|
+
client = ResumeAnalyser()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Production vs Development
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from aiondtech import ResumeAnalyser
|
|
221
|
+
|
|
222
|
+
# Development (default)
|
|
223
|
+
client = ResumeAnalyser(api_key="your-key")
|
|
224
|
+
# Uses: https://api.dev.aiondtech.com
|
|
225
|
+
|
|
226
|
+
# Production
|
|
227
|
+
client = ResumeAnalyser(api_key="your-key", production=True)
|
|
228
|
+
# Uses: https://api.aiondtech.com
|
|
229
|
+
|
|
230
|
+
# Custom URL
|
|
231
|
+
client = ResumeAnalyser(api_key="your-key", base_url="https://custom.api.com")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Response Models
|
|
235
|
+
|
|
236
|
+
All API responses are converted to typed Python objects:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
# ResumeUploadResult
|
|
240
|
+
result.resume_id # int
|
|
241
|
+
result.message # str
|
|
242
|
+
result.credits_used # int
|
|
243
|
+
|
|
244
|
+
# ResumeAnalysisResult
|
|
245
|
+
analysis.resume_id # int
|
|
246
|
+
analysis.parsed_data # dict
|
|
247
|
+
analysis.full_name # str (shortcut)
|
|
248
|
+
analysis.email # str (shortcut)
|
|
249
|
+
analysis.skills # List[str] (shortcut)
|
|
250
|
+
|
|
251
|
+
# ResumeComparisonResult
|
|
252
|
+
comparison.resume_id # int
|
|
253
|
+
comparison.job_id # int
|
|
254
|
+
comparison.comparison_score # float (0-100)
|
|
255
|
+
comparison.comparison_reason # str
|
|
256
|
+
comparison.matched_keywords # List[str]
|
|
257
|
+
comparison.missing_keywords # List[str]
|
|
258
|
+
comparison.is_good_match # bool (score >= 70)
|
|
259
|
+
comparison.match_level # str ("excellent", "good", "fair", "poor")
|
|
260
|
+
|
|
261
|
+
# JobResult
|
|
262
|
+
job.job_id # int
|
|
263
|
+
job.title # str
|
|
264
|
+
job.description # str
|
|
265
|
+
job.created_at # str
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Rate Limits
|
|
269
|
+
|
|
270
|
+
| Tier | Per Minute | Per Day |
|
|
271
|
+
|------|------------|---------|
|
|
272
|
+
| Starter | 50 | 500 |
|
|
273
|
+
| Growth | 100 | 1,000 |
|
|
274
|
+
| Enterprise | 500 | Unlimited |
|
|
275
|
+
|
|
276
|
+
## Support
|
|
277
|
+
|
|
278
|
+
- **Documentation**: [https://docs.aiondtech.com](https://docs.aiondtech.com)
|
|
279
|
+
- **API Reference**: [https://api.aiondtech.com/docs](https://api.aiondtech.com/docs)
|
|
280
|
+
- **Email**: support@aiondtech.com
|
|
281
|
+
- **Issues**: [GitHub Issues](https://github.com/aiondtech/aiondtech-python/issues)
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT License - see [LICENSE](LICENSE) for details.
|