chore: move cv and job applications to private rafapolo/job, add delivery-lead watcher
cv/ and jobs/ become symlinks into ../rafapolo/job (gitignored there). scripts/watch-delivery-lead.ts runs daily via launchd and alerts on Telegram when the MRP-Global agentic Delivery Lead role resurfaces or closes.
This commit is contained in:
+3
-5
@@ -4,9 +4,7 @@ node_modules/
|
|||||||
|
|
||||||
# job data and personal notes
|
# job data and personal notes
|
||||||
results/
|
results/
|
||||||
jobs/
|
# symlinks into the private ../rafapolo/job/ (gitignored there too)
|
||||||
|
jobs
|
||||||
|
cv
|
||||||
|
|
||||||
# CV files (large binaries + personal)
|
|
||||||
cv/*.pdf
|
|
||||||
cv/*.odt
|
|
||||||
cv/*.html
|
|
||||||
|
|||||||
@@ -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/ # one-off search scripts; import from ../config.ts and ../fetch.ts
|
||||||
scripts/scicore-watch.sh # daily launchd watch on sciCORE's open-positions page
|
scripts/scicore-watch.sh # daily launchd watch on sciCORE's open-positions page
|
||||||
results/sent.txt # links already pushed to Telegram — never sent twice
|
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/polo.md # Rafael's profile — use for ranking jobs
|
||||||
cv/ # CV files (html, pdf, odt)
|
|
||||||
results/jobs.csv # output — one row per job
|
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
|
plans/ # LinkedIn post drafts
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -178,6 +178,12 @@ Extra filters for temp scripts (import from `../fetch.ts`):
|
|||||||
- `isEnglishContent(id)` — drop DE/FR description content
|
- `isEnglishContent(id)` — drop DE/FR description content
|
||||||
- Add your own inline RE for EMEA, .NET, etc.
|
- 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/<company-slug>/` (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/<company-slug>/`.
|
||||||
|
|
||||||
## Ranking jobs against the CV
|
## Ranking jobs against the CV
|
||||||
|
|
||||||
Read `cv/polo.md` for the full profile. Key signals:
|
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 |
|
| 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-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 <plist>` to stop it.
|
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 <plist>` to stop it.
|
||||||
|
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Rafael Polo - CV</title>
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
size: 8.2681in 11.6929in;
|
|
||||||
margin: 0.5in;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Calibri, Liberation Sans, sans-serif;
|
|
||||||
font-size: 11pt;
|
|
||||||
line-height: 1.2;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.left-col {
|
|
||||||
display: table-cell;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 24pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
font-size: 11pt;
|
|
||||||
font-style: italic;
|
|
||||||
margin-bottom: 0.15in;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
table.personal {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-bottom: 0.2in;
|
|
||||||
}
|
|
||||||
table.personal td {
|
|
||||||
padding: 0.02in 0.05in;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
table.personal .label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
width: 1in;
|
|
||||||
}
|
|
||||||
table.personal .header-row td {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 11pt;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
padding-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 11pt;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
margin: 0.15in 0 0.08in 0;
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
padding-bottom: 0.03in;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin: 0.05in 0;
|
|
||||||
padding-left: 0.15in;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.04in;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.job-item {
|
|
||||||
margin-bottom: 0.08in;
|
|
||||||
}
|
|
||||||
.job-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.job-meta {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 9pt;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.job-desc {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 10pt;
|
|
||||||
color: #444;
|
|
||||||
margin-left: 0.1in;
|
|
||||||
}
|
|
||||||
.edu-item {
|
|
||||||
margin-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
.edu-degree {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.edu-school {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.skill-item {
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-bottom: 0.02in;
|
|
||||||
}
|
|
||||||
.lang-item {
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-bottom: 0.02in;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
font-size: 9pt;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 0.2in;
|
|
||||||
padding-top: 0.1in;
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="title">Rafael Polo</div>
|
|
||||||
<div class="subtitle">Senior Software Engineer — Platform, Data & AI</div>
|
|
||||||
|
|
||||||
<table class="personal">
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="header-row">Personal</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">First name:</td>
|
|
||||||
<td>Rafael</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Last name:</td>
|
|
||||||
<td>Polo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Citizenship:</td>
|
|
||||||
<td>Portuguese (EU) · Brazilian — no work permit required</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Address:</td>
|
|
||||||
<td>Basel, 4055, Switzerland</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Email:</td>
|
|
||||||
<td>rafael@extrapolo.com</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Web:</td>
|
|
||||||
<td>linkedin.com/in/rafaelpolo<br>extrapolo.com</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h3>Formal Education</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="edu-item">
|
|
||||||
<span class="edu-degree"><em><strong>Postgraduate in Data Science for Finance</strong></em> – 2021</span><br>
|
|
||||||
<span class="edu-school">NOVA IMS Information Management School, Lisbon</span>
|
|
||||||
</li>
|
|
||||||
<li class="edu-item">
|
|
||||||
<span class="edu-degree"><em><strong>Master in Information Systems</strong></em> – 2010–2012</span><br>
|
|
||||||
<span class="edu-school">PPGI/UNIRIO — Knowledge Representation & Reasoning</span>
|
|
||||||
</li>
|
|
||||||
<li class="edu-item">
|
|
||||||
<span class="edu-degree"><em><strong>Bachelor in Computer Science</strong></em> – 2004–2009</span><br>
|
|
||||||
<span class="edu-school">Metodista Bennett, Rio de Janeiro</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Relevant Professional Experience</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">Swisscom</span> – <span class="job-meta">2024–2025 – Zürich, Switzerland</span><br>
|
|
||||||
<span class="job-desc"><strong>DevOps Engineer</strong> developing platforms for Telecommunication Operations.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">EDGE Empower®</span> – <span class="job-meta">2024–2025 – Baar, Switzerland</span><br>
|
|
||||||
<span class="job-desc"><strong>Senior Technical Lead</strong> managing projects and external UX & Dev Teams for a SaaS Product.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">ExtraPolo — Independent Consulting</span> – <span class="job-meta">2010–2022 – Rio de Janeiro · Berlin · Athens · Lisbon</span><br>
|
|
||||||
<span class="job-desc">40+ projects across civic tech, data journalism, open-source platforms and interactive installations for public and private clients.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">Moedax Digital Services</span> – <span class="job-meta">2018–2021 – Lisbon · Rio de Janeiro</span><br>
|
|
||||||
<span class="job-desc"><strong>Founder & CTO</strong> – Fintech startup connecting CBDC digital currency with KYC compliance automation.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">GFOSS.eu / Code + Create</span> – <span class="job-meta">2017–2018 – Athens, Greece</span><br>
|
|
||||||
<span class="job-desc"><strong>Web Programming Teacher</strong> – taught HTML, CSS, jQuery to refugee and local youth.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">Tactical Tech Collective</span> – <span class="job-meta">2014–2016 – Berlin, Germany</span><br>
|
|
||||||
<span class="job-desc"><strong>Full-Stack Web Architect</strong> – investigative data tools under Exposing the Invisible team.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">Trommsdorf + Drüner</span> – <span class="job-meta">2013–2014 – Berlin, Germany</span><br>
|
|
||||||
<span class="job-desc"><strong>Full-Stack Engineer</strong> – Innovation Team developing Business Intelligence dashboards.</span>
|
|
||||||
</li>
|
|
||||||
<li class="job-item">
|
|
||||||
<span class="job-title">Cortex Intelligence</span> – <span class="job-meta">2007–2008 – Rio de Janeiro, Brazil</span><br>
|
|
||||||
<span class="job-desc"><strong>Developer</strong> – Competitive Intelligence and Data Mining R&D team.</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Certifications</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="skill-item">Swiss Federal Act on Data Protection (nFADP)</li>
|
|
||||||
<li class="skill-item">Gen AI Foundational Models — NLP & LLMs</li>
|
|
||||||
<li class="skill-item">AI Agents using RAG and LangChain</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Relevant Tech Skills</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="skill-item"><strong>Languages & Frameworks:</strong> Python · Ruby on Rails · TypeScript · React · Java</li>
|
|
||||||
<li class="skill-item"><strong>DevOps & Infra:</strong> Docker · Kubernetes · CI/CD · AWS · Linux · Git</li>
|
|
||||||
<li class="skill-item"><strong>Data & AI:</strong> SQL · ETL · Data Engineering · RAG · LangChain · LLM integrations</li>
|
|
||||||
<li class="skill-item"><strong>Architecture:</strong> REST APIs · MVC · OOP · UML · System Design · Web Crawling</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Natural Languages</h3>
|
|
||||||
<ul>
|
|
||||||
<li class="lang-item">Native Brazilian Portuguese</li>
|
|
||||||
<li class="lang-item">Full professional English</li>
|
|
||||||
<li class="lang-item">Full professional Spanish</li>
|
|
||||||
<li class="lang-item">German B1 – TELC certified · 3 yrs Berlin + 3 yrs Basel</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
References available upon request · CV updated May 2026
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Rafael Polo - CV</title>
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
size: 8.2681in 11.6929in;
|
|
||||||
margin: 0.5in;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Liberation Sans, Carlito, Georgia, serif;
|
|
||||||
font-size: 11pt;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 36pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.1in;
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
font-size: 14pt;
|
|
||||||
margin-bottom: 0.3in;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 24pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0.2in 0 0.1in 0;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
padding-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 18pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0.15in 0 0.08in 0;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 14pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0.1in 0 0.05in 0;
|
|
||||||
}
|
|
||||||
.section {
|
|
||||||
margin-bottom: 0.15in;
|
|
||||||
}
|
|
||||||
.personalinline {
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-bottom: 0.2in;
|
|
||||||
}
|
|
||||||
.personalinline span {
|
|
||||||
margin-right: 0.3in;
|
|
||||||
}
|
|
||||||
.key-value {
|
|
||||||
display: flex;
|
|
||||||
margin: 0.03in 0;
|
|
||||||
}
|
|
||||||
.key {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 1.5in;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin: 0.05in 0;
|
|
||||||
padding-left: 0.2in;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.03in;
|
|
||||||
}
|
|
||||||
.small {
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.experience-item {
|
|
||||||
margin-bottom: 0.1in;
|
|
||||||
}
|
|
||||||
.exp-header {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.exp-meta {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.exp-desc {
|
|
||||||
margin-left: 0.2in;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="title">Rafael Polo</div>
|
|
||||||
<div class="subtitle">Senior Software Engineer — Platform, Data & AI</div>
|
|
||||||
|
|
||||||
<div class="personalinline">
|
|
||||||
<span>Basel, 4055, Switzerland</span>
|
|
||||||
<span>rafael@extrapolo.com</span>
|
|
||||||
<span>linkedin.com/in/rafaelpolo</span>
|
|
||||||
<span>extrapolo.com</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="personalinline">
|
|
||||||
<span>Portuguese (EU) · Brazilian — no work permit required</span>
|
|
||||||
<span>20+ years building production systems across Switzerland, Germany, Greece and Brazil</span>
|
|
||||||
<span>Fluent English · German B1 TELC (3 yrs Berlin + 3 yrs Basel) · EU citizen, immediately available</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h1>P R O F E S S I O N A L E X P E R I E N C E</h1>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">Swisscom</div>
|
|
||||||
<div class="exp-meta">DevOps Engineer · Zürich, Switzerland</div>
|
|
||||||
<div class="exp-desc">Modernization of mission-critical internal platforms & CI/CD pipelines for high-availability Telecom infrastructure.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">EDGE Empower®</div>
|
|
||||||
<div class="exp-meta">Senior Technical Lead · Baar, Switzerland · 2024 – 2025</div>
|
|
||||||
<div class="exp-desc">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.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">ExtraPolo — Independent Consulting</div>
|
|
||||||
<div class="exp-meta">Software Engineer & Consultant · Rio de Janeiro · Berlin · Athens · Lisbon · 2010 – 2022</div>
|
|
||||||
<div class="exp-desc">40+ projects across civic tech, data journalism, open-source platforms and interactive installations for public and private clients.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">Moedax Digital Services</div>
|
|
||||||
<div class="exp-meta">Founder & CTO · Lisbon · Rio de Janeiro · 2018 – 2021</div>
|
|
||||||
<div class="exp-desc">Fintech startup connecting CBDC digital currency with KYC compliance automation; built data pipelines and banking integrations in Python.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">GFOSS.eu / Code + Create</div>
|
|
||||||
<div class="exp-meta">Web Programming Teacher · Athens, Greece · 2017 – 2018</div>
|
|
||||||
<div class="exp-desc">Taught web programming (HTML, CSS, jQuery) to refugee and local youth through the EU-funded Code + Create initiative.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">Tactical Tech Collective</div>
|
|
||||||
<div class="exp-meta">Full-Stack Web Architect · Berlin, Germany · Dec 2014 – Jun 2016</div>
|
|
||||||
<div class="exp-desc">Built investigative data tools and platforms under the Exposing the Invisible team (Ruby on Rails, data visualisation).</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">Trommsdorff + Drüner</div>
|
|
||||||
<div class="exp-meta">Full-Stack Engineer · Berlin, Germany</div>
|
|
||||||
<div class="exp-desc">Innovation team — Business Intelligence dashboards for marketing consultancy clients.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="exp-header">Cortex Intelligence</div>
|
|
||||||
<div class="exp-meta">Developer · Rio de Janeiro, Brazil</div>
|
|
||||||
<div class="exp-desc">Competitive Intelligence and Data Mining R&D team.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h1>E D U C A T I O N</h1>
|
|
||||||
<div class="key-value"><span class="key">Postgraduate in Data Science for Finance</span><span class="value">NOVA IMS Information Management School, Lisbon — 2021</span></div>
|
|
||||||
<div class="key-value"><span class="key">Master in Information Systems</span><span class="value">PPGI/UNIRIO — Knowledge Representation & Reasoning — 2010–2012</span></div>
|
|
||||||
<div class="key-value"><span class="key">Bachelor in Computer Science</span><span class="value">Metodista Bennett, Rio de Janeiro — 2004–2009</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h1>L A N G U A G E S</h1>
|
|
||||||
<div class="key-value"><span class="key">Portuguese</span><span class="value">native</span></div>
|
|
||||||
<div class="key-value"><span class="key">English</span><span class="value">full professional</span></div>
|
|
||||||
<div class="key-value"><span class="key">Spanish</span><span class="value">full professional</span></div>
|
|
||||||
<div class="key-value"><span class="key">German</span><span class="value">B1 — TELC certified · 3 yrs Berlin + 3 yrs Basel</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h1>C E R T I F I C A T I O N S</h1>
|
|
||||||
<ul>
|
|
||||||
<li>Swiss Federal Act on Data Protection (nFADP)</li>
|
|
||||||
<li>Gen AI Foundational Models — NLP & LLMs</li>
|
|
||||||
<li>AI Agents using RAG and LangChain</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h1>T E C H S K I L L S</h1>
|
|
||||||
<div class="key-value"><span class="key">Languages & Frameworks</span><span class="value">Python · Ruby on Rails · TypeScript · React · Java</span></div>
|
|
||||||
<div class="key-value"><span class="key">DevOps & Infra</span><span class="value">Docker · Kubernetes · CI/CD · AWS · Linux · Git</span></div>
|
|
||||||
<div class="key-value"><span class="key">Data & AI</span><span class="value">SQL · ETL · Data Engineering · RAG · LangChain · LLM integrations · Data Science</span></div>
|
|
||||||
<div class="key-value"><span class="key">Architecture</span><span class="value">REST APIs · MVC · OOP · UML · System Design · Web Crawling & Scraping</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="small">
|
|
||||||
References available upon request · CV updated May 2026
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Rafael Polo - CV</title>
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
size: 8.2681in 11.6929in;
|
|
||||||
margin: 0.5in;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Liberation Sans, Carlito, Calibri, Georgia, sans-serif;
|
|
||||||
font-size: 11pt;
|
|
||||||
line-height: 1.3;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.left-col {
|
|
||||||
display: table-cell;
|
|
||||||
width: 65%;
|
|
||||||
padding-right: 0.2in;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.right-col {
|
|
||||||
display: table-cell;
|
|
||||||
width: 35%;
|
|
||||||
vertical-align: top;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.photo {
|
|
||||||
width: 2in;
|
|
||||||
height: auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 24pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
font-size: 12pt;
|
|
||||||
font-style: italic;
|
|
||||||
margin-bottom: 0.2in;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 12pt;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0.15in 0 0.08in 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05in;
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
padding-bottom: 0.03in;
|
|
||||||
}
|
|
||||||
.personal-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.personal-table td {
|
|
||||||
padding: 0.02in 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.personal-table .label {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 1.2in;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.personal-table .header {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12pt;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05in;
|
|
||||||
padding-bottom: 0.05in;
|
|
||||||
}
|
|
||||||
.section-header {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 11pt;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.03in;
|
|
||||||
margin-top: 0.15in;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin: 0.05in 0;
|
|
||||||
padding-left: 0.15in;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.04in;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.job-title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.job-meta {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 10pt;
|
|
||||||
}
|
|
||||||
.job-company {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.job-desc {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-left: 0.1in;
|
|
||||||
}
|
|
||||||
.edu-item {
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-bottom: 0.03in;
|
|
||||||
}
|
|
||||||
.edu-school {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.skill-category {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-top: 0.05in;
|
|
||||||
}
|
|
||||||
.skill-list {
|
|
||||||
font-size: 10pt;
|
|
||||||
margin-left: 0.1in;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
font-size: 9pt;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 0.3in;
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
padding-top: 0.1in;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="left-col">
|
|
||||||
<p class="title">Rafael Polo</p>
|
|
||||||
<p class="subtitle">Senior Software Engineer — Platform, Data & AI</p>
|
|
||||||
|
|
||||||
<table class="personal-table">
|
|
||||||
<tr>
|
|
||||||
<td class="header" colspan="2">Personal</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Address:</td>
|
|
||||||
<td>Basel, 4055, Switzerland</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Email:</td>
|
|
||||||
<td>rafael@extrapolo.com</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">LinkedIn:</td>
|
|
||||||
<td>linkedin.com/in/rafaelpolo</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Web:</td>
|
|
||||||
<td>extrapolo.com</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Citizenship:</td>
|
|
||||||
<td>Portuguese (EU) · Brazilian — no work permit required</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="label">Languages:</td>
|
|
||||||
<td>Fluent English · German B1 TELC (3 yrs Berlin + 3 yrs Basel) · EU citizen, immediately available</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h3>Formal Education</h3>
|
|
||||||
<ul>
|
|
||||||
<li><em><strong>Postgraduate in Data Science for Finance</strong></em> – 2021<br>
|
|
||||||
<span class="edu-school">NOVA IMS Information Management School, Lisbon</span></li>
|
|
||||||
<li><em><strong>Master in Information Systems</strong></em> – 2010–2012<br>
|
|
||||||
<span class="edu-school">Research Area: Knowledge Representation & Reasoning, PPGI/UNIRIO</span></li>
|
|
||||||
<li><em><strong>Bachelor in Computer Science</strong></em> – 2004–2009<br>
|
|
||||||
<span class="edu-school">Metodista Bennett, Rio de Janeiro</span></li>
|
|
||||||
<li>Thesis: Agile Systems Development for Web 2.0 with Ruby on Rails</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Relevant Professional Experience</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Swisscom</strong> – <em>2024–2025</em> – <em>Zürich, Switzerland</em>
|
|
||||||
<p class="job-desc"><strong>DevOps Engineer</strong> developing platforms for Telecommunication Operations.</p></li>
|
|
||||||
<li><strong>EDGE Empower®</strong> – <em>2024–2025</em> – <em>Baar, Switzerland</em>
|
|
||||||
<p class="job-desc"><strong>Senior Technical Lead</strong> managing projects and external UX & Dev Teams for a SaaS Company.</p></li>
|
|
||||||
<li><strong>ExtraPolo — Independent Consulting</strong> – <em>2010–2022</em> – <em>Rio de Janeiro · Berlin · Athens · Lisbon</em>
|
|
||||||
<p class="job-desc">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.</p></li>
|
|
||||||
<li><strong>Moedax Digital Services</strong> – <em>2018–2021</em> – <em>Lisbon and Rio de Janeiro</em>
|
|
||||||
<p class="job-desc"><strong>Founder & CTO</strong> – connected Brazilian CBDC with KYC processes and automation</p></li>
|
|
||||||
<li><strong>GFOSS.eu / Code + Create</strong> – <em>2017–2018</em> – <em>Athens, Greece</em>
|
|
||||||
<p class="job-desc"><strong>Web Programming Teacher</strong> – taught web programming to refugee and local youth</p></li>
|
|
||||||
<li><strong>Tactical Tech Collective</strong> – <em>2014–2016</em> – <em>Berlin, Germany</em>
|
|
||||||
<p class="job-desc"><strong>Full-Stack Web Architect</strong> on an NGO under the Exposing the Invisible Team.</p></li>
|
|
||||||
<li><strong>Trommsdorf + Drüner</strong> – <em>2013–2014</em> – <em>Berlin, Germany</em>
|
|
||||||
<p class="job-desc"><strong>Full-stack Engineer</strong> – Innovation Team developing Business Intelligence dashboards.</p></li>
|
|
||||||
<li><strong>Cortex Intelligence</strong> – <em>2007–2008</em> – <em>Rio de Janeiro, Brazil</em>
|
|
||||||
<p class="job-desc"><strong>Developer</strong> in the Competitive Intelligence and Data Mining Team.</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Certifications</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Swiss Federal Act on Data Protection (nFADP)</li>
|
|
||||||
<li>Gen AI Foundational Models — NLP & LLMs</li>
|
|
||||||
<li>AI Agents using RAG and LangChain</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Relevant Tech Skills</h3>
|
|
||||||
<ul>
|
|
||||||
<li>System Analysis & Modeling with UML</li>
|
|
||||||
<li>Quick Proof-of-Concepts development, deploy and management.</li>
|
|
||||||
<li>Large Language Models, Gen AI, LangChain integration and Processes Automation</li>
|
|
||||||
<li>MVC System Architecture, Design Patterns & Object-Oriented Programming</li>
|
|
||||||
<li>Data Engineering, Data Science, Relational Databases & SQL</li>
|
|
||||||
<li>Experience with Blockchain, Smart Contracts and P2P systems</li>
|
|
||||||
<li>Distributing and Scaling Processes with Message Queues</li>
|
|
||||||
<li>Developing Web Crawlers & Bots for Data Mining & ETL</li>
|
|
||||||
<li>Linux Systems, VPS, DevOps, SSH & Deploy with Continuous Integration processes</li>
|
|
||||||
<li>Projects mostly developed in Python, Ruby on Rails, Django, Java</li>
|
|
||||||
<li>Full Stack Developer & Front-end Web Design with HTML, CSS and JavaScript frameworks</li>
|
|
||||||
<li>CI/CD, Docker, Kubernetes and GitHub workflows</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3>Natural Languages</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Native Brazilian Portuguese</li>
|
|
||||||
<li>Advanced Spoken Spanish</li>
|
|
||||||
<li>A2 level Greek – <em>3 years in Athens</em></li>
|
|
||||||
<li>B1 level German / TELC proof – <em>3 years in Berlin</em> and 3 in Switzerland</li>
|
|
||||||
<li>Fluent English – <em>13 years in between international teams</em></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
References available upon request · CV updated May 2026
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right-col">
|
|
||||||
<!-- Photo placeholder - remove if not needed -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-130
@@ -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
|
|
||||||
|
|
||||||
@@ -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<State> {
|
||||||
|
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<string> {
|
||||||
|
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<Hit[]> {
|
||||||
|
const cards = new Map<string, { title: string; company: string }>();
|
||||||
|
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("<li>").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<Hit[]> {
|
||||||
|
const found = new Map<string, { title: string; path: string }>();
|
||||||
|
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<boolean | null> {
|
||||||
|
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(
|
||||||
|
`• <b>${escapeHtml(h.title)}</b> — ${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 = `🔎 <b>Delivery Lead – Agentic Solutions watch</b>\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}`);
|
||||||
Reference in New Issue
Block a user