intake, Parte II: da tool personale a piattaforma di acquisizione dati
La Parte II del case study intake: far evolvere un tool personale in una pipeline di acquisizione dati multi-sorgente, senza rompere ciò che già gira in produzione.
// indice 6 sezioni
Nella Parte I ho raccontato l’origine di intake: un tool personale che cattura link da mobile, li classifica con un modello e li deposita in un database. In coda c’era un primo job scanner che interrogava una manciata di aziende con ATS TeamTailor, uno strumento che usavo ogni giorno e di cui mi fidavo.
Quando la ricerca di lavoro è diventata seria, però, quel perimetro ha iniziato a starmi stretto: le offerte non vivono solo nelle career page di poche aziende, ma nei job board come Jooble, Adzuna e Indeed, ognuno con il suo formato, la sua geografia, la sua qualità dei dati. Questa è la storia di come ho fatto evolvere quel tool in una piccola piattaforma di acquisizione dati, senza rompere il flusso che già girava in produzione.
Iniettare, non riscrivere
La tentazione, davanti a un requisito nuovo, è riscrivere. Avevo un motore di matching che classificava le offerte e già funzionava. Buttarlo per costruire “la versione multi-sorgente” avrebbe significato reintrodurre bug in una parte stabile solo per aggiungerne una nuova.
Ho fatto l’opposto. Il canale multi-sorgente non tocca il motore: si aggancia alla coda di classificazione esistente con un dispatch controllato. I job normalizzati vengono iniettati nello stesso flusso che già alimentava le candidature. Il codice che funziona resta intatto, il rischio è confinato al pezzo nuovo. In un contesto aziendale è la differenza tra un’integrazione che puoi mettere in produzione di venerdì e una che ti tiene sveglio.
Un contratto comune, un registro rigiocabile
Tre provider diversi restituiscono tre forme diverse della stessa cosa. Il primo pezzo è un contratto, NormalizedJob, che li riduce a un formato unico prima che il resto del sistema li veda. Da lì in poi il matching non sa, e non deve sapere, da dove arriva un’offerta.
Il secondo pezzo è il registro. Ogni evento grezzo, il payload esatto come è arrivato dal provider, finisce in una tabella append-only su Supabase. Non lo sovrascrivo mai. Se domani cambio la logica di normalizzazione, posso rigiocare gli eventi storici invece di scoprire che il dato originale non c’è più. Quando integri fonti che non controlli, l’audit trail non è burocrazia: è la rete che ti permette di correggere gli errori a posteriori.
Deduplicare tra fonti diverse
La stessa offerta arriva da tre board con tre URL diversi e tre modi diversi di scrivere il nome dell’azienda. Una singola chiave di deduplica non regge. Ho costruito una cascata: prima l’ID esterno del provider quando c’è, poi l’hash dell’URL canonicalizzato, infine un fingerprint costruito su azienda, titolo e città. Se il primo controllo non decide, passa al successivo. Ogni livello cattura un tipo di duplicato che il precedente lascia sfuggire.
Il matching a due fasi, e una lezione dalla produzione
Il matching è separato in due giudizi. Il modello valuta solo il ruolo: seniority, competenze, coerenza con il profilo. L’eleggibilità geografica, cioè dove posso davvero lavorare, la calcola il codice. Separarli rende ogni parte testabile e spiegabile.
Qui è arrivata la lezione più utile. Dopo un refactor, la funzione che decideva se un’offerta fosse remota ha smesso di funzionare: per settimane non ha marcato una sola offerta come remote-eligible, zero su duecentonove, senza mai un errore o un crash a dare l’allarme. Il sistema girava e produceva risultati plausibili, solo sbagliati, e me ne sono accorto guardando i numeri invece che da un alert. I bug peggiori in produzione non gridano: passano inosservati proprio perché tutto sembra funzionare. L’ho isolato, capito e corretto.
Stato e limiti
Il canale multi-sorgente è giovane. Al primo collaudo ha reso un candidato utile a settimana contro i cinque che mi ero prefissato: il segnale c’è, la resa va ancora tarata. Un provider, Jooble, lo tengo in stato “degradato” perché il suo geocoding sull’Italia è troppo inaffidabile per fidarsi della sede dichiarata. La deduplica gira ancora dopo lo scraping, quindi qualche offerta consuma risorse prima di essere scartata.
Sono nodi aperti, e li scrivo perché sono la parte onesta del lavoro. Un sistema in produzione non è mai finito: ogni limite risolto ne scopre uno nuovo. Questi sono i prossimi.
La disciplina dietro l’integrazione
Collegare tre provider ha richiesto meno lavoro che decidere dove farli entrare, quali dati conservare e quali responsabilità lasciare al codice esistente. Le scelte più importanti hanno riguardato i confini: un contratto comune, eventi rigiocabili, deduplica a cascata e classificazione separata dall’eleggibilità geografica.
È una fotografia di intake mentre cambia scala senza cambiare natura. Resta uno strumento operativo, costruito attraverso modifiche circoscritte che posso osservare, verificare e correggere.