La plupart des équipes de recrutement découvrent les problèmes de funnel lors de la QBR trimestrielle. À ce moment-là, le poste est ouvert depuis soixante jours, deux des trois meilleurs candidats ont signé ailleurs, et le hiring manager a perdu confiance dans le pipeline. Ce workflow comble cet écart. Un flow n8n s’exécute chaque nuit contre votre ATS, calcule les taux de conversion et les temps de résidence par poste et par étape par rapport à une référence glissante sur 90 jours, signale les déviations statistiquement significatives, demande à Claude d’expliquer chacune en une ou deux phrases, et publie le résultat dans un canal Slack adapté au routage avant le stand-up du lendemain matin.
Le bundle dans apps/web/public/artifacts/hiring-funnel-anomaly-n8n/hiring-funnel-anomaly-n8n.json livre quinze nœuds entièrement configurés — deux déclencheurs planifiés, une extraction Ashby, un nœud Code d’agrégation avec la vraie logique d’anomalie, une recherche de référence Postgres, le détecteur, un insert dédoublonnant, l’appel narratif Claude, le formateur Slack, et un chemin parallèle de tendance temps-de-recrutement. Le _README.md compagnon documente les quatre credentials, les trois tables Postgres à créer, et une vérification en cinq étapes pour la première exécution qui teste chaque branche.
Quand l’utiliser
Déployez ce workflow quand votre équipe de recrutement gère au moins huit à dix postes actifs en parallèle, que l’équipe est sur le même ATS (Ashby, Greenhouse ou Lever) depuis au moins quatre-vingt-dix jours pour qu’une référence existe, et qu’au moins une personne de l’équipe possède la santé du funnel comme partie de son rôle. En dessous de ces seuils, le rapport signal-sur-bruit est mauvais : les références sont trop bruitées pour fixer un seuil de score z utile, et personne n’agira réellement sur l’alerte quand elle se déclenche.
L’autre prérequis est la discipline taxonomique. Si vos étapes sont nommées différemment pour chaque poste, ou si les hiring managers créent librement de nouvelles étapes en cours de recherche, l’agrégation par poste et par étape produira une longue traîne de paires (poste, étape) avec un taille d’échantillon d’un. La garde MIN_SAMPLE = 20 du détecteur les supprime, ce qui est correct, mais vous vous retrouverez sans aucun signal plutôt qu’avec un signal erroné. Corrigez d’abord la taxonomie des étapes.
Quand NE PAS l’utiliser
Ne déployez pas ce workflow si vous gérez moins de cinq postes actifs. Les maths ne fonctionnent pas — il n’y a pas assez d’événements par paire (poste, étape) pour calculer un écart-type de référence significatif, et vous passerez plus de temps à ajuster les seuils qu’à agir sur les alertes. Une revue manuelle hebdomadaire dans une feuille de calcul est vraiment meilleure à cette échelle.
Ne déployez pas ce workflow si votre ATS est le seul endroit où les données des candidats existent et que vous ne disposez pas encore d’un entrepôt analytique séparé. Le flow suppose que vous pouvez mettre en place une base de données Postgres autorisée à dupliquer les données des candidats. Si votre équipe de politique de confidentialité ou d’IA n’a pas encore approuvé la sortie des données des candidats de l’ATS, exécutez cet exercice uniquement sur des agrégats au niveau des étapes — supprimez le calcul de résidence au niveau candidat — et revenez quand la politique aura évolué.
Ne déployez pas ce workflow sur une taxonomie d’étapes qui change chaque semaine. La détection d’anomalies sur une définition d’« étape » en mouvement produit des alertes que personne ne peut interpréter. Stabilisez la taxonomie pendant au moins un trimestre avant d’activer le flow.
Enfin, ne déployez pas ce workflow si la réponse de votre équipe à une alerte de funnel serait « nous le savions déjà ». La valeur du workflow est la latence de 24 heures sur une métrique qui surgirait autrement dans 60 jours. Si vous avez déjà un stand-up quotidien où c’est examiné en direct, l’alerte est redondante.
Configuration
Construisez d’abord la référence. Exécutez un remplissage ponctuel sur les 90 derniers jours en utilisant le même endpoint de flux d’applications Ashby que le workflow utilise, groupez par (role_id, from_stage, to_stage), et écrivez conversion_rate_mean, conversion_rate_stddev, dwell_seconds_p50, stage_sla_seconds et sample_size dans la table funnel_baselines. Le DDL pour cette table — et pour role_tth_baselines et anomaly_alerts — se trouve dans le _README.md du bundle.
Importez hiring-funnel-anomaly-n8n.json du bundle dans n8n. Le workflow est livré inactif intentionnellement. Ouvrez Paramètres et confirmez que le fuseau horaire correspond aux heures de travail de votre équipe ; les deux nœuds Cron évaluent leurs expressions dans le fuseau horaire du workflow, pas en UTC. Créez les quatre credentials référencés par nom dans le JSON : PLACEHOLDER_ASHBY_CRED_ID, PLACEHOLDER_POSTGRES_CRED_ID, PLACEHOLDER_ANTHROPIC_CRED_ID, PLACEHOLDER_SLACK_CRED_ID. Le README explique chacun, y compris les portées Slack (chat:write, chat:write.public) et la forme Header Auth Anthropic.
Effectuez la vérification en cinq étapes pour la première exécution avant d’activer. L’étape deux — insérer une ligne de référence synthétique garantie de se déclencher — est celle que la plupart des gens sautent et se demandent ensuite pourquoi aucune alerte ne se déclenche ; faites-la. L’étape trois confirme que la clé de dédoublonnage fait son travail, ce qui est la garde de coût pour l’appel Claude.
Rafraîchissez les références mensuellement. Les taux de conversion dérivent avec la saisonnalité, les conditions du marché et la composition de l’équipe, et une référence obsolète produit soit du spam d’alertes soit des régressions manquées. Le rafraîchissement est la même requête qui a construit la référence ; cronez-la comme un workflow n8n séparé ou comme un job SQL côté Postgres.
Ce que le flow fait
Le Cron de 2h déclenche une extraction du flux d’applications Ashby pour les dernières 24 heures. Le nœud Code agrégateur regroupe les événements par (role_id, from_stage, to_stage), calcule le taux de conversion et le temps de résidence médian d’aujourd’hui pour chaque paire, et émet un élément par paire. Le nœud Postgres Lookup Baseline joint chaque paire à sa ligne de référence. Le nœud Code Detect Anomalies applique trois règles : un score z de conversion d’étape en dessous de -2,0 par rapport à la moyenne de référence est signalé comme stage_conversion_drop, un temps de résidence médian dépassant stage_sla_seconds * 1,5 est signalé comme candidate_stalled, et une paire (poste, étape) avec zéro événement aujourd’hui et moins de 20 événements historiques est signalée comme new_role_no_movement avec une note de faible sévérité indiquant que les seuils ont été supprimés.
Chaque signalement est écrit dans anomaly_alerts avec une dedupe_key de role::from_stage::to_stage::anomaly_type::yyyy-mm-dd sous une clause ON CONFLICT DO NOTHING. Seuls les inserts qui ont retourné une ligne — c’est-à-dire les alertes qui n’existaient pas déjà pour aujourd’hui — procèdent à l’appel narratif Claude et au post Slack. C’est la garde de coût : une ré-exécution le même jour ne facture pas deux fois Anthropic et ne poste pas deux fois dans Slack. Le formateur Slack route selon le type d’anomalie : les chutes au niveau d’étape et les candidats bloqués vont dans #recruiting-alerts, les tendances temps-de-recrutement et les nouveaux-postes-sans-mouvement vont dans #recruiting-leadership, et les chutes de canal source vont dans #sourcing.
Le Cron de 3h exécute le chemin parallèle de tendance temps-de-recrutement contre une table hires. Il remodèle les lignes où la moyenne glissante sur 7 jours dépasse le seuil dans la même enveloppe d’alerte et les fait passer par le même chemin de dédoublonnage et de post.
Réalité des coûts
Pour une équipe de 30 postes exécutant ceci chaque nuit, attendez environ 600 agrégations (poste, étape) par jour. L’extraction Ashby et les recherches Postgres ne coûtent pratiquement rien. L’appel narratif Claude utilise claude-sonnet-4-6 avec un plafond de 256 tokens et ne se déclenche que sur les alertes nouvellement insérées — pour une équipe saine, c’est typiquement 0 à 3 alertes par nuit, soit environ 0,05-0,15 $ par nuit en tokens. Un pic de 20 régressions simultanées coûte environ 1,00 $. La clé de dédoublonnage garde les ré-exécutions gratuites.
n8n auto-hébergé sur un VPS à 20 $/mois gère cette charge avec de la marge ; le plan Starter n8n Cloud (24 $/mois) convient aussi. Postgres peut être la même instance qui alimente tout le reste que vous exécutez — les trois tables sont petites, peu fréquentées, et indexées sur une seule clé composite. Le coût marginal total est dominé par le temps humain pour maintenir la référence à jour et la taxonomie des étapes stable, pas par l’exécution.
Si vous remplacez Opus pour l’appel narratif, prévoyez un multiplicateur de coût de tokens d’environ 5x ; c’est rarement justifié pour une explication d’une à deux phrases.
Métriques de succès
Suivez la latence médiane entre le début réel d’une régression (poste, étape) et l’action de l’équipe de recrutement à ce sujet. Avant ce flow, cette latence est typiquement de deux à six semaines (la prochaine revue de pipeline). Avec le flow déployé et suivi, elle devrait tomber à 24-48 heures. Si ce n’est pas le cas — si les alertes se déclenchent et que personne n’agit — le problème n’est pas le détecteur, c’est le routage ou le seuil, et la correction est soit de resserrer les canaux jusqu’à ce que l’alerte atteigne une personne ayant l’autorité d’agir, soit de relâcher le seuil de score z jusqu’à ce que le volume d’alertes corresponde à ce que l’équipe peut absorber.
Une métrique secondaire à observer : alertes rejetées sans action en pourcentage du total. Au-dessus de 30 %, vous alertez sur du bruit ; en dessous de 5 %, vous sous-alertez probablement et manquez des signaux.
Alternatives
L’alternative DIY est un job Python ou SQL qui exécute la même agrégation et poste dans Slack via webhook. Ça fonctionne et le coût par événement est plus faible, mais le graphe n8n est la documentation — un ingénieur recruiting-ops qui rejoint le trimestre prochain peut ouvrir le workflow, voir les huit nœuds dans l’ordre, et comprendre le système sans lire de code. Le chemin DIY saute aussi typiquement l’insert de dédoublonnage et la garde de coût autour de l’appel LLM, ce qui est là que viennent les factures.
L’alternative prête à l’emploi est d’acheter Gem, Ashby Analytics ou Datapeople pour le reporting de funnel. Ce sont de bons produits et la bonne réponse pour les équipes qui veulent des tableaux de bord gérés et ne veulent pas posséder une table de référence Postgres. Ce sont la mauvaise réponse quand vous voulez des alertes d’anomalies dans Slack avec une narrative jointe, car aucun d’eux ne livre ça aujourd’hui ; ils livrent des tableaux de bord que quelqu’un doit penser à vérifier. Le compromis est : payer le vendeur et perdre la latence d’alerte, ou posséder le flow n8n et gagner le signal de 24 heures au prix de gérer Postgres.
L’alternative statu quo — la revue de pipeline trimestrielle — est ce que la plupart des équipes font déjà. Elle ne coûte rien et fait remonter les mêmes régressions, juste soixante jours plus tard. Si vos postes prennent six mois à pourvoir et qu’un signal avec soixante jours de retard vous laisse encore le temps de corriger le tir, le statu quo convient vraiment.
Points de vigilance
La fatigue des alertes est le mode d’échec dominant. Une équipe qui reçoit dix alertes chaque matin commencera à toutes les ignorer en une semaine, y compris celle qui comptait. La garde est les constantes MIN_SAMPLE = 20 et Z_THRESHOLD = 2,0 dans le nœud Code Detect Anomalies, plus le DWELL_MULTIPLIER = 1,5 pour les candidats bloqués. Commencez avec ces valeurs, regardez le canal pendant les trois premières nuits, et resserrez — pas relâchez — jusqu’à ce que le matin médian amène 0 à 2 alertes. Relâchez plus tard si vous constatez que vous manquez de vraies régressions.
La dérive de référence produit des faux négatifs silencieux. Les taux de conversion changent avec le marché du travail, le mix de sourcing de l’équipe et le poste lui-même. Une référence calculée en novembre sur un marché actif sous-signalera dans un marché mou et sur-signalera dans un marché tendu. La garde est le rafraîchissement mensuel de funnel_baselines et role_tth_baselines depuis la même requête qui les a construits — planifiez-le comme un workflow n8n récurrent ou un job Postgres et traitez l’échec de ce rafraîchissement comme un incident P1.
L’action automatique sur une alerte de recrutement est presque toujours fausse. La narrative que Claude retourne est de la corrélation, pas de la causalité ; la traiter comme une directive (« la conversion a chuté de 30 %, rejeter automatiquement tous les no-shows à l’entretien téléphonique ») aggravera le problème. La garde est structurelle : le flow n’a pas de chemin de ré-écriture vers l’ATS. Si un futur contributeur propose d’en ajouter un, refusez. L’IA fait remonter l’anomalie ; les humains diagnostiquent et agissent.
Les données privilégiées des candidats quittent l’ATS. Les tables Postgres contiennent role_id, les noms d’étapes, les taux de conversion et les temps de résidence, ce qui est uniquement agrégé par conception — mais une extension imprudente qui ajoute les noms ou coordonnées des candidats pour permettre des narratives plus riches créera une nouvelle surface de confidentialité. La garde est le schéma dans _README.md : les tables n’ont intentionnellement aucune colonne identifiant les candidats. Si un contributeur veut les ajouter, faites passer le changement par votre revue de confidentialité et de politique IA.
L’attribution par canal source n’est aussi bonne que les données ATS. L’alerte optionnelle source_channel_drop (référencée dans la carte de routage Slack mais non activée par défaut dans le bundle) dépend du fait que chaque candidat dispose d’une attribution source propre dans Ashby. Si votre équipe est négligente dans le tagging des sources, l’alerte se déclenchera sur des problèmes de qualité des données, pas sur de vrais problèmes de canal. La garde est une vérification de prérequis : n’activez pas ce type d’alerte jusqu’à ce que l’attribution source soit au moins complète à 90 % dans votre export ATS. Vérifiez avec une requête SQL d’une ligne sur le flux d’applications avant de l’activer.
# Hiring funnel anomaly detection — n8n bundle
## What this flow does
This bundle contains a complete n8n workflow that watches your applicant tracking system for funnel-shaped problems and surfaces them in Slack within 24 hours of the metric moving. Two scheduled triggers wake up nightly. The 2am job pulls the last 24 hours of stage-transition events from Ashby, aggregates them per role-by-stage, joins each row to a rolling baseline stored in Postgres, and emits an alert when today's conversion rate is at least two standard deviations below the baseline mean, when median dwell time in a stage exceeds the role's stage SLA by 50%, or when a role has had zero applicant movement in seven days. The 3am job recomputes a rolling 7-day time-to-hire per role and flags any role exceeding its threshold. Alerts are written to a deduplicated `anomaly_alerts` table so re-running the flow on the same day cannot re-fire. Newly inserted alerts are explained by Claude in one or two sentences and posted to a routing-aware Slack channel.
The two scheduled triggers are independent, which is deliberate. The per-stage detector reads a high-volume application feed; the time-to-hire trend check runs a heavier SQL aggregate. Splitting them by an hour avoids contention on the baselines table and lets you disable one path without breaking the other.
## Import
1. In n8n, open `Workflows`, click `Add workflow`, then `Import from file`.
2. Select `hiring-funnel-anomaly-n8n.json` from this bundle.
3. The workflow imports inactive. Do not toggle it active until credentials are wired and the first-run verification below passes.
4. Open `Settings` (top right) and confirm `Timezone` is the value you want — the JSON ships with `America/New_York` and both Cron expressions evaluate in that zone.
## Credentials
The flow references four placeholder credentials by name. Create each one under `Credentials` in n8n before running.
### Ashby — API
The HTTP Request node `Ashby — Application Feed (24h)` uses Basic Auth with your Ashby API key as the username and an empty password. Generate the key under `Settings → API` in Ashby. The default scope (`Read Applications`) is sufficient. If your ATS is Greenhouse, swap the URL to `https://harvest.greenhouse.io/v1/applications?updated_after={{ $now.minus({hours:24}).toISO() }}` and use the Greenhouse Harvest API key. For Lever, point at `https://api.lever.co/v1/opportunities?expand=stage&updated_at_start={{ ... }}` and use a Lever API key.
### Postgres — funnel-baselines
A standard Postgres connection. The flow expects three tables in the same database:
- `funnel_baselines (role_id text, from_stage text, to_stage text, conversion_rate_mean numeric, conversion_rate_stddev numeric, dwell_seconds_p50 numeric, stage_sla_seconds integer, sample_size integer, refreshed_at timestamptz, primary key (role_id, from_stage, to_stage))`
- `role_tth_baselines (role_id text primary key, role_name text, tth_baseline_days numeric, tth_threshold_days numeric)`
- `anomaly_alerts (id bigserial primary key, role_id text, from_stage text, to_stage text, anomaly_type text, severity text, current_value numeric, baseline_value numeric, window_end timestamptz, dedupe_key text unique, created_at timestamptz default now())`
The DDL is intentionally not bundled because every team's role and stage taxonomy is different. Build the baselines once from a 90-day backfill of the same Ashby feed before turning the flow active. Refresh `funnel_baselines` and `role_tth_baselines` monthly using the same query that built them.
### Anthropic — x-api-key
The narrative explanation step uses Anthropic's HTTP API directly via Header Auth. Create a Header Auth credential with `Name: x-api-key` and `Value: <your Anthropic API key>`. The model is pinned to `claude-sonnet-4-6` in the node body — change it there if you prefer Haiku for cost or Opus for higher-stakes alerts. Token spend scales with new alerts only because the explanation node sits behind the dedupe insert.
### Slack — bot token
Create a Slack app, install it to your workspace, grant `chat:write` and `chat:write.public`, and store the bot token in a Header Auth credential with `Name: Authorization` and `Value: Bearer xoxb-…`. The `Format Slack Message` code node maps each `anomaly_type` to a destination channel (`#recruiting-alerts`, `#sourcing`, `#recruiting-leadership`); make sure the bot has been invited to each channel or `chat.postMessage` will silently 200 with `ok: false`.
## First-run verification
Before flipping the workflow active, walk through these checks. Each one exercises a different branch of the graph; running them in order proves the whole flow without waiting for a real anomaly.
1. **Disable the 2am Cron, then click `Execute Workflow` from the Cron node manually.** Confirm the Ashby HTTP node returns at least one event. If it returns an empty array, your API token is scoped wrong or the time window has no activity.
2. **Insert one synthetic baseline row that is guaranteed to flag.** `INSERT INTO funnel_baselines (role_id, from_stage, to_stage, conversion_rate_mean, conversion_rate_stddev, sample_size) VALUES ('ROLE_TEST','phone_screen','onsite', 0.50, 0.05, 200);`. Then craft an aggregator output that reports `conversion_rate_today = 0.10` for the same key. Run the workflow and confirm a row appears in `anomaly_alerts` and a message lands in `#recruiting-alerts`.
3. **Run the same execution a second time.** Confirm the dedupe insert returns no rows, no Claude call is made, and no Slack message is sent. This validates the cost guard.
4. **Manually run the 3am Cron node.** With no roles exceeding their threshold, it should complete without writing a row. Insert a fake `hires` row that exceeds threshold, re-run, and confirm a `time_to_hire_trend` alert is persisted and posted to `#recruiting-leadership`.
5. **Delete the test rows from `anomaly_alerts` and `funnel_baselines`** before activating the flow. Forgetting this step pollutes your real baseline.
Once all five steps pass, toggle the workflow `Active`. Watch `#recruiting-alerts` for the first three nights and tighten the `Z_THRESHOLD` or `DWELL_MULTIPLIER` constants in the `Detect Anomalies` code node if the volume is wrong for your team.