15/12/2025Datapizza Salaries - Manifesto

Dati dalla community, per la community.

Datapizza Salaries è la nostra piattaforma per mappare il mercato tech italiano attraverso statistiche dettagliate su stipendi, ruoli e trend. Questo è il nostro manifesto: i principi che ci guidano e come li mettiamo in pratica.


Per un'Italia competitiva nel tech

Da anni Datapizza lavora per rendere l'Italia competitiva nel tech. Con Datapizza Jobs abbiamo introdotto la "RAL in chiaro" negli annunci di lavoro. Con il nostro framework Open Source Datapizza AI supportiamo lo sviluppo di tecnologie costruite dalla community italiana. Con i nostri contenuti ed eventi cerchiamo di far crescere un ecosistema che possa competere a livello internazionale.

Datapizza Salaries nasce dalla stessa visione: la trasparenza salariale non è un lusso, ma è una necessità per un mercato tech sano.

I nostri principi

Costruire una piattaforma "dalla community, per la community" non è uno slogan. È un vincolo di design che influenza ogni decisione tecnica. Ecco cosa significa per noi:

Anonimato reale. I dati personali non vengono mai collegati alle submission. Non chiediamo account, non tracciamo identità, non chiediamo dati sensibili. I meccanismi anti-abuso che utilizziamo (ne parliamo più avanti) sono progettati per essere completamente separati dai dati che raccogliamo.

Trasparenza verificabile. Non basta dire "siamo trasparenti". Il dataset completo viene pubblicato automaticamente ogni settimana su Hugging Face. Chiunque può scaricarlo, analizzarlo, verificare che i numeri sulla piattaforma corrispondano ai dati grezzi.

Restituire valore. La community ci dà i dati, noi restituiamo insight. La piattaforma è costruita per non richiedere autenticazione, per non obbligare nessuno a passare dai nostri canali: il dato deve essere il più accessibile e trasparente possibile.

Rispetto di chi contribuisce. Il form di submission è essenziale. Chiediamo solo quello che serve per produrre statistiche utili, senza autenticazione o passaggi inutili. Se qualcuno della community dedica due minuti a contribuire, quei due minuti devono valere.

Integrità dei dati. Senza autenticazione, dobbiamo proteggere il dataset da spam e manipolazioni. Lo facciamo con tecnologie che non compromettono l'anonimato, trovando il punto di equilibrio tra apertura e affidabilità.


Come lo facciamo

Passiamo dalla teoria alla pratica. Questa sezione è più tecnica: se ti interessa solo usare la piattaforma, puoi saltare direttamente alle conclusioni. Se invece vuoi capire come abbiamo implementato questi principi, continua a leggere.

Dataset pubblico su Hugging Face

Ogni settimana, un cronjob pubblica automaticamente l'intero dataset su Hugging Face tramite le loro API. Non una selezione, non un campione: tutti i dati, con un flag che indica se ogni submission ha superato i controlli di integrità.

Il dataset è rilasciato sotto licenza CC-BY-NC-4.0, così che rimanga completamente open source e il dato resti di proprietà della community, e non vengano usati per uso commerciale.

Vai al Dataset

Questo significa che puoi:

  • Verificare che le statistiche mostrate nell'app corrispondano ai dati grezzi
  • Fare le tue analisi con i criteri che preferisci
  • Decidere autonomamente se includere o escludere submission in base al flag di validazione

La pubblicazione automatica elimina anche qualsiasi dubbio che i dati vengano "curati" prima del rilascio.

Trasparenza nell'app

Quando navighi su salaries.datapizza.tech, ogni statistica mostra il numero di submission su cui è calcolata. Non solo il valore mediano, ma anche quanti dati lo supportano.

Questo serve a due scopi:

  1. Ti permette di valutare l'affidabilità di ogni numero. Una mediana calcolata su 500 submission è più robusta di una calcolata su 12.
  2. Rende evidente dove servono più dati, incentivando contribution mirate.

Protezione anti-bot: Cloudflare Turnstile

Il form di submission è protetto da Cloudflare Turnstile, un'alternativa ai CAPTCHA tradizionali che verifica gli utenti senza puzzle frustranti. È il primo livello di difesa contro bot e spam automatizzato.

Fingerprinting per integrità: Thumbmark.js

Senza autenticazione, come evitiamo che qualcuno invii decine di submission per alterare le statistiche?

Usiamo Thumbmark.js, una libreria che genera un fingerprint del browser basato su caratteristiche tecniche (canvas rendering, font disponibili, WebGL capabilities, ecc.). Questo fingerprint è un hash che identifica una combinazione browser/dispositivo, non una persona.

Il punto cruciale: il fingerprint non viene mai collegato alla submission. Viene salvato in una collection MongoDB separata, con un TTL (time-to-live) dinamico. Se per x settimane non arrivano submission con quel fingerprint, viene cancellato automaticamente.

Il flusso è:

  1. Alla submission, generiamo il fingerprint
  2. Controlliamo se esiste già nella collection
  3. Se esiste e ha submission recenti, marchiamo la nuova submission come "da verificare"
  4. Il fingerprint stesso viene aggiornato (reset del TTL) ma rimane separato dai dati

Questo sistema non ha lo scopo di evitare che ogni individuo al mondo mandi i dati due volte (qualcuno potrebbe usare dispositivi diversi su reti diverse), ma grazie al volume di dati che gestiamo, è un compromesso ragionevole tra protezione e privacy, e la soluzione migliore per evitare di collegare una mail. Nel dataset pubblico, ogni submission ha un flag che indica se ha passato questo controllo: sei tu a decidere se fidarti o meno.


L'architettura

L'infrastruttura è progettata per supportare i principi che abbiamo descritto: accesso facile ai dati, performance senza compromessi e una codebase mantenibile.

Next.js con caching aggressivo

L'app è costruita in Next.js, con quasi tutto renderizzato server-side. Usiamo il caching nativo di Next per le pagine:

// Ogni pagina job title viene rigenerata al massimo ogni settimana
export const revalidate = 3600 * 24 * 7;

Per ogni job title, pre-generiamo tutte le pagine a build time con generateStaticParams:

export async function generateStaticParams() {
  const jobTitles = await getJobTitles();
  return jobTitles.map((jobTitle) => ({
    "job-title-id": jobTitle.id.toString(),
  }));
}

Questo significa che ogni job title ha la sua pagina pre-generata, con dati già in cache, e con metadata (per SEO per esempio) già pronti. Quando un utente cerca "Software Engineer stipendio Italia", la pagina è già lì, cached e veloce.

Python per le aggregazioni: Polars

Le aggregazioni statistiche richiedono librerie specializzate. Invece di reinventare la ruota in TypeScript, usiamo Python con Polars: una libreria di data analysis moderna, scritta in Rust, significativamente più veloce di pandas.

Anche con il caching, le aggregazioni devono essere performanti per quando la cache viene invalidata o per nuovi job title. Polars ci permette di calcolare mediane, percentili, deviazioni standard e breakdown multidimensionali in millisecondi.

Single source of truth: da Zod a Pydantic

Qui c'è un problema classico: usiamo TypeScript per Next.js e Python per le API di aggregazione. Come manteniamo allineati i tipi, le validazioni, le costanti?

La nostra soluzione: TypeScript è la source of truth. Definiamo tutto in Zod (schema di validazione per TypeScript), poi generiamo automaticamente:

  1. JSON Schema dal Zod schema
  2. Modelli Pydantic dal JSON Schema

Ecco un esempio semplificato per rendere l'idea. Questo schema Zod:

export const aggregateResponseSchema = z.object({
  jobTitleId: z.number().int().positive(),
  count: z.number().nonnegative(),
  cards: z.array(
    z.object({
      seniority: z.enum(["junior", "mid", "senior"]),
      medianSalary: z.number().min(0).max(500000),
      count: z.number().nonnegative(),
      // ... altri campi statistici
    })
  ).length(3),
  // ... altri breakdown
});

Viene trasformato automaticamente in JSON Schema, e poi in Pydantic:

class Card(BaseModel):
    model_config = ConfigDict(extra='forbid')
    seniority: Seniority
    medianSalary: confloat(ge=0.0, le=500000.0)
    count: confloat(ge=0.0)
    meanSalary: confloat(ge=0.0, le=500000.0)
    # ...

class AggregateResponse(BaseModel):
    model_config = ConfigDict(extra='forbid')
    jobTitleId: PositiveInt
    count: confloat(ge=0.0)
    cards: List[Card] = Field(..., max_length=3, min_length=3)
    # ...

Lo script di generazione usa zod-to-json-schema e datamodel-codegen

Ecco un piccolo snippet di questo script:

// Genera JSON Schema da Zod
const schema = zodToJsonSchema(aggregateResponseSchema, {
  name: "AggregateResponse",
});
writeFileSync("api/generated/aggregate-response-schema.json",
  JSON.stringify(schema, null, 2));

// Poi, via shell, genera Pydantic
execSync(`datamodel-codegen \\
  --input generated/aggregate-response-schema.json \\
  --output generated/aggregate_response.py \\
  --input-file-type jsonschema \\
  --output-model-type pydantic_v2.BaseModel`);

Questo pattern si estende anche alle costanti: la lista delle regioni italiane, i livelli di seniority, le industry—tutto definito una volta in TypeScript, esportato in JSON, importato in Python. Zero drift, zero bug di allineamento.

FastAPI su Vercel Python Runtime

Il backend Python gira come serverless function su Vercel Python Runtime. FastAPI gestisce le richieste di aggregazione, valida input e output con i modelli Pydantic generati, e comunica con Next.js attraverso API interne (protette da API key condivisa nello stesso environment).

Questa architettura ci permette di avere tutto in un unico deployment Vercel, senza gestire server separati o layer di caching esterni come Redis. Il caching di Next.js è sufficiente per il nostro pattern di accesso.


Conclusioni

Datapizza Salaries è il nostro tentativo di costruire qualcosa che mancava: una fonte di dati sul lavoro tech italiano che sia trasparente, verificabile, e di proprietà della community che la alimenta.

Se lavori nel tech in Italia, contribuisci con la tua submission. Se vuoi mettere le mani in pasta, scarica il dataset e facci sapere cosa trovi. Se hai feedback, siamo tutt'orecchi.

E se questo deep-dive tecnico ti ha incuriosito e vuoi saperne di più sull'architettura, come gestiamo il caching multi-livello, i pattern di aggregazione Polars, la type safety su più linguaggi di programmazione, la developer experience, o il deployment su Vercel, scrivici. Potremmo farne un altro blog post.

Esplora Datapizza Salaries →


Leonardo Trapani · GitHub · LinkedIn · Website SWE @ Datapizza