feat: rebuild evidence-first application workflow
This commit is contained in:
+40
-5
@@ -255,6 +255,11 @@ COMPANIES = [
|
||||
"scroll_count": 5,
|
||||
"use_inner_text_as_blob": True,
|
||||
"cookie_accept": ["button:has-text('Accept all')", "button:has-text('Reject all')"],
|
||||
# Results are paged 20 at a time and "page" is 1-indexed — without this only the
|
||||
# first 20 of ~48 Zürich roles were ever scraped.
|
||||
"page_param": "page",
|
||||
"page_param_start": 2,
|
||||
"max_pages": 6,
|
||||
}),
|
||||
("apple", "Apple", "playwright", {
|
||||
"url": "https://jobs.apple.com/en-us/search?location=switzerland-CHE",
|
||||
@@ -534,7 +539,8 @@ def fetch_pcsx(args):
|
||||
while True:
|
||||
url = f"{base}?domain={domain}&query=&location={urllib.parse.quote(location)}&start={start}&num=50"
|
||||
data = http_get_json(url, headers={"Referer": f"https://apply.careers.microsoft.com/careers?location={urllib.parse.quote(location)}"})
|
||||
positions = (data.get("data") or {}).get("positions", []) or []
|
||||
payload = data.get("data") or {}
|
||||
positions = payload.get("positions", []) or []
|
||||
for p in positions:
|
||||
locs = p.get("locations") or []
|
||||
jobs.append({
|
||||
@@ -545,9 +551,16 @@ def fetch_pcsx(args):
|
||||
"posted": p.get("postedTs", ""),
|
||||
"description": (p.get("description") or "")[:2000],
|
||||
})
|
||||
if not positions or len(positions) < 50:
|
||||
# The API serves ~10 rows per call regardless of `num`, so a short page is NOT
|
||||
# end-of-results — page until a call comes back empty or `count` is reached.
|
||||
# (The old `len(positions) < 50` break capped Microsoft at its first 10 CH roles
|
||||
# and hid the entire Zürich Principal-FDE cluster sitting on page 2.)
|
||||
if not positions:
|
||||
break
|
||||
start += len(positions)
|
||||
total = payload.get("count")
|
||||
if isinstance(total, int) and start >= total:
|
||||
break
|
||||
if start >= 500:
|
||||
break
|
||||
return jobs
|
||||
@@ -1016,6 +1029,23 @@ def _absolutize(href, prefix):
|
||||
return prefix.rstrip("/") + "/" + cleaned
|
||||
|
||||
|
||||
def _strip_query_param(url, param):
|
||||
"""Drop a single query parameter from a URL, preserving the rest.
|
||||
|
||||
Job-card hrefs inherit the listing page's query string, so with query-param
|
||||
pagination the same posting would get a different URL (and therefore a different
|
||||
id) depending on which page it happened to land on — making every reshuffle look
|
||||
like a brand-new job and orphaning its decision-log entry.
|
||||
"""
|
||||
if not url or f"{param}=" not in url:
|
||||
return url
|
||||
head, _, query = url.partition("?")
|
||||
if not query:
|
||||
return url
|
||||
kept = [kv for kv in query.split("&") if kv and not kv.startswith(f"{param}=")]
|
||||
return f"{head}?{'&'.join(kept)}" if kept else head
|
||||
|
||||
|
||||
def _extract_location_from_blob(full, default=""):
|
||||
"""Pull a short location line out of a job-card text blob.
|
||||
|
||||
@@ -1127,6 +1157,8 @@ def fetch_playwright(args):
|
||||
link_el = card if not args.get("link_sel") else card.locator(args["link_sel"]).first
|
||||
href = (link_el.get_attribute(args.get("link_attr", "href")) or "") if link_el.count() else ""
|
||||
href = _absolutize(href, args.get("url_prefix", ""))
|
||||
if args.get("page_param"):
|
||||
href = _strip_query_param(href, args["page_param"])
|
||||
|
||||
if not title:
|
||||
continue
|
||||
@@ -1172,15 +1204,18 @@ def fetch_playwright(args):
|
||||
page.wait_for_timeout(500)
|
||||
except Exception:
|
||||
pass
|
||||
# Optional query-param pagination (e.g. Drupal "?page=N", 0-indexed). The base URL is
|
||||
# page 0 (already loaded); fetch successive pages until one adds no new cards.
|
||||
# Optional query-param pagination (e.g. Drupal "?page=N"). The base URL is the first
|
||||
# page (already loaded); fetch successive pages until one adds no new cards.
|
||||
# `page_param_start` is the value the SECOND page takes: 1 for 0-indexed boards
|
||||
# (Drupal), 2 for 1-indexed ones (Google, where "?page=1" is just page one again).
|
||||
page_param = args.get("page_param")
|
||||
if page_param:
|
||||
base = args["url"]
|
||||
joiner = "&" if "?" in base else "?"
|
||||
second = args.get("page_param_start", 1)
|
||||
for p in range(args.get("max_pages", 8)):
|
||||
if p > 0:
|
||||
page.goto(f"{base}{joiner}{page_param}={p}", timeout=45000,
|
||||
page.goto(f"{base}{joiner}{page_param}={second + p - 1}", timeout=45000,
|
||||
wait_until="domcontentloaded")
|
||||
added = scrape_current()
|
||||
if p > 0 and added == 0:
|
||||
|
||||
@@ -135,8 +135,8 @@
|
||||
"https://www.google.com/about/careers/applications/jobs/results/87066954308690630-senior-data-engineer?location=Switzerland": {
|
||||
"company": "Google",
|
||||
"title": "Senior Data Engineer (Merchant Data Science)",
|
||||
"decision": "applied",
|
||||
"note": "Tier-1 DE fit; self-serve data products = team charter; Zurich team. | SENT 2026-06-15 (resume+CL finalized, 85.5/100). Clarify L4/L5 + comp 180k+ at recruiter stage. ADVANCED 2026-06-17: invited to 30-min Google Hiring Assessment.",
|
||||
"decision": "rejected",
|
||||
"note": "Tier-1 DE fit; self-serve data products matched team charter. SENT 2026-06-15 (85.5/100). Invited to Google Hiring Assessment 2026-06-17; PASSED 2026-06-20. CLOSED 2026-07-24: Google Careers status changed to Not proceeding; no interview. Assessment pass retained separately. Official reapply rule: 90 days for the same job only; other Google roles remain eligible subject to 3 applications per rolling 30 days.",
|
||||
"date": "2026-06-15"
|
||||
},
|
||||
"https://jobs.ashbyhq.com/kraken.com/c331de1b-b75a-48f5-9d19-0e56ccb935ab": {
|
||||
@@ -723,9 +723,9 @@
|
||||
"https://databricks.com/company/careers/open-positions/job?gh_jid=8396801002": {
|
||||
"company": "Databricks",
|
||||
"title": "Senior Forward Deployed Engineer, Full Stack",
|
||||
"decision": "shortlist",
|
||||
"note": "Claude judgment 2026-07-06: forward-deployed lane is an explicit interest area; Databricks is a target data-infra company",
|
||||
"date": "2026-07-06"
|
||||
"decision": "skip",
|
||||
"note": "Senior FDE, Full Stack is hybrid London-only; not CH-eligible. Stronger title match than OpenAI, but the required work location is a hard stop.",
|
||||
"date": "2026-07-22"
|
||||
},
|
||||
"https://databricks.com/company/careers/open-positions/job?gh_jid=8439078002": {
|
||||
"company": "Databricks",
|
||||
@@ -737,16 +737,16 @@
|
||||
"https://databricks.com/company/careers/open-positions/job?gh_jid=8506063002": {
|
||||
"company": "Databricks",
|
||||
"title": "Senior Specialist Solutions Architect - AI & ML Engineer",
|
||||
"decision": "shortlist",
|
||||
"note": "Claude judgment 2026-07-06: AI-platform positioning fit; Nordics-remote is an explicitly stated remote lane",
|
||||
"date": "2026-07-06"
|
||||
"decision": "skip",
|
||||
"note": "Senior Specialist Solutions Architect AI/ML Engineer is located Finland, Remote-Denmark, or Stockholm only, and requires 7+ years hands-on ML plus enterprise GenAI/RAG/agents and pre-sales depth. Not CH-eligible and beyond verified LLM scope.",
|
||||
"date": "2026-07-22"
|
||||
},
|
||||
"https://databricks.com/company/careers/open-positions/job?gh_jid=8303017002": {
|
||||
"company": "Databricks",
|
||||
"title": "Senior Staff Software Engineer - Delta",
|
||||
"decision": "maybe",
|
||||
"note": "Claude judgment 2026-07-06 (updated): downgraded from shortlist - same Senior Staff SWE title/level as the Unity Catalog req at the same company, which was confirmed to require 15+ years experience. Likely same bar; verify JD before pursuing.",
|
||||
"date": "2026-07-06"
|
||||
"decision": "skip",
|
||||
"note": "Senior Staff SWE Delta is Zurich-based but requires 15+ years large-scale distributed-systems experience, low-level performance/debugging, and Delta Lake core/open-source work; not the data-platform integration profile.",
|
||||
"date": "2026-07-22"
|
||||
},
|
||||
"https://databricks.com/company/careers/open-positions/job?gh_jid=8422483002": {
|
||||
"company": "Databricks",
|
||||
@@ -1311,9 +1311,9 @@
|
||||
"https://novartis.wd3.myworkdayjobs.com/job/Basel-City/Forward-Deployed-Engineer--FDE--Data42_REQ-10079038-1": {
|
||||
"company": "Novartis",
|
||||
"title": "Forward Deployed Engineer (FDE) Data42",
|
||||
"decision": "shortlist",
|
||||
"note": "Claude judgment 2026-07-06: FDE + internal data platform (Data42) is a direct hit on your forward-deployed lane and data-eng stack, hands-on IC role not leadership",
|
||||
"date": "2026-07-06"
|
||||
"decision": "skip",
|
||||
"note": "Essential requirements are an unrelated biomedical degree, 8+ years in biomedical research/bioengineering/bioinformatics, and hands-on Palantir Foundry delivery. Despite the data-engineering/FDE title, this is not an honest fit.",
|
||||
"date": "2026-07-22"
|
||||
},
|
||||
"https://novartis.wd3.myworkdayjobs.com/job/Basel-City/Global-Head-of-Technology---Architecture-in-Biomedical-Research--BR-_REQ-10079333-1": {
|
||||
"company": "Novartis",
|
||||
@@ -1664,5 +1664,12 @@
|
||||
"decision": "skip",
|
||||
"note": "Cloud AI Architect — above experience level (user judgment 2026-07-18).",
|
||||
"date": "2026-07-18"
|
||||
},
|
||||
"https://jobs.ashbyhq.com/openai/067788c8-505b-4296-bc05-d5699cb87a1f": {
|
||||
"company": "",
|
||||
"title": "",
|
||||
"decision": "skip",
|
||||
"note": "OpenAI FDE Zurich: decline to pursue. Structural gaps in customer-facing external deployments, current full-stack frontend delivery, production LLM ownership/evaluation; 50% travel also a deliberate lifestyle tradeoff. Keep core search on Staff Data Engineering, data/AI platform, and more evidence-aligned FDE roles.",
|
||||
"date": "2026-07-22"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user