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.

105 lines
4.0 KiB
Python

"""Gila River Health Care adapter — Infor CloudSuite, keyword search + click pagination."""
from __future__ import annotations
from loguru import logger
from gimme_job.adapters.base import ManifestDrivenAdapter
from gimme_job.adapters.registry import register
from gimme_job.models.dto import JobPostingCandidate, RawJobCard
_SEARCH_KEYWORD = "dent" # broad match: captures Dentist, Dental, etc.
_KEYWORDS = ["orthodontist", "orthodontic", "dentist"]
@register("gilariver")
class GilaRiverAdapter(ManifestDrivenAdapter):
def apply_search(self, page, query) -> None:
"""Type keyword into search form and submit; wait for filtered results."""
try:
page.wait_for_selector(
".JobPostingSearchRel_col_KeywordSearch",
timeout=15000,
state="visible",
)
kw_input = page.query_selector(".JobPostingSearchRel_col_KeywordSearch")
if not kw_input:
logger.warning("[gilariver] Keyword input not found")
return
kw_input.click(click_count=3)
kw_input.type(_SEARCH_KEYWORD, delay=50)
kw_input.press("Enter")
page.wait_for_load_state("networkidle", timeout=15000)
logger.info(f"[gilariver] Searched for '{_SEARCH_KEYWORD}'")
except Exception as e:
logger.warning(f"[gilariver] apply_search failed: {e}")
def collect_cards(self, page, manifest) -> list[RawJobCard]:
"""Extract cards via JS and filter by keywords in-memory."""
raw_data = page.evaluate("""() => {
const cards = document.querySelectorAll('.lm-card-content');
return Array.from(cards).map(card => {
const titleA = card.querySelector('span.lm-card-field.text-strong a');
const subtitle = card.querySelector(
'span.lm-card-field:not(.text-strong):not(.text-emphasis)'
);
const posted = card.querySelector('span.lm-card-field.text-emphasis');
return {
title: (titleA?.textContent || '').trim(),
url: titleA?.href || '',
subtitle: (subtitle?.textContent || '').trim(),
posted: (posted?.textContent || '').trim()
};
}).filter(r => r.title);
}""")
cards = []
for item in raw_data:
title = item["title"]
if not self._matches_keywords(title):
continue
# Subtitle: "Location | Category | Status - Employment Type"
parts = [p.strip() for p in item["subtitle"].split("|")]
location = parts[0] if parts else None
cards.append(RawJobCard(
title=title,
company="Gila River Health Care",
location=location or None,
posted_text=item["posted"] or None,
url=item["url"] or None,
))
logger.info(
f"[gilariver] Keyword filter: {len(cards)}/{len(raw_data)} cards matched"
)
return cards
def paginate(self, page, page_index: int, config) -> bool:
"""Click Next Page button; return False when disabled or missing."""
try:
buttons = page.query_selector_all("button")
next_btn = next(
(b for b in buttons if b.inner_text().strip() == "Next Page"), None
)
if not next_btn or not next_btn.is_enabled():
return False
next_btn.click()
page.wait_for_load_state("networkidle", timeout=15000)
return True
except Exception as e:
logger.debug(f"[gilariver] paginate failed: {e}")
return False
def normalize(self, raw: RawJobCard) -> JobPostingCandidate:
return super().normalize(raw)
@staticmethod
def _matches_keywords(title: str | None) -> bool:
if not title:
return False
title_lower = title.lower()
return any(kw in title_lower for kw in _KEYWORDS)