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