talentro-commons 0.18.12__tar.gz → 0.19.0__tar.gz

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.
Files changed (44) hide show
  1. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/PKG-INFO +2 -2
  2. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/pyproject.toml +2 -2
  3. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/acquisition/dataclasses.py +6 -4
  4. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/acquisition/models.py +1 -3
  5. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/billing/models.py +1 -2
  6. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/candidates/dataclasses.py +2 -2
  7. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/general/models.py +2 -3
  8. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/caching.py +0 -1
  9. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/clients.py +0 -1
  10. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/db.py +1 -1
  11. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/google_storage.py +12 -19
  12. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/attributes.py +4 -1
  13. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/files.py +1 -1
  14. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/vacancy.py +1 -1
  15. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/vacancies/dataclasses.py +36 -21
  16. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/vacancies/models.py +8 -15
  17. talentro_commons-0.19.0/src/talentro/vacancies/taxanomy.py +190 -0
  18. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/README.md +0 -0
  19. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/__init__.py +0 -0
  20. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/acquisition/__init__.py +0 -0
  21. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/billing/__init__.py +0 -0
  22. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/billing/dataclasses.py +0 -0
  23. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/candidates/__init__.py +0 -0
  24. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/candidates/models.py +0 -0
  25. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/constants.py +0 -0
  26. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/event.py +0 -0
  27. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/exceptions.py +0 -0
  28. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/general/__init__.py +0 -0
  29. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/general/dataclasses.py +0 -0
  30. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/iam/__init__.py +0 -0
  31. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/iam/dataclasses.py +0 -0
  32. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/iam/models.py +1 -1
  33. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/iam/types.py +0 -0
  34. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/integrations/__init__.py +0 -0
  35. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/integrations/dataclasses.py +2 -2
  36. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/integrations/models.py +0 -0
  37. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/__init__.py +0 -0
  38. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/billing.py +2 -2
  39. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/services/rabbitmq.py +0 -0
  40. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/__init__.py +0 -0
  41. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/enum.py +0 -0
  42. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/singleton.py +0 -0
  43. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/util/string.py +0 -0
  44. {talentro_commons-0.18.12 → talentro_commons-0.19.0}/src/talentro/vacancies/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: talentro-commons
3
- Version: 0.18.12
3
+ Version: 0.19.0
4
4
  Summary: This package contains all globally used code, services, models and data structures for Talentro
5
5
  License: Proprietary
6
6
  Author: Emiel van Essen
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.13
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Dist: aio-pika (>=9.5.7,<10.0.0)
14
14
  Requires-Dist: aiocache[redis] (>=0.12.3,<0.13.0)
15
- Requires-Dist: fastapi (>=0.124.0,<0.125.0)
15
+ Requires-Dist: fastapi (>=0.128.0,<0.129.0)
16
16
  Requires-Dist: google-cloud-storage (>=3.6.0,<4.0.0)
17
17
  Requires-Dist: httpx (>=0.28.1,<0.29.0)
18
18
  Requires-Dist: sqlalchemy (>=2.0.38,<3.0.0)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "talentro-commons"
3
- version = "0.18.12"
3
+ version = "0.19.0"
4
4
  description = "This package contains all globally used code, services, models and data structures for Talentro"
5
5
  authors = [
6
6
  { name = "Emiel van Essen", email = "emiel@marksmen.nl"}
@@ -11,7 +11,7 @@ requires-python = ">=3.13,<4.0"
11
11
  dependencies = [
12
12
  "sqlalchemy (>=2.0.38,<3.0.0)",
13
13
  "sqlmodel (>=0.0.27,<0.0.28)",
14
- "fastapi (>=0.124.0,<0.125.0)",
14
+ "fastapi (>=0.128.0,<0.129.0)",
15
15
  "aio-pika (>=9.5.7,<10.0.0)",
16
16
  "httpx (>=0.28.1,<0.29.0)",
17
17
  "google-cloud-storage (>=3.6.0,<4.0.0)",
@@ -1,15 +1,17 @@
1
1
  from datetime import datetime
2
2
  from typing import Optional
3
3
  from uuid import UUID
4
+
4
5
  from pydantic import BaseModel
5
6
 
7
+ from .models import CampaignStatus
6
8
  from ..acquisition.models import ChannelType, CampaignGoal, Campaign as CampaignModel, Ad as AdModel, AdStatus
7
9
  from ..general.dataclasses import ResolvableCompanyModel
8
10
  from ..integrations.dataclasses import LinkInfo
9
- from ..vacancies.dataclasses import FeedInfo
10
-
11
- from ..services.clients import MSClient
12
11
  from ..services.caching import CacheService
12
+ from ..services.clients import MSClient
13
+ from ..util.enum import to_enum
14
+ from ..vacancies.dataclasses import FeedInfo
13
15
 
14
16
 
15
17
  # Campaign object
@@ -61,7 +63,7 @@ class CampaignInfo(ResolvableCompanyModel):
61
63
  organization=model.organization,
62
64
  name=model.name,
63
65
  external_id=model.external_id,
64
- status=model.status,
66
+ status=to_enum(CampaignStatus, model.status),
65
67
  last_sync_date=model.last_sync_date,
66
68
  ad_count=model.ad_count,
67
69
  auto_sync=model.auto_sync,
@@ -1,10 +1,8 @@
1
1
  import enum
2
-
3
- from uuid import UUID
4
-
5
2
  from datetime import datetime
6
3
  from enum import StrEnum
7
4
  from typing import Optional
5
+ from uuid import UUID
8
6
 
9
7
  from sqlalchemy import Column, JSON, Enum
10
8
  from sqlmodel import Field, Relationship
@@ -1,7 +1,6 @@
1
- from uuid import UUID
2
-
3
1
  from datetime import datetime, timezone
4
2
  from typing import Optional
3
+ from uuid import UUID
5
4
 
6
5
  from sqlmodel import SQLModel, Field
7
6
 
@@ -3,10 +3,10 @@ from typing import Optional
3
3
  from uuid import UUID
4
4
 
5
5
  from pydantic import BaseModel
6
-
7
6
  from talentro.acquisition.dataclasses import CampaignInfo
7
+ from talentro.candidates.models import Application as ApplicationModel, Document as DocumentModel, \
8
+ Candidate as CandidateModel
8
9
  from talentro.vacancies.dataclasses import VacancyInfo
9
- from talentro.candidates.models import Application as ApplicationModel, Document as DocumentModel, Candidate as CandidateModel
10
10
 
11
11
 
12
12
  class CandidateInfo(BaseModel):
@@ -1,9 +1,8 @@
1
- from uuid import UUID, uuid4
2
-
1
+ from datetime import datetime, timezone
3
2
  from typing import Optional
3
+ from uuid import UUID, uuid4
4
4
 
5
5
  from sqlmodel import SQLModel, Field
6
- from datetime import datetime, timezone
7
6
 
8
7
 
9
8
  class BaseModel(SQLModel):
@@ -1,5 +1,4 @@
1
1
  import os
2
-
3
2
  from functools import wraps
4
3
  from typing import Any, List
5
4
 
@@ -1,7 +1,6 @@
1
1
  import os
2
2
 
3
3
  from httpx import AsyncClient
4
-
5
4
  from talentro.util.singleton import SingletonMeta
6
5
 
7
6
 
@@ -3,7 +3,7 @@ from typing import Annotated
3
3
 
4
4
  from fastapi import Depends
5
5
  from sqlalchemy import create_engine
6
- from sqlmodel import SQLModel, Session
6
+ from sqlmodel import Session
7
7
 
8
8
  from ..util.singleton import SingletonMeta
9
9
 
@@ -1,21 +1,18 @@
1
1
  import base64
2
2
  import os
3
+ import tempfile
3
4
  import traceback
4
-
5
5
  from enum import StrEnum
6
6
  from http import HTTPStatus
7
7
 
8
- import tempfile
9
-
10
8
  from fastapi import UploadFile
11
9
  from google.api_core.exceptions import NotFound
12
10
  from google.cloud import storage
13
11
  from google.cloud.storage import Bucket, Blob
14
-
15
12
  from talentro.constants import ErrorCode, DisplayMessage
16
13
  from talentro.exceptions import APIException
17
- from ..general.dataclasses import FileData
18
14
 
15
+ from ..general.dataclasses import FileData
19
16
  from ..util.singleton import SingletonMeta
20
17
  from ..util.string import render_template_path
21
18
 
@@ -45,24 +42,20 @@ class GoogleStorage(metaclass=SingletonMeta):
45
42
  tmp_fd, tmp_path = tempfile.mkstemp(suffix=ext)
46
43
  os.close(tmp_fd)
47
44
 
48
- try:
49
- blob = self._get_blob(bucket, destination, path_definitions)
45
+ blob = self._get_blob(bucket, destination, path_definitions)
50
46
 
51
- with open(tmp_path, "wb") as f:
52
- f.write(file_bytes)
47
+ with open(tmp_path, "wb") as f:
48
+ f.write(file_bytes)
53
49
 
54
- blob.upload_from_filename(
55
- tmp_path,
56
- content_type=file.content_type # handig voor GCS metadata
57
- )
50
+ blob.upload_from_filename(
51
+ tmp_path,
52
+ content_type=file.content_type # handig voor GCS metadata
53
+ )
58
54
 
59
- if os.path.exists(tmp_path):
60
- os.remove(tmp_path)
55
+ if os.path.exists(tmp_path):
56
+ os.remove(tmp_path)
61
57
 
62
- return blob
63
- except Exception as e:
64
- print(e)
65
- traceback.print_exc()
58
+ return blob
66
59
 
67
60
  def _get_bucket(self, bucket: BucketEnum) -> Bucket:
68
61
  try:
@@ -1,4 +1,7 @@
1
- def safe_get(data: dict, attribute: str, default: any = None, splitter:str = '.'):
1
+ from typing import Any
2
+
3
+
4
+ def safe_get(data: dict, attribute: str, default: Any = None, splitter:str = '.'):
2
5
  if attribute is None:
3
6
  return default
4
7
 
@@ -1,7 +1,7 @@
1
1
  import base64
2
2
 
3
3
 
4
- def save_temp_file(file_name: str, data: str) -> str:
4
+ def save_temp_file(file_name: str, data: str) -> None:
5
5
  file_bytes = base64.b64decode(data)
6
6
 
7
7
  with open(file_name, "wb") as f:
@@ -17,7 +17,7 @@ def generate_vacancy_hash(vacancy_data: VacancyModel) -> str:
17
17
  "location_city", "location_state", "location_country", "salary_min",
18
18
  "salary_max", "salary_currency", "salary_frequency", "recruiter_first_name",
19
19
  "recruiter_last_name", "recruiter_phone_number", "recruiter_email",
20
- "recruiter_role"
20
+ "recruiter_role", "contract_type", "industry_category"
21
21
  ]
22
22
  }
23
23
 
@@ -1,26 +1,24 @@
1
- import os
2
-
1
+ from dataclasses import field
3
2
  from datetime import datetime
4
3
  from typing import List, Optional, Literal
5
4
  from uuid import UUID
6
- from dataclasses import field
7
- from pydantic.dataclasses import dataclass
8
5
 
6
+ from pydantic import BaseModel
9
7
  from pydantic import field_validator
8
+ from pydantic.dataclasses import dataclass
10
9
 
11
- from httpx import AsyncClient
12
- from pydantic import BaseModel
10
+ from .taxanomy import RemoteType, SalaryFrequency, JobCategory, Education, ExperienceLevel, ContractType, IndustryCategory
13
11
 
14
12
  from ..general.dataclasses import ResolvableCompanyModel
15
13
  from ..integrations.dataclasses import LinkInfo
14
+ from ..services.caching import CacheService
15
+ from ..services.clients import MSClient
16
16
  from ..util.enum import to_enum
17
17
  from ..util.vacancy import generate_vacancy_hash
18
+ from ..vacancies.models import Feed as FeedModel, Vacancy as VacancyModel
19
+ from ..vacancies.models import Question as QuestionModel, ApplicationFlow as ApplicationFlowModel, QuestionCategory, \
20
+ ApplicationFlowType
18
21
 
19
- from ..services.clients import MSClient
20
- from ..services.caching import CacheService
21
-
22
- from ..vacancies.models import Feed as FeedModel, Vacancy as VacancyModel, SalaryFrequency, RemoteType
23
- from ..vacancies.models import Question as QuestionModel, ApplicationFlow as ApplicationFlowModel, QuestionCategory, ApplicationFlowType
24
22
 
25
23
  class VacancyLocation(BaseModel):
26
24
  zipcode: str | None = None
@@ -122,8 +120,8 @@ class ApplicationFlowConfig(BaseModel):
122
120
  updated_at=model.updated_at,
123
121
  organization=model.organization,
124
122
  name=model.name,
125
- type=model.type,
126
- questions=questions,
123
+ type=ApplicationFlowType(model.type),
124
+ questions=[await QuestionConfig.from_model(question) for question in questions],
127
125
  )
128
126
 
129
127
 
@@ -162,9 +160,11 @@ class RawVacancy(BaseModel):
162
160
  job_site_url: str
163
161
  company_name: str
164
162
  publish_date: datetime | None = None
165
- category: List[str] = field(default_factory=list)
166
- experience: List[str] = field(default_factory=list)
167
- education: List[str] = field(default_factory=list)
163
+
164
+ category: List[JobCategory] = field(default_factory=list)
165
+ experience: List[ExperienceLevel] = field(default_factory=list)
166
+ education: List[Education] = field(default_factory=list)
167
+ industry_category: List[IndustryCategory] = field(default_factory=list)
168
168
 
169
169
  # Connected data
170
170
  hours: Hours = field(default_factory=Hours)
@@ -176,10 +176,14 @@ class RawVacancy(BaseModel):
176
176
  description: str | None = None
177
177
  status: str | None = None
178
178
  parent_company_name: str | None = None
179
- remote_type: RemoteType | None = None
180
179
  expiration_date: datetime | None = None
181
180
  last_updated_date: datetime | None = None
182
181
 
182
+ remote_type: RemoteType | None = None
183
+ contract_type: ContractType | None = None
184
+ video_url: str | None = None
185
+ applied_sanitizers: List[str] = []
186
+
183
187
  @field_validator("remote_type", mode="before")
184
188
  @classmethod
185
189
  def cast_remote_type(cls, v):
@@ -198,10 +202,12 @@ class RawVacancy(BaseModel):
198
202
  company_name=self.company_name,
199
203
  parent_company_name=self.parent_company_name,
200
204
  remote_type=self.remote_type.value if self.remote_type else None,
205
+ contract_type=self.contract_type.value if self.contract_type else None,
201
206
  publish_date=self.publish_date,
202
207
  expiration_date=self.expiration_date,
203
208
  last_updated_date=self.last_updated_date,
204
209
  category=self.category,
210
+ industry_category=self.industry_category,
205
211
  experience=self.experience,
206
212
  education=self.education,
207
213
  hours_fte=self.hours.fte,
@@ -223,6 +229,8 @@ class RawVacancy(BaseModel):
223
229
  recruiter_phone_number=self.recruiter.phone_number,
224
230
  recruiter_email=self.recruiter.email,
225
231
  recruiter_role=self.recruiter.role,
232
+ video_url=self.video_url,
233
+ applied_sanitizers=getattr(self, "applied_sanitizers", []),
226
234
  )
227
235
 
228
236
  checksum = generate_vacancy_hash(model)
@@ -266,7 +274,7 @@ class VacancyInfo(RawVacancy):
266
274
  return VacancyInfo(**result.json())
267
275
 
268
276
  @classmethod
269
- async def from_model(cls: "Vacancy", model: VacancyModel) -> "Vacancy":
277
+ async def from_model(cls: "VacancyInfo", model: VacancyModel) -> "VacancyInfo":
270
278
  if model.application_flow_id:
271
279
  application_flow = await ApplicationFlowConfig.resolve_object(model.application_flow_id, model.organization)
272
280
  else:
@@ -321,9 +329,13 @@ class VacancyInfo(RawVacancy):
321
329
  job_site_url=model.job_site_url,
322
330
  company_name=model.company_name,
323
331
  publish_date=model.publish_date,
324
- category=list(model.category or []),
325
- experience=list(model.experience or []),
326
- education=list(model.education or []),
332
+
333
+ contract_type=to_enum(ContractType, getattr(model, "contract_type", ContractType.UNSPECIFIED)),
334
+
335
+ category=[to_enum(JobCategory, category) for category in model.category or []],
336
+ experience=[to_enum(ExperienceLevel, experience) for experience in model.experience or []],
337
+ education=[to_enum(Education, education) for education in model.education or []],
338
+ industry_category=[to_enum(IndustryCategory, industry_category) for industry_category in model.industry_category or []],
327
339
 
328
340
  hours=hours,
329
341
  location=location,
@@ -333,8 +345,11 @@ class VacancyInfo(RawVacancy):
333
345
  status=model.status,
334
346
  parent_company_name=model.parent_company_name,
335
347
  remote_type=remote_type,
348
+ video_url=model.video_url,
336
349
  expiration_date=model.expiration_date,
337
350
  last_updated_date=model.last_updated_date,
351
+
352
+ applied_sanitizers=getattr(model, "applied_sanitizers", []),
338
353
  )
339
354
 
340
355
  # Feed object
@@ -1,30 +1,18 @@
1
- from enum import StrEnum
2
- from uuid import UUID
3
-
4
1
  from datetime import datetime
2
+ from enum import StrEnum
5
3
  from typing import Optional, List
4
+ from uuid import UUID
6
5
 
7
6
  from sqlalchemy import Column, JSON, UniqueConstraint, Enum
8
7
  from sqlmodel import Field, Relationship
9
8
 
10
9
  from ..general.models import BaseModel
11
10
 
11
+
12
12
  class SourceType(StrEnum):
13
13
  ATS = "ATS"
14
14
  CUSTOM_FILE = "CUSTOM_FILE"
15
15
 
16
- class SalaryFrequency(StrEnum):
17
- MONTH = "MONTH"
18
- YEAR = "YEAR"
19
- WEEK = "WEEK"
20
- HOUR = "HOUR"
21
-
22
-
23
- class RemoteType(StrEnum):
24
- ONSITE = "ONSITE"
25
- HYBRID = "HYBRID"
26
- REMOTE = "REMOTE"
27
-
28
16
 
29
17
  class QuestionCategory(StrEnum):
30
18
  SCREENING = "SCREENING"
@@ -59,6 +47,7 @@ class Feed(VacanciesOrganizationModel, table=True):
59
47
 
60
48
  vacancies: list["Vacancy"] = Relationship(back_populates="feed")
61
49
 
50
+
62
51
  class Vacancy(VacanciesOrganizationModel, table=True):
63
52
  feed_id: UUID = Field(foreign_key="feed.id", ondelete="CASCADE", index=True)
64
53
  feed: "Feed" = Relationship(back_populates="vacancies")
@@ -78,6 +67,8 @@ class Vacancy(VacanciesOrganizationModel, table=True):
78
67
  category: List[str] = Field(sa_column=Column(JSON))
79
68
  experience: List[str] = Field(sa_column=Column(JSON))
80
69
  education: List[str] = Field(sa_column=Column(JSON))
70
+ industry_category: List[str] = Field(sa_column=Column(JSON))
71
+ contract_type: Optional[str] = Field()
81
72
  hours_fte: Optional[float] = Field()
82
73
  hours_min: Optional[int] = Field()
83
74
  hours_max: Optional[int] = Field()
@@ -97,9 +88,11 @@ class Vacancy(VacanciesOrganizationModel, table=True):
97
88
  recruiter_phone_number: Optional[str] = Field()
98
89
  recruiter_email: Optional[str] = Field()
99
90
  recruiter_role: Optional[str] = Field()
91
+ video_url: Optional[str] = Field()
100
92
  checksum: str = Field(index=True)
101
93
  application_flow_id: Optional[UUID] = Field(foreign_key="applicationflow.id", ondelete="SET NULL", index=True, nullable=True)
102
94
  application_flow: "ApplicationFlow" = Relationship(back_populates="vacancies")
95
+ applied_sanitizers: List[str] = Field(sa_column=Column(JSON), default_factory=list)
103
96
 
104
97
  __table_args__ = (
105
98
  UniqueConstraint("feed_id", "reference_number", name="uq_feed_reference"),
@@ -0,0 +1,190 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class SalaryFrequency(StrEnum):
5
+ MONTH = "MONTH"
6
+ YEAR = "YEAR"
7
+ WEEK = "WEEK"
8
+ HOUR = "HOUR"
9
+
10
+ class RemoteType(StrEnum):
11
+ ONSITE = "ONSITE"
12
+ HYBRID = "HYBRID"
13
+ REMOTE = "REMOTE"
14
+
15
+ class JobCategory(StrEnum):
16
+ # Customer-facing & commercial
17
+ CUSTOMER_SERVICE = "CUSTOMER_SERVICE"
18
+ SALES = "SALES"
19
+ BUSINESS_DEVELOPMENT = "BUSINESS_DEVELOPMENT"
20
+ ACCOUNT_MANAGEMENT = "ACCOUNT_MANAGEMENT"
21
+
22
+ # Marketing & communication
23
+ MARKETING = "MARKETING"
24
+ COMMUNICATIONS_PR = "COMMUNICATIONS_PR"
25
+ CONTENT_COPY = "CONTENT_COPY"
26
+ BRAND = "BRAND"
27
+ EVENTS = "EVENTS"
28
+ DIGITAL_MARKETING = "DIGITAL_MARKETING"
29
+
30
+ # People & organization
31
+ HR = "HR"
32
+ RECRUITMENT = "RECRUITMENT"
33
+ LEARNING_AND_DEVELOPMENT = "LEARNING_AND_DEVELOPMENT"
34
+
35
+ # Finance & legal
36
+ FINANCE_ACCOUNTING = "FINANCE_ACCOUNTING"
37
+ CONTROLLING = "CONTROLLING"
38
+ LEGAL = "LEGAL"
39
+ COMPLIANCE_RISK = "COMPLIANCE_RISK"
40
+
41
+ # Tech
42
+ IT_SUPPORT = "IT_SUPPORT"
43
+ SOFTWARE_ENGINEERING = "SOFTWARE_ENGINEERING"
44
+ DATA_ANALYTICS = "DATA_ANALYTICS"
45
+ DATA_SCIENCE_ML = "DATA_SCIENCE_ML"
46
+ DEVOPS_SRE = "DEVOPS_SRE"
47
+ SECURITY = "SECURITY"
48
+ QA_TESTING = "QA_TESTING"
49
+ PRODUCT_MANAGEMENT = "PRODUCT_MANAGEMENT"
50
+ PROJECT_MANAGEMENT = "PROJECT_MANAGEMENT"
51
+ UX_UI_DESIGN = "UX_UI_DESIGN"
52
+
53
+ # Operations & supply chain
54
+ OPERATIONS = "OPERATIONS"
55
+ PROCUREMENT = "PROCUREMENT"
56
+ LOGISTICS_SUPPLY_CHAIN = "LOGISTICS_SUPPLY_CHAIN"
57
+ TRANSPORT = "TRANSPORT"
58
+ WAREHOUSE = "WAREHOUSE"
59
+ ADMINISTRATION = "ADMINISTRATION"
60
+ OFFICE_MANAGEMENT = "OFFICE_MANAGEMENT"
61
+ FACILITY_MANAGEMENT = "FACILITY_MANAGEMENT"
62
+
63
+ # Skilled trades & industry
64
+ ENGINEERING_TECHNICAL = "ENGINEERING_TECHNICAL"
65
+ MAINTENANCE = "MAINTENANCE"
66
+ MANUFACTURING_PRODUCTION = "MANUFACTURING_PRODUCTION"
67
+ QUALITY_MANUFACTURING = "QUALITY_MANUFACTURING"
68
+ CONSTRUCTION_INSTALLATION = "CONSTRUCTION_INSTALLATION"
69
+ ENERGY_UTILITIES = "ENERGY_UTILITIES"
70
+
71
+ # Sector-specific
72
+ HEALTHCARE_MEDICAL = "HEALTHCARE_MEDICAL"
73
+ EDUCATION_TRAINING = "EDUCATION_TRAINING"
74
+ RESEARCH_SCIENCE = "RESEARCH_SCIENCE"
75
+ GOVERNMENT_NONPROFIT = "GOVERNMENT_NONPROFIT"
76
+ SECURITY_DEFENSE = "SECURITY_DEFENSE"
77
+
78
+ # Retail & hospitality
79
+ RETAIL = "RETAIL"
80
+ HOSPITALITY = "HOSPITALITY"
81
+ FOOD_SERVICE = "FOOD_SERVICE"
82
+
83
+ # Creative & media
84
+ DESIGN_CREATIVE = "DESIGN_CREATIVE"
85
+ MEDIA_JOURNALISM = "MEDIA_JOURNALISM"
86
+
87
+ # Consulting & advisory
88
+ CONSULTING_ADVISORY = "CONSULTING_ADVISORY"
89
+
90
+ OTHER = "OTHER"
91
+
92
+
93
+ class IndustryCategory(StrEnum):
94
+ UNSPECIFIED = "UNSPECIFIED"
95
+
96
+ ICT = "ICT"
97
+ GOVERNMENT_NONPROFIT = "GOVERNMENT_NONPROFIT"
98
+ FASHION_TEXTILES_COSMETICS = "FASHION_TEXTILES_COSMETICS"
99
+ WHOLESALE_TRADE = "WHOLESALE_TRADE"
100
+ BANKING_FINANCIAL_SERVICES = "BANKING_FINANCIAL_SERVICES"
101
+ AUTOMOTIVE = "AUTOMOTIVE"
102
+ INTERNET_SERVICES = "INTERNET_SERVICES"
103
+ CONSULTING_ADVISORY = "CONSULTING_ADVISORY"
104
+ RETAIL = "RETAIL"
105
+ CONSTRUCTION_INSTALLATION = "CONSTRUCTION_INSTALLATION"
106
+ TELECOMMUNICATIONS = "TELECOMMUNICATIONS"
107
+ HOSPITALITY = "HOSPITALITY"
108
+ CHEMICALS_PETROCHEMICALS = "CHEMICALS_PETROCHEMICALS"
109
+ FACILITY_SERVICES = "FACILITY_SERVICES"
110
+ INSURANCE = "INSURANCE"
111
+ ENGINEERING_TECHNICAL = "ENGINEERING_TECHNICAL"
112
+ EDUCATION_TRAINING = "EDUCATION_TRAINING"
113
+ SPORTS_RECREATION_TOURISM = "SPORTS_RECREATION_TOURISM"
114
+ INDUSTRIAL_MANUFACTURING = "INDUSTRIAL_MANUFACTURING"
115
+ ELECTRONICS = "ELECTRONICS"
116
+ FMCG = "FMCG"
117
+ SECURITY_SERVICES = "SECURITY_SERVICES"
118
+ BUSINESS_SERVICES = "BUSINESS_SERVICES"
119
+ PASSENGER_TRANSPORT = "PASSENGER_TRANSPORT"
120
+ STAFFING_RECRUITMENT = "STAFFING_RECRUITMENT"
121
+ PHARMACEUTICALS = "PHARMACEUTICALS"
122
+ AGRICULTURE_FORESTRY_FISHING = "AGRICULTURE_FORESTRY_FISHING"
123
+ WASTE_ENVIRONMENT = "WASTE_ENVIRONMENT"
124
+ MARITIME = "MARITIME"
125
+ REAL_ESTATE = "REAL_ESTATE"
126
+ LOGISTICS_TRANSPORT_DISTRIBUTION = "LOGISTICS_TRANSPORT_DISTRIBUTION"
127
+ HEALTHCARE_WELLBEING = "HEALTHCARE_WELLBEING"
128
+ LEGAL_SERVICES = "LEGAL_SERVICES"
129
+ ACCOUNTING_AUDIT = "ACCOUNTING_AUDIT"
130
+ ADVERTISING_PR_COMMUNICATIONS = "ADVERTISING_PR_COMMUNICATIONS"
131
+ ARTS_CULTURE_ENTERTAINMENT = "ARTS_CULTURE_ENTERTAINMENT"
132
+ MEDIA_PUBLISHING_BROADCASTING = "MEDIA_PUBLISHING_BROADCASTING"
133
+ ENERGY_UTILITIES = "ENERGY_UTILITIES"
134
+
135
+ OTHER = "OTHER"
136
+
137
+ class ExperienceLevel(StrEnum):
138
+ UNSPECIFIED = "UNSPECIFIED"
139
+
140
+ INTERN = "INTERN"
141
+ ENTRY_LEVEL = "ENTRY_LEVEL"
142
+ JUNIOR = "JUNIOR"
143
+ MID_LEVEL = "MID_LEVEL"
144
+ SENIOR = "SENIOR"
145
+ LEAD = "LEAD"
146
+ MANAGER = "MANAGER"
147
+ SENIOR_MANAGER = "SENIOR_MANAGER"
148
+ DIRECTOR = "DIRECTOR"
149
+ EXECUTIVE = "EXECUTIVE"
150
+
151
+ OTHER = "OTHER"
152
+
153
+ class Education(StrEnum):
154
+ UNSPECIFIED = "UNSPECIFIED"
155
+
156
+ # ISCED 2011 niveaus (UNESCO)
157
+ NO_FORMAL_EDUCATION = "NO_FORMAL_EDUCATION"
158
+ PRIMARY = "PRIMARY"
159
+ LOWER_SECONDARY = "LOWER_SECONDARY"
160
+ UPPER_SECONDARY = "UPPER_SECONDARY"
161
+ POST_SECONDARY_NON_TERTIARY = "POST_SECONDARY_NON_TERTIARY"
162
+ SHORT_CYCLE_TERTIARY = "SHORT_CYCLE_TERTIARY"
163
+ BACHELOR = "BACHELOR"
164
+ MASTER = "MASTER"
165
+ DOCTORATE = "DOCTORATE"
166
+ POSTDOCTORAL = "POSTDOCTORAL"
167
+
168
+ OTHER = "OTHER"
169
+
170
+ class ContractType(StrEnum):
171
+ UNSPECIFIED = "UNSPECIFIED"
172
+
173
+ PERMANENT = "PERMANENT" # indefinite / open-ended
174
+ TEMPORARY = "TEMPORARY" # fixed-term
175
+ CONTRACTOR = "CONTRACTOR" # contractor / 1099 / zelfstandige (zonder payroll)
176
+ FREELANCE = "FREELANCE" # freelancer (overlaps contractor; keep if channels distinguish)
177
+ SELF_EMPLOYED = "SELF_EMPLOYED" # zzp / independent (if you want explicit)
178
+ INTERIM = "INTERIM" # interim assignment
179
+
180
+ INTERN = "INTERN" # internship / stage
181
+ APPRENTICESHIP = "APPRENTICESHIP" # leer-werk / dual / BBL
182
+ TEMP_AGENCY = "TEMP_AGENCY" # via uitzendbureau / staffing
183
+ VOLUNTEER = "VOLUNTEER" # volunteer
184
+ SEASONAL = "SEASONAL" # seasonal work
185
+ STUDENT_JOB = "STUDENT_JOB" # part-time student job / bijbaan
186
+ HOLIDAY_JOB = "HOLIDAY_JOB" # vacation job
187
+
188
+ FRANCHISE = "FRANCHISE" # franchise / zelfstandige formule
189
+
190
+ OTHER = "OTHER"
@@ -1,7 +1,7 @@
1
1
  from uuid import UUID
2
2
 
3
- from sqlmodel import Field
4
3
  from sqlalchemy import Column, JSON
4
+ from sqlmodel import Field
5
5
 
6
6
  from ..general.models import BaseModel
7
7
 
@@ -1,13 +1,13 @@
1
- from uuid import UUID
2
1
  from datetime import datetime
3
2
  from typing import Optional
3
+ from uuid import UUID
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from .models import Link as LinkModel, Integration as IntegrationModel, IntegrationType
8
8
  from ..general.dataclasses import ResolvableModel, ResolvableCompanyModel, FileData
9
- from ..services.clients import MSClient
10
9
  from ..services.caching import CacheService
10
+ from ..services.clients import MSClient
11
11
 
12
12
 
13
13
  # Integration object
@@ -2,10 +2,10 @@ from datetime import datetime, UTC
2
2
  from functools import wraps
3
3
  from http import HTTPStatus
4
4
 
5
- from ..event import Message, Event, EventMeta
6
- from ..services.rabbitmq import QueueContext
7
5
  from ..constants import ErrorCode, DisplayMessage, SKU
6
+ from ..event import Message, Event, EventMeta
8
7
  from ..exceptions import APIException
8
+ from ..services.rabbitmq import QueueContext
9
9
 
10
10
 
11
11
  def billable_event(sku: SKU, details=None):