"""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)