Complete family planning application with: - React frontend with TypeScript - Node.js/Express backend with TypeScript - Python ingestion service for document processing - Planning ingestion service with LLM integration - Shared UI components and type definitions - OAuth integration for calendar synchronization - Comprehensive documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
Planning Ingestion Service
Service Node.js professionnel d'ingestion et normalisation de plannings. Supporte images (OCR), PDF et Excel avec normalisation intelligente vers un format JSON standard unique via heuristiques et LLM (OpenAI/Anthropic).
Caractéristiques
- ✅ Ingestion multi-format : Images (OCR Tesseract), PDF, Excel (.xlsx/.xls)
- ✅ Normalisation intelligente : Heuristiques + fallback LLM si ambiguïté
- ✅ JSON standard unique : Format contractuel pour toute sortie
- ✅ Sécurité : Clé API chiffrée AES-256-GCM, jamais en clair
- ✅ CLI & HTTP : Interface en ligne de commande et API REST
- ✅ Persistance : SQLite avec chiffrement des clés API
Architecture
planning-ingestion/
├── src/
│ ├── types/schema.ts # Schémas TypeScript & Zod
│ ├── crypto/encryption.ts # Chiffrement AES-256-GCM
│ ├── database/db.ts # SQLite (documents, schedules, api_keys)
│ ├── extractors/
│ │ ├── ocr.ts # Tesseract pour images
│ │ ├── pdf.ts # pdf-parse pour PDF
│ │ └── excel.ts # xlsx pour Excel
│ ├── normalizer/parser.ts # Heuristiques de parsing
│ ├── llm/client.ts # OpenAI & Anthropic
│ ├── services/ingestion.ts # Service principal
│ ├── cli.ts # CLI
│ └── server.ts # API HTTP
├── data/ # Base SQLite
├── uploads/ # Fichiers uploadés
├── package.json
├── tsconfig.json
└── .env
Installation
npm install
Configuration
Créer un fichier .env à la racine :
# Server
PORT=8000
NODE_ENV=development
# Security - OBLIGATOIRE : Clé maîtresse pour chiffrer les clés API (32+ caractères)
MASTER_KEY=votre-cle-securisee-minimum-32-caracteres
# Database
DATABASE_PATH=./data/planning.db
# Upload
UPLOAD_DIR=./uploads
MAX_FILE_SIZE_MB=50
# Confidence threshold (0.0-1.0) : en dessous, utilise le LLM
CONFIDENCE_THRESHOLD=0.7
⚠️ MASTER_KEY est critique : changez-la et ne la commitez jamais.
CLI
1. Configurer la clé API LLM
Saisie interactive (recommandé) :
npm run cli setup:api
# Choisir : openai ou anthropic
# Saisir la clé API
Ou avec paramètre :
npm run cli setup:api -- --provider openai
La clé est chiffrée et stockée dans SQLite, jamais en clair.
2. Ingérer un document
npm run cli ingest path/to/planning.pdf
# Retourne : Document ID: abc-123-xyz
Formats supportés : .png, .jpg, .jpeg, .pdf, .xlsx, .xls
3. Normaliser vers JSON standard
npm run cli normalize --document abc-123-xyz
# Options :
# --scope weekly|monthly
# --subject enfants|vacances
# Retourne : Schedule ID: def-456-uvw
4. Exporter le JSON
npm run cli export:schedule --id def-456-uvw --out schedule.json
Le fichier schedule.json est prêt à être consommé par votre application.
HTTP API
Démarrer le serveur
npm run build
npm start
# ou en dev :
npm run dev
Le serveur écoute sur http://localhost:8000.
Endpoints
POST /auth/api-key
Stocker la clé API LLM de manière sécurisée.
Body :
{
"provider": "openai",
"apiKey": "sk-..."
}
Response :
{
"message": "API key stored securely"
}
POST /ingest
Ingérer un fichier (multipart/form-data).
cURL :
curl -X POST http://localhost:8000/ingest \
-F "file=@planning.pdf"
Response :
{
"document_id": "abc-123-xyz",
"filename": "planning.pdf"
}
POST /ingest/normalize
Normaliser un document vers JSON standard.
Body :
{
"document_id": "abc-123-xyz",
"scope": "weekly",
"subject": "enfants"
}
Response :
{
"schedule_id": "def-456-uvw"
}
GET /schedules/:id
Récupérer le JSON standard normalisé.
cURL :
curl http://localhost:8000/schedules/def-456-uvw
Response (JSON contractuel) :
{
"version": "1.0",
"calendar_scope": "weekly",
"timezone": "Europe/Paris",
"period": {
"start": "2025-01-13",
"end": "2025-01-19"
},
"entities": ["enfants"],
"events": [
{
"title": "Mathématiques",
"date": "2025-01-13",
"start_time": "08:30",
"end_time": "10:00",
"location": "Salle B12",
"tags": ["cours"],
"notes": null,
"confidence": 0.95,
"source_cells": []
}
],
"extraction": {
"method": "pdf",
"model": "internal",
"heuristics": ["weekday-headers", "time-patterns", "date-extraction"]
}
}
GET /documents/:id
Récupérer les métadonnées d'un document.
Response :
{
"id": "abc-123-xyz",
"filename": "planning.pdf",
"document_type": "pdf",
"mime_type": "application/pdf",
"size_bytes": 245830,
"uploaded_at": "2025-01-12T14:30:00.000Z"
}
JSON Standard (Contrat de sortie)
Toute sortie respecte strictement ce schéma :
{
version: "1.0",
calendar_scope: "weekly" | "monthly",
timezone: "Europe/Paris",
period: {
start: "YYYY-MM-DD",
end: "YYYY-MM-DD"
},
entities: string[], // ex: ["enfants", "vacances"]
events: [
{
title: string,
date: "YYYY-MM-DD",
start_time: "HH:MM", // 24h
end_time: "HH:MM",
location: string | null,
tags: string[],
notes: string | null,
confidence: number, // 0.0 à 1.0
source_cells: string[] // ex: ["A1", "B2"] (Excel uniquement)
}
],
extraction: {
method: "ocr" | "pdf" | "excel" | "llm",
model: string, // "tesseract", "gpt-4o", "claude-3.5-sonnet", "internal"
heuristics: string[]
}
}
Exemples I/O
Entrée : Image scannée d'un planning hebdomadaire
Fichier : planning_semaine.jpg (scan manuscrit ou imprimé)
npm run cli ingest planning_semaine.jpg
# Document ID: img-001
npm run cli normalize --document img-001 --scope weekly --subject enfants
# Schedule ID: sch-001
npm run cli export:schedule --id sch-001 --out semaine.json
Sortie semaine.json :
{
"version": "1.0",
"calendar_scope": "weekly",
"timezone": "Europe/Paris",
"period": {
"start": "2025-01-13",
"end": "2025-01-19"
},
"entities": ["enfants"],
"events": [
{
"title": "Piscine",
"date": "2025-01-15",
"start_time": "14:00",
"end_time": "15:30",
"location": "Centre aquatique",
"tags": ["sport"],
"notes": null,
"confidence": 0.88,
"source_cells": []
}
],
"extraction": {
"method": "llm",
"model": "gpt-4o",
"heuristics": ["llm-analysis"]
}
}
Entrée : Fichier Excel avec plusieurs onglets
Fichier : planning_mensuel.xlsx
Onglet "Janvier" avec :
| Lundi | Mardi | Mercredi | Jeudi | Vendredi |
|---|---|---|---|---|
| 8h-9h : Français | 8h-9h : Maths | 8h-9h : Sport | ... | ... |
curl -X POST http://localhost:8000/ingest -F "file=@planning_mensuel.xlsx"
# { "document_id": "xls-002" }
curl -X POST http://localhost:8000/ingest/normalize \
-H "Content-Type: application/json" \
-d '{"document_id":"xls-002","scope":"monthly","subject":"enfants"}'
# { "schedule_id": "sch-002" }
curl http://localhost:8000/schedules/sch-002 > mensuel.json
Sortie mensuel.json :
{
"version": "1.0",
"calendar_scope": "monthly",
"timezone": "Europe/Paris",
"period": {
"start": "2025-01-01",
"end": "2025-01-31"
},
"entities": ["enfants"],
"events": [
{
"title": "Français",
"date": "2025-01-06",
"start_time": "08:00",
"end_time": "09:00",
"location": null,
"tags": ["cours"],
"notes": null,
"confidence": 0.98,
"source_cells": ["B2"]
},
{
"title": "Maths",
"date": "2025-01-07",
"start_time": "08:00",
"end_time": "09:00",
"location": null,
"tags": ["cours"],
"notes": null,
"confidence": 0.98,
"source_cells": ["C2"]
}
],
"extraction": {
"method": "excel",
"model": "internal",
"heuristics": ["weekday-headers", "hour-columns", "table-detection"]
}
}
Sécurité
- Chiffrement clé API : AES-256-GCM avec IV et Auth Tag uniques
- Master Key : Dérivée de
.envvia scrypt (salt), jamais loggée - Pas de fuite : Les endpoints ne retournent jamais de clé API en clair
- SQLite sécurisé : Table
api_keysavec colonnes chiffrées
Normalisation
Heuristiques
Le parser interne détecte :
- Jours FR : lun, mar, mer, jeu, ven, sam, dim
- Mois FR : janvier, février, ..., décembre
- Heures :
8h30,8:30,08:30→08:30 - Portée :
weeklysi semaine identifiable, sinonmonthly - Entités : mots-clés ("enfant", "vacances", "congé")
- Événements : détection de plages horaires + titres
LLM Fallback
Si confidence < CONFIDENCE_THRESHOLD (défaut 0.7), appel LLM avec prompt strict retournant uniquement le JSON contractuel.
Modèles :
- OpenAI :
gpt-4o - Anthropic :
claude-3-5-sonnet-20241022
Validation
Tous les JSON produits sont validés par Zod selon le schéma StandardScheduleSchema.
Logs
Logs sobres sans données sensibles. En production, configurer un système de monitoring (ex: Winston transports vers fichier).
Tests
Ajoutez vos tests unitaires/intégration (ex: Vitest, Jest) :
npm test
Build
npm run build
# Produit dist/
node dist/server.js
Déploiement
- Clonez le repo sur le serveur
- Créez
.envavecMASTER_KEYsécurisée - Installez :
npm install - Configurez la clé API :
npm run cli setup:api -- --provider openai - Buildez :
npm run build - Lancez :
npm start
Utilisez PM2, systemd ou Docker pour la production.
Troubleshooting
Erreur "MASTER_KEY not set" :
- Vérifiez
.envavecMASTER_KEYde 32+ caractères
LLM ne se déclenche pas :
- Vérifiez que la confiance est <
CONFIDENCE_THRESHOLD - Vérifiez que la clé API est configurée :
npm run cli setup:api
OCR imprécis :
- Utilisez des images haute résolution
- Pré-traitez l'image (contraste, rotation)
- Le LLM sera appelé si la confiance est faible
Excel mal parsé :
- Vérifiez le format des cellules (dates, heures)
- Le LLM peut corriger les ambiguïtés
Licence
Propriétaire. Tous droits réservés.