Files
FamilyPlanner/planning-ingestion/README.md
philippe fdd72c1135 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>
2025-10-14 10:43:33 +02:00

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 .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:3008: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) :

npm test

Build

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.