You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
64 lines
1.7 KiB
Python
64 lines
1.7 KiB
Python
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class RawJobCard(BaseModel):
|
|
"""Raw extracted data from a single job card on a page."""
|
|
|
|
title: str
|
|
company: Optional[str] = None
|
|
location: Optional[str] = None
|
|
posted_text: Optional[str] = None
|
|
url: Optional[str] = None
|
|
salary_text: Optional[str] = None
|
|
employment_type: Optional[str] = None
|
|
raw_text: Optional[str] = None
|
|
external_job_id: Optional[str] = None
|
|
|
|
|
|
class JobPostingCandidate(BaseModel):
|
|
"""Normalized job posting ready for DB upsert."""
|
|
|
|
site_id: str
|
|
external_job_id: Optional[str] = None
|
|
title: str
|
|
company: Optional[str] = None
|
|
location: Optional[str] = None
|
|
posted_text: Optional[str] = None
|
|
posted_at_normalized: Optional[datetime] = None
|
|
job_url: Optional[str] = None
|
|
salary_text: Optional[str] = None
|
|
employment_type: Optional[str] = None
|
|
raw_text: Optional[str] = None
|
|
fingerprint: str = ""
|
|
|
|
def is_valid(self) -> bool:
|
|
return bool(self.title and self.title.strip())
|
|
|
|
|
|
class SearchQuery(BaseModel):
|
|
"""Merged search parameters for a single site run."""
|
|
|
|
keywords: list[str] = Field(default_factory=lambda: ["orthodontist"])
|
|
location: str = ""
|
|
remote: bool = False
|
|
date_mode: str = "today_or_last_24h"
|
|
sort: str = "relevance"
|
|
max_items: int = 30
|
|
|
|
@property
|
|
def keywords_str(self) -> str:
|
|
return " ".join(self.keywords)
|
|
|
|
@property
|
|
def keywords_urlencoded(self) -> str:
|
|
from urllib.parse import quote_plus
|
|
return quote_plus(self.keywords_str)
|
|
|
|
@property
|
|
def location_urlencoded(self) -> str:
|
|
from urllib.parse import quote_plus
|
|
return quote_plus(self.location)
|