Stage de fin d’études chez Takima
Tima, l’outil de recrutement de Takima
Quatre mois sur un produit en production, utilisé chaque jour par de vrais recruteurs, après deux mois de formation interne. La question qui m’a suivi tout le stage : comment livrer ce qui compte sans casser ce qui marche ?
- Période
- juin – septembre 2026, après la formation interne
- Rôle
- Développeur full-stack dans l’équipe Tima, encadré par une tech lead
- Technologies
Le produit
Tima est un ATS, un logiciel de suivi des candidatures. Un recruteur y crée un poste, y rattache des candidats, puis les fait avancer d’étape en étape : premier contact, entretien de motivation, entretien technique, décision. À chaque étape, les intervenants planifient un rendez-vous, écrivent au candidat à partir de modèles d’e-mails et consignent leur évaluation.
L’API Spring Boot porte toutes les règles métier et les autorisations. Le front Next.js n’est qu’une vue. L’envoi des e-mails est délégué à un service séparé qui lit une file Kafka : si la messagerie ralentit, l’application reste réactive.
Mes utilisateurs étaient mes collègues du pôle recrutement. La boucle entre « il me faudrait ça » et « c’est en production » était courte, et les retours aussi.
Faire travailler un LLM sans lui laisser le dernier mot
Quand un client envoie un besoin par e-mail, un recruteur devait recopier à la main l’intitulé, la durée, la localisation, les compétences. J’ai conçu la création automatique des missions : le courriel et ses pièces jointes sont soumis à un modèle de langage, qui renvoie un brouillon de mission. Les pièces sont archivées sur un stockage compatible S3. Le recruteur vérifie et complète au lieu de tout ressaisir.
La difficulté n’est pas d’appeler le modèle, c’est de discipliner une réponse qui n’est jamais deux fois la même. Le modèle reçoit un schéma JSON dérivé de la classe Java attendue. Si la réponse arrive tronquée, on tente de la réparer avant d’abandonner, et une erreur qui vaut la peine d’être retentée est signalée comme telle.
Même logique pour les comptes rendus d’entretien produits par Gemini pendant les visioconférences : je les récupère par les API Google Meet et Drive, je les découpe en sections et je pré-remplis l’évaluation. Enfin, j’ai rendu le fournisseur de LLM interchangeable par configuration. La bascule d’OpenAI vers OpenRouter s’est faite sans toucher au code, avec un retour arrière possible.
Une règle s’est dégagée au fil des tickets : l’IA pré-remplit le factuel, elle ne donne jamais un avis. Tout ce qui relève du jugement du recruteur reste vide par défaut.
La fonctionnalité prête qui ne servait à personne
C’est aussi sur ces comptes rendus que je me suis le plus planté. J’avais demandé à ma tech lead, qui fait passer des entretiens, quels champs elle voulait voir pré-remplis, j’avais croisé avec d’autres interviewers, et j’avais fini en avance.
Sauf que je n’avais activé les API Meet et Drive qu’en développement. La seule personne qui pouvait le faire en production venait de partir trois semaines en congé. La fonctionnalité a attendu deux semaines pour une demande que j’aurais pu envoyer avant son départ.
Depuis, une fonctionnalité n’est pas finie pour moi tant qu’elle n’est pas déployable. Le code n’est qu’une partie du travail.
Sécuriser l’API objet par objet
Certains points d’entrée étaient réservés aux utilisateurs connectés, mais ne vérifiaient pas que l’utilisateur avait le droit d’agir sur le candidat précis visé. Un contrôle par rôle ne suffit pas : deux recruteurs de même rôle ne suivent pas les mêmes candidats.
J’ai ajouté un contrôle d’accès au niveau de l’objet, avec des gardes réutilisables et une permission dédiée, puis audité tous les points d’entrée concernés pour n’en oublier aucun. J’ai aussi conçu un formulaire d’inscription public pour les freelances : ouvert sans authentification, il n’accepte qu’une liste fermée de champs, pour qu’on ne puisse pas s’attribuer un statut ou un rôle en ajoutant un champ à la requête.
Chaque règle d’accès est accompagnée d’un test qui échoue si on la retire. C’est la seule façon que j’ai trouvée d’être sûr qu’une règle de sécurité existe encore six mois plus tard.
Deux prénoms identiques qui ne l’étaient pas
Un bug de recherche : certains candidats au prénom accentué restaient introuvables. Impossible à reproduire sur mon poste. J’ai soupçonné ma requête, ma machine, l’encodage de la base, et perdu presque une journée.
La cause n’était nulle part dans le code. Unicode autorise deux façons d’écrire « é » : un seul caractère, ou un « e » suivi d’un accent combinant. À l’écran, rien ne les distingue. En mémoire, ce ne sont pas les mêmes octets.
String normalized = Normalizer.normalize(search, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}", "");Plutôt qu’une rustine dépendante de l’environnement, j’ai normalisé systématiquement le texte avant de le désaccentuer, et ajouté un test qui éprouve les deux écritures. Je ne regarde plus un champ texte accentué de la même façon.
Un client de plus, une seule base de code
Tima sert plusieurs instances depuis le même code, chacune avec des comportements réglés par configuration. Quand un nouveau client est arrivé, sa demande principale paraissait simple : pouvoir rattacher un candidat à plusieurs postes.
Notre première version a fait apparaître des bugs partout. Le changement touchait le screening, les entretiens, le tableau de bord, la fiche candidat. On a repris la conception à zéro, listé chaque écran concerné, puis repris les écrans un par un. Le chantier a été bien plus lourd qu’annoncé.
La leçon a été inconfortable : sur une évolution aussi transverse, le temps qu’on ne passe pas à cadrer au départ, on le repaie ensuite écran par écran.
Ce que j’en retiens
Ce que je fais bien : entrer vite dans une grosse base de code inconnue, en partant du comportement observable et des tests existants plutôt qu’en lisant ligne à ligne. Tenir la barre sur les tests et la non-régression. Mener une fonctionnalité jusqu’au bout.
Ce que je travaille : mieux estimer la charge d’une tâche, prendre de la hauteur sur l’architecture avant de coder, et penser à la mise en production dès le début plutôt qu’à la fin.
Ce qu’en dit ma tech lead
Il a rapidement pris en main notre environnement technique : une application en microservices, avec un backend en Java / Spring / Hibernate et un front en React / Next.
Julien a su s’approprier assez vite les outils d’intelligence artificielle appliqués au développement. Il les a utilisés de façon pertinente dans son travail quotidien, ce qui a eu un effet positif sur la vélocité de l’équipe.