diff --git a/.gitignore b/.gitignore index 2bb3c9e..df4bc54 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,7 @@ node_modules/ # job data and personal notes results/ -jobs/ +# symlinks into the private ../rafapolo/job/ (gitignored there too) +jobs +cv -# CV files (large binaries + personal) -cv/*.pdf -cv/*.odt -cv/*.html diff --git a/CLAUDE.md b/CLAUDE.md index d04bb1a..04d7daf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,10 +30,10 @@ telegram.ts # Telegram delivery, chunking, --setup chat-ID discovery scripts/ # one-off search scripts; import from ../config.ts and ../fetch.ts scripts/scicore-watch.sh # daily launchd watch on sciCORE's open-positions page results/sent.txt # links already pushed to Telegram — never sent twice +cv/ → ../rafapolo/job/cv/ # symlink; CVs (cv-source.md is canonical, polo.md is the ranking profile) cv/polo.md # Rafael's profile — use for ranking jobs -cv/ # CV files (html, pdf, odt) results/jobs.csv # output — one row per job -jobs/ # notes on specific job leads +jobs/ → ../rafapolo/job/applications/ # symlink; one folder per company: cover/motivation letters, dossiers, notes plans/ # LinkedIn post drafts ``` @@ -178,6 +178,12 @@ Extra filters for temp scripts (import from `../fetch.ts`): - `isEnglishContent(id)` — drop DE/FR description content - Add your own inline RE for EMEA, .NET, etc. +## Application history (`jobs/`, `cv/`) + +`cv/` and `jobs/` are symlinks into the **private** `../rafapolo/job/` (gitignored in both repos; `rafapolo` is a public GitHub Pages site, so never move these into a non-ignored path there). Everything else about past applications lives beside them: `../rafapolo/job/career-strategy-notes.md`, `credentials/` (certs, diplomas, references) and the dossier PDFs at the root. + +Before presenting a job or writing a letter, check `jobs//` (e.g. `ls jobs | grep -i roche`): if a folder exists, Rafael already applied there — say so, reuse the earlier letter as a starting point, and mention any track record in memory (e.g. Adobe and BIS cold applies are dead). New letters go in `jobs//`. + ## Ranking jobs against the CV Read `cv/polo.md` for the full profile. Key signals: @@ -216,6 +222,7 @@ Runs as a **launchd agent** — this machine has no cron at all, so don't reach | plist (`~/Library/LaunchAgents/`) | when | runs | |---|---|---| | `com.rafapolo.linkme-daily` | 09:00 / 12:00 / 15:00 / 17:00 / 20:00 | `bun run daily.ts` → `results/daily.log` | +| `com.rafapolo.linkme-watch-delivery-lead` | 10:00 | `bun run scripts/watch-delivery-lead.ts` → `results/watch-delivery-lead.log`; watches for the MRP-Global "Delivery Lead – Agentic Solutions" opening (LinkedIn 4473851998) resurfacing elsewhere and for the original closing. Telegram only on a hit. Unload once the lead is resolved | If the Mac is asleep at the scheduled time, launchd fires the job on wake rather than skipping the day. `launchctl list | grep linkme` to check it's loaded; `launchctl unload ` to stop it. diff --git a/cv/old/polo-cv-accurate.html b/cv/old/polo-cv-accurate.html deleted file mode 100644 index af1b388..0000000 --- a/cv/old/polo-cv-accurate.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - Rafael Polo - CV - - - -
Rafael Polo
-
Senior Software Engineer — Platform, Data & AI
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Personal
First name:Rafael
Last name:Polo
Citizenship:Portuguese (EU) · Brazilian — no work permit required
Address:Basel, 4055, Switzerland
Email:rafael@extrapolo.com
Web:linkedin.com/in/rafaelpolo
extrapolo.com
- -

Formal Education

-
    -
  • - Postgraduate in Data Science for Finance – 2021
    - NOVA IMS Information Management School, Lisbon -
  • -
  • - Master in Information Systems – 2010–2012
    - PPGI/UNIRIO — Knowledge Representation & Reasoning -
  • -
  • - Bachelor in Computer Science – 2004–2009
    - Metodista Bennett, Rio de Janeiro -
  • -
- -

Relevant Professional Experience

-
    -
  • - Swisscom – 2024–2025 – Zürich, Switzerland
    - DevOps Engineer developing platforms for Telecommunication Operations. -
  • -
  • - EDGE Empower® – 2024–2025 – Baar, Switzerland
    - Senior Technical Lead managing projects and external UX & Dev Teams for a SaaS Product. -
  • -
  • - ExtraPolo — Independent Consulting – 2010–2022 – Rio de Janeiro · Berlin · Athens · Lisbon
    - 40+ projects across civic tech, data journalism, open-source platforms and interactive installations for public and private clients. -
  • -
  • - Moedax Digital Services – 2018–2021 – Lisbon · Rio de Janeiro
    - Founder & CTO – Fintech startup connecting CBDC digital currency with KYC compliance automation. -
  • -
  • - GFOSS.eu / Code + Create – 2017–2018 – Athens, Greece
    - Web Programming Teacher – taught HTML, CSS, jQuery to refugee and local youth. -
  • -
  • - Tactical Tech Collective – 2014–2016 – Berlin, Germany
    - Full-Stack Web Architect – investigative data tools under Exposing the Invisible team. -
  • -
  • - Trommsdorf + Drüner – 2013–2014 – Berlin, Germany
    - Full-Stack Engineer – Innovation Team developing Business Intelligence dashboards. -
  • -
  • - Cortex Intelligence – 2007–2008 – Rio de Janeiro, Brazil
    - Developer – Competitive Intelligence and Data Mining R&D team. -
  • -
- -

Certifications

-
    -
  • Swiss Federal Act on Data Protection (nFADP)
  • -
  • Gen AI Foundational Models — NLP & LLMs
  • -
  • AI Agents using RAG and LangChain
  • -
- -

Relevant Tech Skills

-
    -
  • Languages & Frameworks: Python · Ruby on Rails · TypeScript · React · Java
  • -
  • DevOps & Infra: Docker · Kubernetes · CI/CD · AWS · Linux · Git
  • -
  • Data & AI: SQL · ETL · Data Engineering · RAG · LangChain · LLM integrations
  • -
  • Architecture: REST APIs · MVC · OOP · UML · System Design · Web Crawling
  • -
- -

Natural Languages

-
    -
  • Native Brazilian Portuguese
  • -
  • Full professional English
  • -
  • Full professional Spanish
  • -
  • German B1 – TELC certified · 3 yrs Berlin + 3 yrs Basel
  • -
- - - - \ No newline at end of file diff --git a/cv/old/polo-cv.html b/cv/old/polo-cv.html deleted file mode 100644 index ec614a8..0000000 --- a/cv/old/polo-cv.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - Rafael Polo - CV - - - -
Rafael Polo
-
Senior Software Engineer — Platform, Data & AI
- -
- Basel, 4055, Switzerland - rafael@extrapolo.com - linkedin.com/in/rafaelpolo - extrapolo.com -
- -
- Portuguese (EU) · Brazilian — no work permit required - 20+ years building production systems across Switzerland, Germany, Greece and Brazil - Fluent English · German B1 TELC (3 yrs Berlin + 3 yrs Basel) · EU citizen, immediately available -
- -
-

P R O F E S S I O N A L E X P E R I E N C E

- -
-
Swisscom
-
DevOps Engineer · Zürich, Switzerland
-
Modernization of mission-critical internal platforms & CI/CD pipelines for high-availability Telecom infrastructure.
-
- -
-
EDGE Empower®
-
Senior Technical Lead · Baar, Switzerland · 2024 – 2025
-
Built a PayTool cutting processing time by 98%; reduced CI/CD pipeline from 14 → 4 minutes; managed 3 developers and a UX designer for a SaaS product.
-
- -
-
ExtraPolo — Independent Consulting
-
Software Engineer & Consultant · Rio de Janeiro · Berlin · Athens · Lisbon · 2010 – 2022
-
40+ projects across civic tech, data journalism, open-source platforms and interactive installations for public and private clients.
-
- -
-
Moedax Digital Services
-
Founder & CTO · Lisbon · Rio de Janeiro · 2018 – 2021
-
Fintech startup connecting CBDC digital currency with KYC compliance automation; built data pipelines and banking integrations in Python.
-
- -
-
GFOSS.eu / Code + Create
-
Web Programming Teacher · Athens, Greece · 2017 – 2018
-
Taught web programming (HTML, CSS, jQuery) to refugee and local youth through the EU-funded Code + Create initiative.
-
- -
-
Tactical Tech Collective
-
Full-Stack Web Architect · Berlin, Germany · Dec 2014 – Jun 2016
-
Built investigative data tools and platforms under the Exposing the Invisible team (Ruby on Rails, data visualisation).
-
- -
-
Trommsdorff + Drüner
-
Full-Stack Engineer · Berlin, Germany
-
Innovation team — Business Intelligence dashboards for marketing consultancy clients.
-
- -
-
Cortex Intelligence
-
Developer · Rio de Janeiro, Brazil
-
Competitive Intelligence and Data Mining R&D team.
-
-
- -
-

E D U C A T I O N

-
Postgraduate in Data Science for FinanceNOVA IMS Information Management School, Lisbon — 2021
-
Master in Information SystemsPPGI/UNIRIO — Knowledge Representation & Reasoning — 2010–2012
-
Bachelor in Computer ScienceMetodista Bennett, Rio de Janeiro — 2004–2009
-
- -
-

L A N G U A G E S

-
Portuguesenative
-
Englishfull professional
-
Spanishfull professional
-
GermanB1 — TELC certified · 3 yrs Berlin + 3 yrs Basel
-
- -
-

C E R T I F I C A T I O N S

-
    -
  • Swiss Federal Act on Data Protection (nFADP)
  • -
  • Gen AI Foundational Models — NLP & LLMs
  • -
  • AI Agents using RAG and LangChain
  • -
-
- -
-

T E C H S K I L L S

-
Languages & FrameworksPython · Ruby on Rails · TypeScript · React · Java
-
DevOps & InfraDocker · Kubernetes · CI/CD · AWS · Linux · Git
-
Data & AISQL · ETL · Data Engineering · RAG · LangChain · LLM integrations · Data Science
-
ArchitectureREST APIs · MVC · OOP · UML · System Design · Web Crawling & Scraping
-
- -
- References available upon request · CV updated May 2026 -
- - \ No newline at end of file diff --git a/cv/old/polo-new-cv.html b/cv/old/polo-new-cv.html deleted file mode 100644 index c1f6460..0000000 --- a/cv/old/polo-new-cv.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - Rafael Polo - CV - - - -
-
-

Rafael Polo

-

Senior Software Engineer — Platform, Data & AI

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Personal
Address:Basel, 4055, Switzerland
Email:rafael@extrapolo.com
LinkedIn:linkedin.com/in/rafaelpolo
Web:extrapolo.com
Citizenship:Portuguese (EU) · Brazilian — no work permit required
Languages:Fluent English · German B1 TELC (3 yrs Berlin + 3 yrs Basel) · EU citizen, immediately available
- -

Formal Education

-
    -
  • Postgraduate in Data Science for Finance – 2021
    - NOVA IMS Information Management School, Lisbon
  • -
  • Master in Information Systems – 2010–2012
    - Research Area: Knowledge Representation & Reasoning, PPGI/UNIRIO
  • -
  • Bachelor in Computer Science – 2004–2009
    - Metodista Bennett, Rio de Janeiro
  • -
  • Thesis: Agile Systems Development for Web 2.0 with Ruby on Rails
  • -
- -

Relevant Professional Experience

-
    -
  • Swisscom – 2024–2025 – Zürich, Switzerland -

    DevOps Engineer developing platforms for Telecommunication Operations.

  • -
  • EDGE Empower® – 2024–2025 – Baar, Switzerland -

    Senior Technical Lead managing projects and external UX & Dev Teams for a SaaS Company.

  • -
  • ExtraPolo — Independent Consulting – 2010–2022 – Rio de Janeiro · Berlin · Athens · Lisbon -

    For more than 10 years, worked as an independent Software Developer and IT Consultant, developing dozens of Projects for public and private contractors in Brazil, Germany, Greece and Switzerland.

  • -
  • Moedax Digital Services – 2018–2021 – Lisbon and Rio de Janeiro -

    Founder & CTO – connected Brazilian CBDC with KYC processes and automation

  • -
  • GFOSS.eu / Code + Create – 2017–2018 – Athens, Greece -

    Web Programming Teacher – taught web programming to refugee and local youth

  • -
  • Tactical Tech Collective – 2014–2016 – Berlin, Germany -

    Full-Stack Web Architect on an NGO under the Exposing the Invisible Team.

  • -
  • Trommsdorf + Drüner – 2013–2014 – Berlin, Germany -

    Full-stack Engineer – Innovation Team developing Business Intelligence dashboards.

  • -
  • Cortex Intelligence – 2007–2008 – Rio de Janeiro, Brazil -

    Developer in the Competitive Intelligence and Data Mining Team.

  • -
- -

Certifications

-
    -
  • Swiss Federal Act on Data Protection (nFADP)
  • -
  • Gen AI Foundational Models — NLP & LLMs
  • -
  • AI Agents using RAG and LangChain
  • -
- -

Relevant Tech Skills

-
    -
  • System Analysis & Modeling with UML
  • -
  • Quick Proof-of-Concepts development, deploy and management.
  • -
  • Large Language Models, Gen AI, LangChain integration and Processes Automation
  • -
  • MVC System Architecture, Design Patterns & Object-Oriented Programming
  • -
  • Data Engineering, Data Science, Relational Databases & SQL
  • -
  • Experience with Blockchain, Smart Contracts and P2P systems
  • -
  • Distributing and Scaling Processes with Message Queues
  • -
  • Developing Web Crawlers & Bots for Data Mining & ETL
  • -
  • Linux Systems, VPS, DevOps, SSH & Deploy with Continuous Integration processes
  • -
  • Projects mostly developed in Python, Ruby on Rails, Django, Java
  • -
  • Full Stack Developer & Front-end Web Design with HTML, CSS and JavaScript frameworks
  • -
  • CI/CD, Docker, Kubernetes and GitHub workflows
  • -
- -

Natural Languages

-
    -
  • Native Brazilian Portuguese
  • -
  • Advanced Spoken Spanish
  • -
  • A2 level Greek – 3 years in Athens
  • -
  • B1 level German / TELC proof – 3 years in Berlin and 3 in Switzerland
  • -
  • Fluent English – 13 years in between international teams
  • -
- - -
-
- -
-
- - \ No newline at end of file diff --git a/cv/old/polo-new-cv.odt b/cv/old/polo-new-cv.odt deleted file mode 100644 index e25f172..0000000 Binary files a/cv/old/polo-new-cv.odt and /dev/null differ diff --git a/cv/old/polo-new-cv.pdf b/cv/old/polo-new-cv.pdf deleted file mode 100644 index 4c6bdb2..0000000 Binary files a/cv/old/polo-new-cv.pdf and /dev/null differ diff --git a/cv/old/rafael-polo-cv-2026-senior.odt b/cv/old/rafael-polo-cv-2026-senior.odt deleted file mode 100644 index 02656e4..0000000 Binary files a/cv/old/rafael-polo-cv-2026-senior.odt and /dev/null differ diff --git a/cv/old/rafael-polo-cv-2026-senior.pdf b/cv/old/rafael-polo-cv-2026-senior.pdf deleted file mode 100644 index fac8f41..0000000 Binary files a/cv/old/rafael-polo-cv-2026-senior.pdf and /dev/null differ diff --git a/cv/old/rafael-polo-cv.pdf b/cv/old/rafael-polo-cv.pdf deleted file mode 100644 index 35c207c..0000000 Binary files a/cv/old/rafael-polo-cv.pdf and /dev/null differ diff --git a/cv/polo.md b/cv/polo.md deleted file mode 100644 index 2dda270..0000000 --- a/cv/polo.md +++ /dev/null @@ -1,130 +0,0 @@ -Rafael Polo - -Basel, 4055, Switzerland - -rafael@extrapolo.com - -linkedin.com/in/rafaelpolo - -extrapolo.com - -Senior Software Engineer — Platform, Data & AI - -Swiss B permit · Portuguese (EU) citizen - -20+ years building production systems across Switzerland, Germany, Greece and Brazil. Specialised in platform - -engineering, data pipelines and AI/LLM integrations. Fluent English · German B1 TELC (3 yrs Berlin + 3 yrs Basel) · EU - -citizen, immediately available. - -P R O F E S S I O N A L E X P E R I E N C E - -Swisscom -DevOps Engineer · Zürich, Switzerland - -Modernization of mission-critical internal platforms & CI/CD pipelines for high-availability Telecom infrastructure. - -EDGE Empower® - -Senior Technical Lead · Baar, Switzerland - -Mar 2024 – Mar 2025 - -Feb 2023 – Feb 2024 - -Built a PayTool cutting processing time by 98%; reduced CI/CD pipeline from 14 → 4 minutes; managed 3 developers and a UX - -designer for a SaaS product. - -ExtraPolo — Independent Consulting -Software Engineer & Consultant · Rio de Janeiro · Berlin · Athens · Lisbon - -2010 – 2022 - -40+ projects across civic tech, data journalism, open-source platforms and interactive installations for public and private clients. Includes Rodado (~730 Brazilian public datasets, 1TB Parquet, DuckDB, NL-to-SQL over MCP) and HelvetiScan (scanner/mapper of 2.5M+ Swiss .ch domains — HTTP, DNS, TLS, WHOIS, CVE). - -Moedax Digital Services - -Founder & CTO · Lisbon · Rio de Janeiro - -2018 – 2021 - -Fintech startup connecting CBDC digital currency with KYC compliance automation; built data pipelines and banking integrations in -Python. - -GFOSS.eu / Code + Create -Web Programming Teacher · Athens, Greece - -2017 – 2018 - -Taught web programming (HTML, CSS, jQuery) to refugee and local youth through the EU-funded Code + Create initiative. - -Tactical Tech Collective -Full-Stack Web Architect · Berlin, Germany - -Dec 2014 – Jun 2016 - -Built investigative data tools and platforms under the Exposing the Invisible team (Ruby on Rails, data visualisation). - -Trommsdorff + Drüner - -Full-Stack Engineer · Berlin, Germany - -Innovation team — Business Intelligence dashboards for marketing consultancy clients. - -Cortex Intelligence -Developer · Rio de Janeiro, Brazil - -Competitive Intelligence and Data Mining R&D team. - -E D U C A T I O N - -L A N G U A G E S - -Dec 2013 – Jan 2014 - -2008 – 2010 - -Postgraduate in Data Science for Finance -NOVA IMS Information Management School, Lisbon — 2021 - -Master in Information Systems -PPGI/UNIRIO — Knowledge Representation & Reasoning — -2010–2012 - -Bachelor in Computer Science -Metodista Bennett, Rio de Janeiro — 2004–2009 - -Portuguese — native - -English — full professional - -Spanish — full professional - -German B1 — TELC certified · 3 yrs Berlin + 3 yrs Basel - -C E R T I F I C A T I O N S - -Swiss Federal Act on Data Protection (nFADP) - -Gen AI Foundational Models — NLP & LLMs - -AI Agents using RAG and LangChain - -T E C H S K I L L S - - Languages & Frameworks · Python · Ruby on Rails · -TypeScript · React - -DevOps & Infra · Docker · Kubernetes · CI/CD · AWS · Linux · -Git - -Data & AI · SQL · ETL · Data Engineering · RAG · LangChain · -LLM integrations · Data Science - -Architecture · REST APIs · MVC · OOP · UML · System Design · -Web Crawling & Scraping - -References available upon request  ·  CV updated May 2026 - diff --git a/scripts/watch-delivery-lead.ts b/scripts/watch-delivery-lead.ts new file mode 100644 index 0000000..d0ebfbe --- /dev/null +++ b/scripts/watch-delivery-lead.ts @@ -0,0 +1,177 @@ +// Daily watch for the "Delivery Lead – Agentic Solutions" opening (Biomedical +// Research Digital & Data, likely Novartis) that MRP-Global posted on LinkedIn +// as job 4473851998 on 2026-10-03. Looks for the same role resurfacing in +// another form — another agency, a direct Novartis posting, a retitled +// repost — and reports when the original stops accepting applications. +// +// Runs from launchd (com.rafapolo.linkme-watch-delivery-lead, daily 10:00). +// Pushes to Telegram only when something new turns up; everything else goes to +// results/watch-delivery-lead.log. State lives in results/.watch-delivery-lead.json, +// so a hit is reported once. `--dry-run` prints instead of sending. + +import { fetchDescription } from "../fetch.ts"; +import { send, escapeHtml } from "../telegram.ts"; + +const ORIGINAL_ID = "4473851998"; +const STATE_FILE = "results/.watch-delivery-lead.json"; +const DRY_RUN = process.argv.includes("--dry-run"); + +const UA = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"; + +// Searches cast wide; SIGNATURE decides. A repost of this opening reuses the +// client's wording, so a candidate needs one client-specific phrase (STRONG) +// plus one role phrase. "agentic" + "delivery lead" alone matched Eraneos and +// AWS on the first dry run (2026-10-05) — generic consultancy wording. +const LINKEDIN_QUERIES = [ + "delivery lead agentic", + "delivery lead AI data pharma", + "delivery lead biomedical", + "agentic solutions delivery", + "biomedical research digital data", + "delivery manager agentic", +]; +const NOVARTIS_QUERIES = ["delivery lead", "delivery manager", "agentic", "biomedical research digital"]; + +const SIGNATURE: [string, RegExp][] = [ + ["agentic", /\bagentic\b/i], + ["Biomedical Research", /biomedical\s+research/i], + ["Digital & Data", /digital\s*(?:&|and)\s*data/i], + ["delivery lead", /delivery\s+lead/i], + ["Palantir", /\bpalantir\b/i], + ["Databricks", /\bdatabricks\b/i], + ["RWD", /\bRWD\b|real[-\s]world\s+data|clinical\s+trials?/i], +]; +const STRONG = new Set(["Biomedical Research", "Digital & Data", "Palantir", "RWD"]); +const ROLE = new Set(["agentic", "delivery lead"]); +const isMatch = (m: string[]) => m.some((x) => STRONG.has(x)) && m.some((x) => ROLE.has(x)); +const TITLE_RE = /deliver|agentic|programme?\s+lead|project\s+lead/i; + +interface Hit { key: string; title: string; company: string; link: string; matched: string[] } +interface State { seen: string[]; reported: string[]; originalClosed?: boolean } + +async function loadState(): Promise { + const f = Bun.file(STATE_FILE); + return (await f.exists()) ? await f.json() : { seen: [], reported: [] }; +} + +function signature(text: string): string[] { + return SIGNATURE.filter(([, re]) => re.test(text)).map(([name]) => name); +} + +const strip = (s: string) => + s.replace(/<[^>]+>/g, " ").replace(/&/g, "&").replace(/'|'/g, "'").replace(/\s+/g, " ").trim(); + +async function get(url: string): Promise { + const res = await fetch(url, { headers: { "User-Agent": UA } }); + if (!res.ok) throw new Error(`${res.status} ${url}`); + return res.text(); +} + +async function searchLinkedIn(state: State): Promise { + const cards = new Map(); + for (const q of LINKEDIN_QUERIES) { + // Past month, Switzerland-wide. One page is enough: these queries are narrow. + const url = `https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=${encodeURIComponent(q)}&location=Switzerland&f_TPR=r2592000&start=0`; + try { + const html = await get(url); + for (const c of html.split("
  • ").slice(1)) { + const id = c.match(/jobs\/view\/[^"]*?(\d{8,})/)?.[1]; + const title = strip(c.match(/base-search-card__title[^>]*>([\s\S]*?)<\/h3>/)?.[1] ?? ""); + const company = strip(c.match(/base-search-card__subtitle[^>]*>([\s\S]*?)<\/h4>/)?.[1] ?? ""); + if (id && id !== ORIGINAL_ID && TITLE_RE.test(title)) cards.set(id, { title, company }); + } + } catch (e) { + console.log(` linkedin "${q}": ${e}`); + } + await Bun.sleep(1500); + } + + const hits: Hit[] = []; + for (const [id, { title, company }] of cards) { + const key = `li-${id}`; + if (state.seen.includes(key)) continue; + const desc = await fetchDescription(id); + if (desc === null) continue; // unreadable — retry tomorrow rather than mark seen + state.seen.push(key); + const matched = signature(`${title} ${desc}`); + console.log(` li ${id} ${title} @ ${company}: [${matched.join(", ")}]`); + if (isMatch(matched)) { + hits.push({ key, title, company, link: `https://www.linkedin.com/jobs/view/${id}/`, matched }); + } + } + return hits; +} + +async function searchNovartis(state: State): Promise { + const found = new Map(); + for (const q of NOVARTIS_QUERIES) { + const url = `https://www.novartis.com/careers/career-search?search_api_fulltext=${encodeURIComponent(q)}&country%5B%5D=LOC_CH`; + try { + const html = await get(url); + for (const m of html.matchAll(/href="(\/careers\/career-search\/job\/details\/(req-\d+)[^"]*)"[^>]*>([\s\S]*?)<\/a>/g)) { + const title = strip(m[3]); + if (TITLE_RE.test(title)) found.set(m[2], { title, path: m[1] }); + } + } catch (e) { + console.log(` novartis "${q}": ${e}`); + } + } + + const hits: Hit[] = []; + for (const [req, { title, path }] of found) { + const key = `nv-${req}`; + if (state.seen.includes(key)) continue; + const link = `https://www.novartis.com${path}`; + let desc: string; + try { + desc = strip(await get(link)); + } catch { + continue; + } + state.seen.push(key); + const matched = signature(`${title} ${desc}`); + console.log(` nv ${req} ${title}: [${matched.join(", ")}]`); + if (isMatch(matched)) hits.push({ key, title, company: "Novartis", link, matched }); + } + return hits; +} + +async function originalClosed(): Promise { + try { + const html = await get(`https://www.linkedin.com/jobs/view/${ORIGINAL_ID}/`); + return /no longer accepting applications/i.test(html); + } catch { + return null; + } +} + +const state = await loadState(); +console.log(`[${new Date().toISOString()}] watch-delivery-lead`); + +const hits = [...(await searchLinkedIn(state)), ...(await searchNovartis(state))].filter( + (h) => !state.reported.includes(h.key), +); +const closed = await originalClosed(); + +const lines: string[] = []; +for (const h of hits) { + lines.push( + `• ${escapeHtml(h.title)} — ${escapeHtml(h.company)}\n ${h.link}\n matched: ${escapeHtml(h.matched.join(", "))}`, + ); +} +if (closed === true && !state.originalClosed) { + lines.push(`• The original MRP-Global posting (${ORIGINAL_ID}) is no longer accepting applications.`); +} + +if (lines.length) { + const msg = `🔎 Delivery Lead – Agentic Solutions watch\n\n${lines.join("\n\n")}`; + if (DRY_RUN) console.log(msg); + else await send(msg); +} + +// Only mark as reported after a successful send, same rule as daily.ts. +state.reported.push(...hits.map((h) => h.key)); +if (closed === true) state.originalClosed = true; +if (!DRY_RUN) await Bun.write(STATE_FILE, JSON.stringify(state, null, 2)); +console.log(` ${hits.length} new hit(s), original closed: ${closed}`);