SkillAgentSearch skills...

Recosante

No description available

Install / Use

/learn @SocialGouv/Recosante
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Recosante

Recosante : Un service public numérique de recommandations d'actions pour réduire l'impact de l'environnement sur sa santé.

Accessible sur https://recosante.beta.gouv.fr/

Ce dépôt est un monorepo créé pour faciliter le déploiement sur l'infrastructure de la Fabrique numérique des ministères sociaux.

Les sous-dossiers ont été repris du travail effectué par l'équipe dédiée de beta.gouv.fr, à partir des dépôts suivants :

  • https://github.com/betagouv/recosante : frontend
  • https://github.com/betagouv/recosante-api : API Node.js, data, envoi des emails
  • https://github.com/betagouv/recosante-mail : templates email

L'application mobile est développée dans un dépôt séparé :

  • https://github.com/SocialGouv/recosante-expo-app : application mobile React Native

Brève description

Recosanté est composé de trois services :

  • Une API d'exposition des indicateurs qui est dans ce dépot, techniquement il s'agit d'une API écrite en Node.js avec Express. Cette API sert aussi à gérer les abonnements au service.

  • Un service qui sauvegarde les différents indices (indice ATMO, épisodes de pollution, Risque d'allergie lié à l'exposition aux pollens (RAEP), vigilance météo, indice UV). Le code de ce service se trouve dans le dossier api-node/src/aggregators.

Les données sont stockées dans une base de données PostgreSQL, dans le schéma public pour les données d'abonnements et les données de prévisions des différents indices renvoyés.

Structure et projets

api-node

Ce projet utilise Node.js avec Express etcontient une API qui sert la data nécessaire au site web. Il comporte un schema propre lui permettant de stocker les utilisateurs. Il utilise Prisma comme ORM pour interroger la base de données PostgreSQL.

Plus d'information dans le README.md du projet.

frontend

Ce projet est le site recosanté. Il utilise Next.JS.

Plus d'information dans le README.md du projet.

Application mobile

L'application mobile Recosanté est développée en React Native avec Expo et consomme l'API Node.js. Elle est hébergée dans un dépôt séparé : recosante-expo-app.

Plus d'information dans le README.md du projet mobile.

Stack technique

Notre stack technique est principalement composée de :

  • front-end : React, Next.js.
  • mobile : React Native, Expo.
  • back-end : Node.js, Express, Prisma, PostgreSQL.
  • hébergement et autres services : Docker, Kubernetes.

Architecture Node.js - Clean Architecture

L'API Node.js suit les principes de la Clean Architecture avec une séparation claire des responsabilités et des couches bien définies.

🏗️ Structure des couches

src/
├── controllers/          # Couche Interface (HTTP)
├── services/            # Couche Application (Logique métier)
├── aggregators/         # Couche Infrastructure (Récupération données externes)
├── getters/             # Couche Infrastructure (Accès aux données)
├── cronjobs/            # Couche Infrastructure (Tâches planifiées)
├── middlewares/         # Couche Interface (Validation, Auth, etc.)
├── schemas/             # Couche Interface (Validation Zod)
├── types/               # Couche Domain (Types TypeScript)
└── utils/               # Couche Infrastructure (Utilitaires)

🔧 Composants principaux

Controllers (Interface Layer)

  • Responsabilité : Gestion des requêtes HTTP et orchestration
  • Principe : Couche la plus externe, ne contient que la logique de routage
  • Exemple : user.ts, indicators.ts, notification.ts
  • Pattern : Validation → Service → Réponse
// Exemple de contrôleur
export async function updateUser(req: Request, res: Response) {
  const validatedData = await validateBody(updateUserSchema)(req);
  const result = await UserService.updateUser(matomoId, validatedData, req.headers);
  res.json(result);
}

Services (Application Layer)

  • Responsabilité : Logique métier et orchestration des cas d'usage
  • Principe : Indépendant de l'infrastructure, testable en isolation
  • Exemple : user.service.ts, eventService.ts
  • Pattern : Use Cases, Business Rules
// Exemple de service
export async function updateUser(matomoId: string, updateData: UserUpdateData): Promise<User> {
  // Logique métier : normalisation des données
  const normalizedData = normalizeUserData(updateData);
  
  // Orchestration : appel aux repositories
  const user = await prisma.user.upsert({
    where: { matomo_id: matomoId },
    update: normalizedData,
    create: { matomo_id: matomoId, ...normalizedData }
  });
  
  return user;
}

Aggregators (Infrastructure Layer)

  • Responsabilité : Récupération et agrégation des données externes
  • Principe : Isolation des APIs externes, gestion des erreurs
  • Exemple : indice_atmo.ts, pollens/, weather_alert.ts
  • Pattern : Adapter Pattern, Retry Logic
// Exemple d'aggregator
export async function getAtmoIndicator(): Promise<void> {
  try {
    // Récupération des données externes
    const externalData = await fetchAtmoData();
    
    // Transformation et validation
    const processedData = transformAtmoData(externalData);
    
    // Persistance
    await prisma.indiceAtmospheric.createMany({
      data: processedData,
      skipDuplicates: true
    });
  } catch (error) {
    capture(error, { extra: { functionCall: 'getAtmoIndicator' } });
    throw error;
  }
}

Getters (Infrastructure Layer)

  • Responsabilité : Accès aux données pour l'API
  • Principe : Interface uniforme pour la récupération des données
  • Exemple : indice_atmo.ts, pollens.ts, weather_alert.ts
  • Pattern : Repository Pattern, Data Access Layer
// Exemple de getter
export async function getIndiceAtmoFromMunicipalityAndDate({
  municipality_insee_code,
  date_UTC_ISO
}: GetIndiceAtmoParams): Promise<Indicator> {
  // Validation des paramètres
  validateParams({ municipality_insee_code, date_UTC_ISO });
  
  // Récupération des données
  const data = await prisma.indiceAtmospheric.findFirst({
    where: {
      municipality_insee_code,
      validity_start: { lte: new Date(date_UTC_ISO) },
      validity_end: { gte: new Date(date_UTC_ISO) }
    }
  });
  
  // Transformation en format API
  return transformToIndicatorFormat(data);
}

Cronjobs (Infrastructure Layer)

  • Responsabilité : Exécution des tâches planifiées
  • Principe : Orchestration des aggregators, gestion des erreurs
  • Exemple : aggregators.ts, notifications.ts, cleaning.ts
  • Pattern : Scheduler Pattern, Error Recovery
// Exemple de cronjob
export async function initAggregators() {
  const aggregators = [
    () => getAtmoIndicator(),
    () => getIndiceUVIndicator(),
    () => getWeatherAlert(),
    () => getBathingWaterIndicator(),
    () => getPollensIndicator(pollensLoggerUtils, pollensApiService)
  ];
  
  for (const aggregator of aggregators) {
    try {
      await aggregator();
    } catch (error) {
      capture(error, { extra: { functionCall: 'initAggregators' } });
      // Continue avec les autres aggregators
    }
  }
}

🧪 Architecture des tests

Tests unitaires (__tests__/unit/)

  • Objectif : Tester les composants en isolation
  • Couverture : Services, Utils, Schemas, Event Handlers
  • Pattern : Mock des dépendances externes
  • Configuration : jest.unit.config.cjs
// Exemple de test unitaire
describe('UserService', () => {
  it('should normalize favorite indicators array', async () => {
    const mockPrisma = {
      user: {
        upsert: jest.fn().mockResolvedValue(mockUser)
      }
    };
    
    const result = await UserService.updateUser('test-id', {
      favorite_indicators: ['pollen_allergy', 'weather_alert']
    });
    
    expect(result.favorite_indicator).toBe('pollen_allergy');
  });
});

Tests d'intégration (__tests__/integration/)

  • Objectif : Tester les interactions entre composants
  • Couverture : Controllers, Base de données, APIs externes
  • Pattern : Base de données de test, Mocks partiels
  • Configuration : jest.integration.config.cjs
// Exemple de test d'intégration
describe('User Controller Integration', () => {
  beforeEach(async () => {
    await prisma.user.deleteMany();
  });
  
  it('should create user and return correct response', async () => {
    const response = await request(app)
      .post('/user')
      .send({ matomo_id: 'test-123' });
    
    expect(response.status).toBe(200);
    expect(response.body.matomo_id).toBe('test-123');
  });
});

🔄 Flux de données

Requête API (Read)

Client → Controller → Service → Getter → Database → Response

Tâche planifiée (Write)

Cronjob → Aggregator → External API → Transform → Database

Gestion d'événements

Event → EventHandler → Service → Database → Notification

🛡️ Principes appliqués

Dependency Inversion

  • Les services ne dépendent pas directement de Prisma
  • Injection de dépendances via interfaces
  • Tests facilités par le mocking

Single Responsibility

  • Chaque composant a une responsabilité unique
  • Controllers : HTTP, Services : Logique métier, Aggregators : Données externes

Open/Closed Principle

  • Extension via nouveaux aggregators sans modification du code existant
  • Ajout de nouveaux indicateurs sans impact sur l'architecture

Interface Segregation

  • Interfaces spécifiques par type de données
  • Schemas Zod pour la validation
  • Types TypeScript stricts

📊 Métriques et monitoring

Logging structuré

console.log(`[INDICE ATMO] Duration: ${Date.now() - now}ms`.padEnd(40), step);

**Error

Related Skills

View on GitHub
GitHub Stars7
CategoryDevelopment
Updated1mo ago
Forks1

Languages

PLpgSQL

Security Score

80/100

Audited on Feb 24, 2026

No findings