"""HospitalRecruiting adapter — JS extraction from job cards.""" from __future__ import annotations import re 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 _BASE_URL = "https://www.hospitalrecruiting.com" _EXTRACT_JS = """() => { const cards = document.querySelectorAll('div:has(> a[href^="/job/"][aria-label])'); return Array.from(cards).map(card => { const link = card.querySelector('a[href^="/job/"][aria-label]'); const h2 = card.querySelector('h2'); const divs = Array.from(card.querySelectorAll(':scope > div')); const companyDiv = divs.find(d => d.textContent.includes('Company:')); const locationDiv = divs.find(d => !d.textContent.includes('Company:') && d.textContent.trim()); const href = link ? link.getAttribute('href') : ''; const idMatch = href.match(/\\/job\\/(\\d+)\\//); return { title: h2?.textContent?.trim() || link?.getAttribute('aria-label') || '', url: href, company: companyDiv ? companyDiv.textContent.replace('Company:', '').trim() : '', location: locationDiv?.textContent?.trim() || '', external_job_id: idMatch ? idMatch[1] : '', }; }); }""" @register("hospitalrecruiting") class HospitalRecruitingAdapter(ManifestDrivenAdapter): def collect_cards(self, page, manifest) -> list[RawJobCard]: try: page.wait_for_selector('a[href^="/job/"][aria-label]', timeout=10000) except Exception: logger.debug("[hospitalrecruiting] job link wait timed out") return self._extract_cards(page) def paginate(self, page, page_index: int, config) -> bool: return False def normalize(self, raw: RawJobCard) -> JobPostingCandidate: # Make URL absolute if raw.url and raw.url.startswith("/"): raw = raw.model_copy(update={"url": _BASE_URL + raw.url}) # Strip "Specialty - " prefix from location # e.g. "Orthodontist - Roanoke, Virginia" → "Roanoke, Virginia" if raw.location: m = re.match(r'^[^,]+ - (.+)$', raw.location) if m: raw = raw.model_copy(update={"location": m.group(1)}) return super().normalize(raw) def _extract_cards(self, page) -> list[RawJobCard]: data = page.evaluate(_EXTRACT_JS) cards = [] for item in data: title = (item.get("title") or "").strip() if not title: continue cards.append(RawJobCard( title=title, company=(item.get("company") or "").strip() or None, location=(item.get("location") or "").strip() or None, url=item.get("url") or None, external_job_id=(item.get("external_job_id") or "").strip() or None, )) logger.debug(f"[hospitalrecruiting] Extracted {len(cards)} cards") return cards