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:
philippe
2025-10-14 10:43:33 +02:00
commit fdd72c1135
239 changed files with 44160 additions and 0 deletions

View 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.