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>
493 lines
10 KiB
Markdown
493 lines
10 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Créer un fichier `.env` à la racine :
|
|
|
|
```env
|
|
# 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é) :
|
|
|
|
```bash
|
|
npm run cli setup:api
|
|
# Choisir : openai ou anthropic
|
|
# Saisir la clé API
|
|
```
|
|
|
|
Ou avec paramĂštre :
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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** :
|
|
|
|
```json
|
|
{
|
|
"provider": "openai",
|
|
"apiKey": "sk-..."
|
|
}
|
|
```
|
|
|
|
**Response** :
|
|
|
|
```json
|
|
{
|
|
"message": "API key stored securely"
|
|
}
|
|
```
|
|
|
|
#### `POST /ingest`
|
|
|
|
Ingérer un fichier (multipart/form-data).
|
|
|
|
**cURL** :
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/ingest \
|
|
-F "file=@planning.pdf"
|
|
```
|
|
|
|
**Response** :
|
|
|
|
```json
|
|
{
|
|
"document_id": "abc-123-xyz",
|
|
"filename": "planning.pdf"
|
|
}
|
|
```
|
|
|
|
#### `POST /ingest/normalize`
|
|
|
|
Normaliser un document vers JSON standard.
|
|
|
|
**Body** :
|
|
|
|
```json
|
|
{
|
|
"document_id": "abc-123-xyz",
|
|
"scope": "weekly",
|
|
"subject": "enfants"
|
|
}
|
|
```
|
|
|
|
**Response** :
|
|
|
|
```json
|
|
{
|
|
"schedule_id": "def-456-uvw"
|
|
}
|
|
```
|
|
|
|
#### `GET /schedules/:id`
|
|
|
|
Récupérer le JSON standard normalisé.
|
|
|
|
**cURL** :
|
|
|
|
```bash
|
|
curl http://localhost:8000/schedules/def-456-uvw
|
|
```
|
|
|
|
**Response** (JSON contractuel) :
|
|
|
|
```json
|
|
{
|
|
"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** :
|
|
|
|
```json
|
|
{
|
|
"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 :
|
|
|
|
```typescript
|
|
{
|
|
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é)
|
|
|
|
```bash
|
|
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`** :
|
|
|
|
```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 | ... | ... |
|
|
|
|
```bash
|
|
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`** :
|
|
|
|
```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 `.env` via scrypt (salt), jamais loggée
|
|
- **Pas de fuite** : Les endpoints ne retournent jamais de clé API en clair
|
|
- **SQLite sécurisé** : Table `api_keys` avec 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** : `weekly` si semaine identifiable, sinon `monthly`
|
|
- **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) :
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
## Build
|
|
|
|
```bash
|
|
npm run build
|
|
# Produit dist/
|
|
node dist/server.js
|
|
```
|
|
|
|
## Déploiement
|
|
|
|
1. Clonez le repo sur le serveur
|
|
2. Créez `.env` avec `MASTER_KEY` sécurisée
|
|
3. Installez : `npm install`
|
|
4. Configurez la clé API : `npm run cli setup:api -- --provider openai`
|
|
5. Buildez : `npm run build`
|
|
6. Lancez : `npm start`
|
|
|
|
Utilisez PM2, systemd ou Docker pour la production.
|
|
|
|
## Troubleshooting
|
|
|
|
**Erreur "MASTER_KEY not set"** :
|
|
|
|
- Vérifiez `.env` avec `MASTER_KEY` de 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.
|