Initial commit: Family Planner application
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>
This commit is contained in:
492
planning-ingestion/README.md
Normal file
492
planning-ingestion/README.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user