Recosante
No description available
Install / Use
/learn @SocialGouv/RecosanteREADME
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
node-connect
352.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
352.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
