· 9 min de lecture

TAP

Plateforme SaaS de régulation transport médico-social pour les sociétés de taxis conventionnés CGSS à La Réunion.

  • #Next.js
  • #TypeScript
  • #Supabase
  • #Sécurité
  • #RGPD

Le projet en une phrase

TAP est une plateforme de régulation de transport pour les sociétés de taxis conventionnés CGSS à La Réunion.


Ce qui a motivé ce projet

À La Réunion, les sociétés de transport conventionnées CGSS assurent des trajets médico-sociaux pour des milliers de patients. Leur quotidien comporte des contraintes fortes :

  • Une régulatrice passe 8 heures par jour, 220 jours par an à gérer les appels, les affectations et les tournées.
  • Le terrain est exigeant : routes sinueuses, temps de trajet variables, zones sans réseau.
  • Les données sont sensibles : patients, prescriptions, traçabilité des transports.
  • Les outils disponibles se résument à des tableaux Excel, des carnets et une logistique manuelle.

Pendant des années, ces sociétés ont fonctionné ainsi. La productivité s’en ressent. J’ai donc entrepris de concevoir un outil adapté, en suivant un cahier des charges fourni par mon chef, qui posait les bases : périmètre fonctionnel, exigences réglementaires, contraintes techniques, hébergement envisagé.


Aperçu de l’interface

Cockpit TAP, vue de la journée en cours avec carte des chauffeurs et alertes Le cockpit : vue temps réel de la journée d’une régulatrice, courses non affectées, brouillons en attente, carte des chauffeurs, échéances réglementaires.

Tableau de bord TAP avec chiffre d’affaires, encours impayé, no-show et top prescripteurs Le tableau de bord : activité du mois, facturation à traiter, taux de no-show, top des prescripteurs.

Liste des patients TAP avec canal de contact préféré et actions d’archivage La liste des patients, avec canal de contact préféré par patient (appel, SMS) et dernière course.

Formulaire de création d’un nouveau patient avec NIR, adresse et référent légal La création d’un patient : identité, NIR chiffré, adresse, référent légal pour les mineurs ou personnes sous tutelle.


Le choix de la stack

┌─────────────────────────────────────────────────────────────────┐
│                         TAP                                     │
├─────────────────────────────────────────────────────────────────┤
│  Frontend    │  Next.js 14 (App Router)                        │
│              │  TypeScript strict                              │
│              │  Tailwind CSS + shadcn/ui                       │
├──────────────┼──────────────────────────────────────────────────┤
│  Backend     │  Next.js API Routes / Server Actions            │
│              │  Supabase (Postgres + Auth + Realtime)          │
│              │  Row Level Security (RLS)                       │
├──────────────┼──────────────────────────────────────────────────┤
│  Infra       │  Turborepo + pnpm workspaces (monorepo)         │
│              │  Migrations versionnées                         │
│              │  OVHCloud (hébergement HDS-ready)               │
├──────────────┼──────────────────────────────────────────────────┤
│  Securité    │  CSP, HSTS, X-Frame-Options                     │
│              │  RLS sur toutes les tables                      │
│              │  Chiffrement applicatif                         │
│              │  Gestion des secrets (Vault)                    │
├──────────────┼──────────────────────────────────────────────────┤
│  Dev tools   │  Biome (lint + format)                          │
│              │  Claude Code                                    │
│              │  GitHub Actions (CI)                            │
└─────────────────────────────────────────────────────────────────┘

Une approche structurée : du cahier des charges à l’architecture

Le projet a démarré avec un cahier des charges qui définissait :

  • Le périmètre fonctionnel (ce que l’application doit faire, et ce qu’elle ne fait pas).
  • Les contraintes réglementaires (RGPD, données de santé, traçabilité).
  • Les exigences non fonctionnelles (performance, disponibilité, sécurité).
  • Les contraintes techniques (stack envisagée, hébergement).

Ce document a été ma boussole. À chaque choix technique, à chaque compromis, je revenais au cahier des charges pour vérifier que je restais dans le cadre défini.

Les ADR : documenter la pensée

J’ai documenté chaque décision d’architecture dans des ADR (Architecture Decision Records). La structure est toujours la même : Contexte → Décision → Conséquences.

  • ADR-001 : Monorepo avec Turborepo.
  • ADR-002 : Supabase pour le backend.
  • ADR-003 : Next.js App Router.
  • ADR-004 : Structure de données des courses.
  • ADR-005 : Politiques RLS pour la sécurité.

Les ADR ne sont pas là pour faire joli. Quand je suis revenu sur un choix trois mois plus tard, ils m’ont rappelé pourquoi j’avais pris telle direction, quelles alternatives j’avais écartées, quelles conséquences j’avais anticipées. C’est un outil que je ne pensais pas utile, et que je ne peux plus ignorer aujourd’hui.


La sécurité : une exigence qui structure tout

TAP manipule des données de santé. L’article 9 du RGPD interdit leur traitement sauf fondement juridique spécifique. Cette contrainte a guidé chaque choix d’architecture, chaque ligne de code, chaque décision d’infrastructure.

J’ai adopté une approche de sécurité par la conception (security by design), en superposant plusieurs couches de protection.

Gestion des secrets et des clés API

La clé service_role de Supabase contourne toutes les politiques RLS. Si elle fuit, c’est toute la base qui est exposée.

J’ai donc structuré le projet comme suit :

  • La clé anon (publique) est utilisée côté client, avec RLS pour contrôler l’accès.
  • La clé service_role n’est utilisée que dans le code serveur (API Routes, Server Components).
  • Les variables d’environnement sont stockées dans un fichier .env.local non versionné.

Pas de NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY. Pas de clé dure. Pas de compromis.

Row Level Security (RLS) : la porte d’entrée obligatoire

Supabase expose la base de données directement via l’API. Sans RLS, n’importe qui avec la clé anon peut lire ou modifier toutes les tables. J’ai donc activé RLS sur chaque table.

ALTER TABLE courses ENABLE ROW LEVEL SECURITY;
ALTER TABLE taxis ENABLE ROW LEVEL SECURITY;
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;

Avec RLS activé et sans politiques définies, une table est complètement inaccessible. C’est le comportement par défaut que j’ai choisi : rien n’est accessible tant que je n’ai pas explicitement défini qui peut accéder à quoi.

Politiques RLS : des règles granulaires

J’ai écrit des politiques pour chaque table, en suivant le principe du moindre privilège.

-- Une régulatrice ne voit que les courses de sa société
CREATE POLICY "Régulatrice voit ses courses"
ON courses FOR SELECT
USING (
  EXISTS (
    SELECT 1 FROM societes
    WHERE societes.id = courses.societe_id
    AND societes.id = auth.user_metadata()->>'societe_id'
  )
);

-- Un chauffeur ne voit que ses propres courses
CREATE POLICY "Chauffeur voit ses courses"
ON courses FOR SELECT
USING (chauffeur_id = auth.uid());

Les politiques RLS ajoutent implicitement des clauses WHERE à chaque requête. C’est la base de données qui applique les règles d’accès, pas le code applicatif. Un bug dans l’application ne peut pas contourner ces contrôles.

Authentification et sessions

TAP utilise Supabase Auth avec des tokens JWT, signés avec des clés asymétriques. Les tokens sont automatiquement envoyés par les clients Supabase lorsque l’utilisateur est connecté. Je les utilise pour :

  • Valider l’identité à chaque requête.
  • Injecter auth.uid() dans les politiques RLS.
  • Gérer les sessions et les rafraîchissements.

Les clés de signature sont rotées régulièrement.

Chiffrement des données

Supabase chiffre toutes les données au repos (AES-256) et en transit (TLS). Pour les données de santé, j’ai ajouté une couche supplémentaire : les champs sensibles sont chiffrés au niveau de l’application avant d’être stockés. J’utilise l’extension Vault de Supabase pour stocker les clés de chiffrement.

En-têtes de sécurité HTTP

J’ai configuré plusieurs en-têtes dans next.config.js :

  • Content-Security-Policy (CSP) : restreint les sources de contenu.
  • Strict-Transport-Security (HSTS) : force les connexions HTTPS.
  • X-Frame-Options : empêche le clickjacking.
  • X-Content-Type-Options : empêche le MIME-sniffing.

Hébergement sur OVHCloud

OVHCloud héberge les serveurs en France, ce qui évite tout transfert de données hors de l’Union européenne. L’infrastructure est conçue pour la conformité RGPD.


Construire le contexte : la vraie valeur ajoutée

Le code généré par l’IA est la partie visible. Ce qui a pris le plus de temps, ce qui a vraiment fait la différence, c’est la construction du contexte.

Le CLAUDE.md : le cerveau partagé

Le fichier CLAUDE.md a été un travail considérable. Il décrit :

  • Les principes de conception (inspirés de Linear, Notion, Stripe).
  • Les patterns d’architecture adoptés.
  • Les contraintes métier et techniques.
  • Les bonnes pratiques du projet (TypeScript, Tailwind, sécurité).
  • Les décisions clés et leur justification.

Pourquoi ce document est-il si important ? Parce que c’est lui qui donne le contexte à Claude. Quand je lui demande de générer une nouvelle fonctionnalité, il a déjà en mémoire toute l’architecture du projet. Il ne part pas de zéro.

C’est la différence entre “avoir un assistant qui code” et “avoir un coéquipier qui comprend le projet”. Le CLAUDE.md transforme l’IA d’un générateur de code en un outil de conception.


L’IA comme outil principal : une relation qui s’apprend

J’ai utilisé Claude Code intensivement, non pas en mode “génère-moi ceci”, mais en mode dialogue.

Le cycle de travail

  1. Je pose une question – une problématique d’architecture ou de conception.
  2. Claude propose – une solution, des alternatives, parfois des contre-arguments.
  3. Je creuse – je demande des précisions, je confronte avec le cahier des charges.
  4. Je tranche – c’est moi qui décide de la direction à prendre.
  5. Claude génère le code – une première version structurée.
  6. Je relis – chaque ligne, chaque fonction, chaque structure.
  7. Je reconduis – je reprends ce qui est bon, je modifie ce qui ne l’est pas.

Ce cycle fait avancer le projet à une vitesse que je n’aurais pas pu atteindre seul. Mais c’est moi qui conduis.

Trouver les points de friction

Mon vrai travail, sur ce projet, a été de découvrir les points de friction :

  • L’IA génère une structure de données. Est-elle adaptée aux requêtes qu’on va faire ?
  • Elle propose une logique d’affectation. Tient-elle la route dans tous les cas ?
  • Elle suggère un pattern d’authentification. Est-il compatible avec la RLS ?

Ces questions, c’est moi qui les pose. Parce que je connais le projet, le contexte, le cahier des charges. L’IA ne peut pas les voir venir.

Reconnaître les patterns, pas coudre des solutions

Je ne réinvente pas la roue. Quand un pattern existe déjà (React, Supabase, Next.js), je l’utilise. Mon travail consiste à :

  • Reconnaître les patterns qui s’appliquent à mon problème.
  • Les adapter au contexte de TAP (temporalité, données de santé, RGPD).
  • Les intégrer proprement.

L’IA génère le squelette. Moi, j’assemble les pièces.

Le skill pour la documentation

J’ai développé un skill Claude pour produire de la documentation au format DOCX. Il prend le code, les ADR, le contexte, et génère une documentation structurée, prête à être partagée avec l’équipe ou le chef de projet.

La documentation est le ciment du projet. Sans elle, le code devient vite incompréhensible.


L’observation terrain : la dernière pièce

Contrairement au “design thinking”, je n’ai pas commencé par observer les régulatrices. Le projet a été construit à partir du cahier des charges, des spécifications recueillies, de la documentation réglementaire, et de ma connaissance des systèmes de régulation.

L’observation terrain est la dernière pièce. Elle viendra après le déploiement :

  1. Mise en ligne sur OVHCloud.
  2. Déploiement progressif auprès de sociétés pilotes.
  3. Observation des régulatrices en situation réelle.
  4. Ajustements basés sur les retours.

Le produit est conçu à partir des besoins exprimés et du cadre défini. Puis il est confronté à la réalité du terrain. C’est cette confrontation qui permet d’affiner, pas l’observation préalable qui ne vaut jamais un vrai usage en conditions réelles.


Ce que j’ai retenu de ce projet

La sécurité n’est pas une option

C’est une contrainte qui structure tout. Le RGPD, les données de santé, la RLS, le chiffrement, les en-têtes HTTP – chaque choix technique a été filtré par le prisme de la sécurité. C’est ce qui donne sa cohérence au projet.

L’IA est un outil, pas un magicien

L’IA génère du code. C’est moi qui le relis, le comprends, le corrige, le reconduis. C’est moi qui pose les bonnes questions, qui détecte les points de friction, qui valide les choix. L’IA est un accélérateur de réflexion, pas un remplaçant.

Le contexte est plus important que le code

Un projet sans contexte tourne en rond. Le CLAUDE.md, les ADR, le cahier des charges – c’est ce qui structure la réflexion. C’est ce qui permet de prendre des décisions cohérentes sur la durée.

La documentation n’est pas optionnelle

J’ai passé des heures à documenter. Des ADR, des specs, des commentaires dans le code. C’est un investissement qui a payé à chaque fois que je suis revenu sur une partie du projet après plusieurs semaines.

L’observation est la dernière étape, pas la première

On ne conçoit pas un produit en observant des utilisateurs. On le conçoit à partir d’un besoin exprimé et d’un cadre défini. Et on l’observe pour l’affiner. L’ordre est important.


Perspectives

Le projet est en phase finale. Prochaines étapes :

  1. Mise en ligne sur OVHCloud.
  2. Déploiement progressif auprès de 3 sociétés pilotes.
  3. Observation terrain et ajustements.
  4. Extension à d’autres sociétés.

En résumé

TAP est un projet réel : un cahier des charges professionnel, des utilisateurs réels, des contraintes réelles (données de santé, RGPD, géographie), une sécurité pensée dès la conception, une stack moderne, et une approche de l’IA assumée. L’IA génère, je relis et je reconduis. Le contexte, documenté dans le CLAUDE.md et les ADR, est ce qui fait la différence.