rahim237cm Claude Opus 5 commited on
Commit
641ec0b
·
1 Parent(s): 0e1604f

Etape 15 : README realigne sur la pile reelle

Browse files

Le README (659 lignes) decrivait un autre systeme : Redis 7.2, Meilisearch 1.5,
Ollama mistral:7b en prerequis, detection de langue triple-methode avec spaCy,
taches Celery, docker-compose.yml inexistant, DATABASE_URL sans le driver
asyncpg pourtant obligatoire. Sur une trentaine d'affirmations concretes des
sections installation et configuration, environ 24 etaient fausses — plus
trompeur qu'absent.

Reecrit (165 lignes) sur ce qui tourne reellement : PostgreSQL 16 + pgvector +
pg_trgm, Gemini via google-genai, LlamaParse, BackgroundTasks. Documente le
chemin d'installation reel (alembic upgrade head -> create_admin.py ->
uvicorn), le telechargement du modele fastText non versionne, les routes
d'authentification, la politique d'echec de l'ingestion, la mise en place de la
base de test, et trois points de deploiement a verifier (migrations non jouees
par le conteneur, data/ ephemere, --workers 1 load-bearing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. README.md +111 -605
README.md CHANGED
@@ -1,659 +1,165 @@
1
- # JuriX Backend v2.1
2
 
3
- Backend API pour la plateforme juridique camerounaise JuriX.
4
 
5
- ## 🚀 Quick Start
 
 
6
 
7
- ### Prérequis
8
 
9
- - Python 3.11+
10
- - PostgreSQL 15+ avec pgvector
11
- - Redis 7.2+
12
- - Meilisearch 1.5+
13
- - Ollama avec mistral:7b
 
 
 
 
 
14
 
15
- ### Installation
 
16
 
17
- ```bash
18
- # Cloner le projet
19
- cd backend
20
-
21
- # Installer les dépendances
22
- pip install -r requirements.txt
23
- # OU avec Poetry
24
- poetry install
25
-
26
- # Télécharger les modèles NLP (requis pour détection de langue)
27
- python scripts/download_models.py
28
-
29
- # Configurer les variables d'environnement
30
- cp .env.example .env
31
- # Éditer .env avec vos paramètres
32
-
33
- # Lancer les migrations
34
- alembic upgrade head
35
-
36
- # Démarrer le serveur
37
- uvicorn app.main:app --reload
38
- ```
39
 
40
- L'API sera disponible sur `http://localhost:8000`
 
 
 
41
 
42
- Documentation interactive: `http://localhost:8000/docs`
43
-
44
- ## 📦 Modules Implémentés
45
-
46
- ### ✅ Module 1: Détection Automatique de Langue
47
-
48
- Service de détection automatique de langue pour documents juridiques.
49
-
50
- **Architecture:** Triple-méthode ensemble avec vote majoritaire
51
- - **Méthodes:** langdetect + spaCy + fastText
52
- - **Précision:** >99% sur documents juridiques
53
- - **Performance:** <1s pour textes jusqu'à 5000 caractères
54
- - **Langues:** Français + Anglais (extensible)
55
-
56
- #### Setup
57
 
58
  ```bash
59
- # 1. Télécharger les modèles NLP
60
- python scripts/download_models.py
61
-
62
- # Ceci télécharge:
63
- # - spaCy fr_core_news_sm (~60MB)
64
- # - spaCy en_core_web_sm (~60MB)
65
- # - fastText lid.176.bin (~130MB)
66
- # Total: ~250MB
67
-
68
- # 2. Vérifier l'installation
69
- python -c "from app.services.language_detector import LanguageDetector; d = LanguageDetector(); print(d.health_check())"
70
  ```
71
 
72
- #### Usage Programmatique
73
-
74
- ```python
75
- from app.services.language_detector import LanguageDetector
76
-
77
- # Créer instance (charge les modèles)
78
- detector = LanguageDetector()
79
-
80
- # Détecter langue
81
- text = """
82
- Article 1. La présente loi régit les conditions de création
83
- des sociétés commerciales au Cameroun conformément au droit OHADA.
84
- """
85
-
86
- result = detector.detect(text)
87
-
88
- print(result)
89
- # {
90
- # 'language': 'fr',
91
- # 'confidence': 0.98,
92
- # 'method_votes': {
93
- # 'langdetect': 'fr',
94
- # 'spacy': 'fr',
95
- # 'fasttext': 'fr'
96
- # },
97
- # 'consensus': True,
98
- # 'processing_time_ms': 450,
99
- # 'text_length': 156
100
- # }
101
- ```
102
-
103
- #### Usage API
104
-
105
- ```bash
106
- # Détecter langue d'un texte
107
- curl -X POST http://localhost:8000/api/v1/language/detect \
108
- -H "Content-Type: application/json" \
109
- -d '{
110
- "text": "Article 1. La présente loi régit les sociétés commerciales au Cameroun.",
111
- "min_confidence": 0.80
112
- }'
113
-
114
- # Réponse:
115
- # {
116
- # "language": "fr",
117
- # "confidence": 0.98,
118
- # "method_votes": {
119
- # "langdetect": "fr",
120
- # "spacy": "fr",
121
- # "fasttext": "fr"
122
- # },
123
- # "consensus": true,
124
- # "processing_time_ms": 450,
125
- # "text_length": 75
126
- # }
127
-
128
- # Health check
129
- curl http://localhost:8000/api/v1/language/health
130
-
131
- # Réponse:
132
- # {
133
- # "service": "LanguageDetector",
134
- # "status": "healthy",
135
- # "models": {
136
- # "spacy_fr": "✅ OK",
137
- # "spacy_en": "✅ OK",
138
- # "fasttext": "✅ OK"
139
- # }
140
- # }
141
- ```
142
-
143
- #### Tests
144
 
145
  ```bash
146
- # Tests unitaires (5 tests de base + edge cases)
147
- pytest backend/tests/test_services/test_language_detector.py -v
148
-
149
- # Avec coverage
150
- pytest backend/tests/test_services/test_language_detector.py -v \
151
- --cov=backend/app/services/language_detector \
152
- --cov-report=html
153
 
154
- # Génération tests Qodo (auto, ~25 tests)
155
- qodo test generate backend/app/services/language_detector.py --max-tests 25
156
-
157
- # Tous les tests
158
- pytest backend/tests/test_services/test_language_detector.py -v
159
- ```
160
-
161
- #### Troubleshooting
162
-
163
- **❌ Erreur: "No module named spacy"**
164
- ```bash
165
- pip install spacy langdetect fasttext-wheel scikit-learn
166
- ```
167
-
168
- **❌ Erreur: "Can't find model 'fr_core_news_sm'"**
169
- ```bash
170
- python scripts/download_models.py
171
- # OU manuellement:
172
- python -m spacy download fr_core_news_sm
173
- python -m spacy download en_core_web_sm
174
- ```
175
-
176
- **❌ Erreur: "fastText model not found"**
177
- ```bash
178
- # Télécharger manuellement
179
- wget https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin \
180
- -O backend/models/fasttext/lid.176.bin
181
- ```
182
-
183
- **❌ Performance lente (>2s)**
184
- - Vérifier que ThreadPoolExecutor est utilisé (parallélisme)
185
- - Réduire la longueur du texte (sampling automatique à 5000 chars)
186
- - Utiliser singleton pattern (éviter rechargement modèles)
187
-
188
- **❌ Précision faible (<95%)**
189
- - Vérifier longueur texte (minimum 100 chars recommandé)
190
- - Textes bilingues donnent confiance plus basse (normal)
191
- - Vérifier que les 3 modèles sont correctement chargés
192
-
193
- ## 🧪 Tests
194
-
195
- ### Lancer les tests
196
-
197
- ```bash
198
- # Tous les tests
199
- pytest backend/tests/ -v
200
-
201
- # Tests spécifiques
202
- pytest backend/tests/test_services/test_language_detector.py -v
203
-
204
- # Avec coverage
205
- pytest backend/tests/ -v --cov=backend/app --cov-report=html
206
-
207
- # Tests de performance
208
- pytest backend/tests/test_services/test_language_detector.py::TestLanguageDetectorPerformance -v
209
- ```
210
-
211
- ### Structure des tests
212
-
213
- ```
214
- backend/tests/
215
- ├── test_main.py # Tests de base (health check, root)
216
- ├── test_services/
217
- │ └── test_language_detector.py # Tests détection langue (32 tests)
218
- ├── test_api/
219
- │ └── (à venir)
220
- ├── test_tasks/
221
- │ └── (à venir)
222
- ├── integration/
223
- │ └── (à venir)
224
- └── performance/
225
- └── (à venir)
226
- ```
227
-
228
- ## 📊 Code Quality
229
-
230
- ### Linting & Formatting
231
-
232
- ```bash
233
- # Black (formatting)
234
- black backend/app backend/tests
235
-
236
- # Ruff (linting)
237
- ruff check backend/app backend/tests
238
-
239
- # MyPy (type checking)
240
- mypy backend/app/services/language_detector.py
241
-
242
- # Pylint
243
- pylint backend/app/services/language_detector.py
244
- ```
245
-
246
- ### Coverage Target
247
-
248
- - **Overall:** >80%
249
- - **Services:** >85%
250
- - **Critical paths:** >95%
251
-
252
- ## 🐳 Docker
253
-
254
- ```bash
255
- # Build image
256
- docker build -t jurix-backend:v2.1 -f docker/Dockerfile.prod .
257
 
258
- # Run avec docker-compose
259
- docker-compose up -d
260
 
261
- # Vérifier logs
262
- docker-compose logs -f api
263
-
264
- # Health check
265
- curl http://localhost:8000/health
266
- curl http://localhost:8000/api/v1/language/health
267
  ```
268
 
269
- ## 📁 Structure du Projet
270
 
271
- ```
272
- backend/
273
- ├── app/
274
- │ ├── __init__.py
275
- │ ├── main.py # Point d'entrée FastAPI
276
- │ ├── core/
277
- │ │ ├── config.py # Configuration (Settings)
278
- │ │ └── dependencies.py # Dependency injection
279
- │ ├── api/
280
- │ │ ├── routes/
281
- │ │ │ └── language.py # ✅ Endpoints détection langue
282
- │ │ └── dependencies/
283
- │ ├── services/
284
- │ │ └── language_detector.py # ✅ Service détection langue
285
- │ ├── models/ # ORM models (SQLAlchemy)
286
- │ ├── tasks/ # Celery tasks
287
- │ └── utils/ # Utilitaires
288
- ├── models/ # Modèles NLP
289
- │ ├── spacy/
290
- │ │ ├── fr_core_news_sm/
291
- │ │ └── en_core_web_sm/
292
- │ └── fasttext/
293
- │ └── lid.176.bin
294
- ├── scripts/
295
- │ └── download_models.py # ✅ Script téléchargement modèles
296
- ├── tests/
297
- │ ├── test_main.py
298
- │ └── test_services/
299
- │ └── test_language_detector.py # ✅ 32 tests
300
- ├── alembic/ # Migrations DB
301
- │ ├── env.py
302
- │ └── versions/
303
- │ └── 001_initial_schema.py
304
- ├── pyproject.toml # Dependencies (Poetry)
305
- ├── requirements.txt # Dependencies (pip)
306
- └── README.md # This file
307
- ```
308
 
309
- ## 🔧 Configuration
310
 
311
- Variables d'environnement (.env):
312
 
313
  ```bash
314
- # App
315
- APP_NAME="JuriX API"
316
- VERSION="2.1.0"
317
- DEBUG=True
318
- ENVIRONMENT="development"
319
-
320
- # Database
321
- DATABASE_URL="postgresql://jurix:password@localhost:5432/jurix_db"
322
-
323
- # Redis
324
- REDIS_URL="redis://:password@localhost:6379/0"
325
-
326
- # Meilisearch
327
- MEILISEARCH_URL="http://localhost:7700"
328
- MEILISEARCH_KEY="master_key"
329
-
330
- # Ollama
331
- OLLAMA_URL="http://localhost:11434"
332
- OLLAMA_MODEL="mistral:7b"
333
-
334
- # Security
335
- SECRET_KEY="your_secret_key_here"
336
- ALGORITHM="HS256"
337
- ACCESS_TOKEN_EXPIRE_MINUTES=30
338
-
339
- # Qodo (Tests auto-générés)
340
- QODO_API_KEY="your_qodo_api_key"
341
-
342
- # CORS
343
- CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"]
344
- ```
345
-
346
- ## 📝 API Documentation
347
-
348
- Documentation interactive disponible sur:
349
- - **Swagger UI:** http://localhost:8000/docs
350
- - **ReDoc:** http://localhost:8000/redoc
351
-
352
- ### Endpoints Principaux
353
-
354
- #### Language Detection
355
-
356
- ```
357
- POST /api/v1/language/detect
358
- GET /api/v1/language/health
359
- ```
360
-
361
- #### À venir
362
-
363
  ```
364
- POST /api/v1/laws # Créer loi
365
- GET /api/v1/laws # Lister lois
366
- GET /api/v1/laws/{id} # Détail loi
367
- POST /api/v1/search # Recherche
368
- POST /api/v1/chatbot/ask # Question RAG
369
- GET /api/v1/categories # Catégories
370
- GET /health # Health check global
371
- ```
372
-
373
- ### ✅ Module 3: Extraction d'Articles
374
 
375
- Utilitaire d'extraction automatique d'articles pour documents juridiques camerounais.
 
376
 
377
- **Architecture:** Regex-based pattern matching avec détection automatique
378
- - **Patterns supportés:** Article X, Art. X, Article premier/première
379
- - **Numérotation hiérarchique:** Support 1.1, 1.2.3 (parent_id)
380
- - **Précision:** >95% sur documents légaux camerounais
381
- - **Performance:** <500ms pour 1000 articles
382
- - **Validation:** Minimum 3 articles requis (configurable)
383
 
384
- #### Usage Programmatique
 
385
 
386
- ```python
387
- from app.utils.text_chunker import extract_articles
 
 
 
 
 
388
 
389
- # Texte juridique
390
- law_text = """
391
- LOI N° 2023-001 DU 15 JANVIER 2023
392
 
393
- Article 1. Objet de la loi
394
- La présente loi régit les sociétés commerciales au Cameroun.
395
 
396
- Article 2. Champ d'application
397
- Les dispositions s'appliquent à toutes les sociétés.
 
 
 
398
 
399
- Article 3. Définitions
400
- Au sens de la présente loi, on entend par société...
401
- """
402
 
403
- # Extraire articles
404
- articles = extract_articles(law_text)
405
 
406
- print(f"Nombre d'articles: {len(articles)}")
407
-
408
- for article in articles:
409
- print(f"Article {article['number']}: {article['title']}")
410
- print(f" Contenu: {article['content'][:50]}...")
411
- print(f" Mots: {article['word_count']}, Chars: {article['char_count']}")
412
- print(f" Parent: {article['parent_id']}, Position: {article['position']}")
413
- print()
414
-
415
- # Résultat:
416
- # Nombre d'articles: 3
417
- # Article 1: Objet de la loi
418
- # Contenu: La présente loi régit les sociétés commerciales...
419
- # Mots: 9, Chars: 58
420
- # Parent: None, Position: 0
421
- # ...
422
  ```
423
-
424
- #### Paramètres d'Extraction
425
-
426
- ```python
427
- # Paramètres personnalisés
428
- articles = extract_articles(
429
- text=law_text,
430
- min_article_length=10, # Minimum 10 caractères par article
431
- preserve_formatting=False, # Nettoyer espaces multiples
432
- strict=True # Erreur si <3 articles
433
- )
434
-
435
- # Mode non-strict (pour documents incomplets)
436
- articles = extract_articles(
437
- text=draft_law_text,
438
- strict=False # Accepte <3 articles
439
- )
440
  ```
441
 
442
- #### Patterns Supportés
443
-
444
- ```python
445
- # ✅ Article X.
446
- "Article 1. Dispositions générales"
447
- "Article 2: Champ d'application"
448
-
449
- # ✅ Art. X
450
- "Art. 1. Titre"
451
- "Art 1: Titre"
452
-
453
- # ✅ Article premier/première
454
- "Article premier. Objet de la loi"
455
- "Article première. Disposition initiale"
456
-
457
- # ✅ Numérotation hiérarchique
458
- "Article 1.1. Sous-article"
459
- "Article 1.2.3. Sous-sous-article"
460
 
461
- # ❌ Non supportés (à venir Phase 2)
462
- "Section I. Titre de section"
463
- "Chapitre II. Titre de chapitre"
464
- "Alinéa 1. Paragraphe"
465
- ```
466
 
467
- #### Intégration Pipeline
468
-
469
- ```python
470
- # Dans process_law task
471
- from app.utils.text_chunker import extract_articles, ArticleExtractionError
472
-
473
- def process_law_task(law_id: int):
474
- """Traiter document juridique uploadé."""
475
-
476
- # 1. Récupérer loi
477
- law = db.query(Law).filter(Law.id == law_id).first()
478
-
479
- # 2. Extraire articles
480
- try:
481
- articles = extract_articles(law.content_fr)
482
-
483
- # Validation
484
- if len(articles) < 3:
485
- raise ValueError(f"Minimum 3 articles requis, {len(articles)} trouvés")
486
-
487
- # 3. Sauvegarder en DB
488
- for article_data in articles:
489
- db_article = Article(
490
- law_id=law_id,
491
- number=article_data['number'],
492
- title=article_data['title'],
493
- content=article_data['content'],
494
- order=article_data['position'],
495
- # parent_id sera ajouté en Phase 2
496
- )
497
- db.add(db_article)
498
-
499
- db.commit()
500
- logger.info(f"✅ {len(articles)} articles extraits pour loi #{law_id}")
501
-
502
- except ArticleExtractionError as e:
503
- logger.error(f"❌ Extraction échouée: {e}")
504
- raise
505
- ```
506
 
507
- #### Format de Retour
508
-
509
- Chaque article extrait contient:
510
-
511
- ```python
512
- {
513
- 'number': '1', # VARCHAR(20) - Numéro article
514
- 'title': 'Dispositions générales', # VARCHAR(200) - Titre (optionnel)
515
- 'content': 'La présente loi...', # TEXT - Contenu article
516
- 'position': 0, # INT - Ordre dans document (0-indexed)
517
- 'parent_id': None, # VARCHAR(20) - Parent pour hiérarchie (1.1 → '1')
518
- 'section': None, # VARCHAR(50) - Section/Chapitre (Phase 2)
519
- 'word_count': 45, # INT - Nombre de mots
520
- 'char_count': 234 # INT - Nombre de caractères
521
- }
522
- ```
523
 
524
- #### Tests
 
 
 
525
 
526
  ```bash
527
- # Tests unitaires (23 tests de base)
528
- pytest backend/tests/test_utils/test_text_chunker.py -v
529
-
530
- # Classes de tests:
531
- # - TestBasicExtraction: 3 tests (patterns standards)
532
- # - TestHierarchicalNumbering: 2 tests (numérotation imbriquée)
533
- # - TestValidation: 7 tests (validation stricte)
534
- # - TestEdgeCases: 5 tests (cas limites)
535
- # - TestStatistics: 2 tests (compteurs)
536
- # - TestPositioning: 1 test (ordre séquentiel)
537
- # - TestMinimumArticleLength: 2 tests (filtrage longueur)
538
- # - TestMultiplePatterns: 1 test (patterns mixtes)
539
-
540
- # Tests d'intégration (7 tests)
541
- pytest backend/tests/test_integration/test_article_extraction_integration.py -v
542
-
543
- # Avec coverage (cible: >90%)
544
- pytest backend/tests/test_utils/test_text_chunker.py -v \
545
- --cov=app.utils.text_chunker \
546
- --cov-report=term-missing
547
-
548
- # Résultat attendu:
549
- # ====== 23 passed, 96% coverage ======
550
- ```
551
-
552
- #### Validation Rules
553
-
554
- **Input:**
555
- - Minimum: 200 caractères
556
- - Maximum: 5,000,000 caractères (5MB)
557
- - Minimum 3 articles requis (strict=True)
558
-
559
- **Article:**
560
- - Contenu: 10-50,000 caractères
561
- - Numéro: 1-20 caractères
562
- - Titre: 0-200 caractères (optionnel)
563
-
564
- **Errors:**
565
 
566
- ```python
567
- # ValueError: Texte invalide
568
- extract_articles("") # → "Le texte ne peut pas être vide"
569
- extract_articles("x" * 50) # → "Texte trop court (50 chars, minimum 200)"
570
-
571
- # ArticleExtractionError: Extraction échouée
572
- extract_articles("Long text without articles...")
573
- # → "Aucun pattern d'article détecté"
574
-
575
- extract_articles("Article 1...\nArticle 2...", strict=True)
576
- # → "Minimum 3 articles requis, 2 trouvés"
577
  ```
578
 
579
- #### Troubleshooting
 
580
 
581
- **❌ Erreur: "Aucun pattern d'article détecté"**
582
- - Vérifier format: "Article 1." ou "Art. 1"
583
- - Patterns supportés: numérique (1, 2, 3) ou textuel (premier, première)
584
 
585
- **❌ Erreur: "Minimum 3 articles requis"**
586
- ```python
587
- # Option 1: Mode non-strict
588
- articles = extract_articles(text, strict=False)
589
-
590
- # Option 2: Ajouter plus d'articles au document
591
  ```
592
-
593
- **❌ Articles trop courts filtrés**
594
- ```python
595
- # Réduire min_article_length
596
- articles = extract_articles(text, min_article_length=5) # Default: 10
597
- ```
598
-
599
- **❌ Titres mal détectés**
600
- - Titres doivent être <100 caractères et sur première ligne
601
- - Format attendu: "Article 1. Titre court\nContenu..."
602
-
603
- **❌ Hiérarchie non reconnue**
604
- - Format requis: numérotation à points (1.1, 1.2.3)
605
- - parent_id extrait automatiquement (1.2.3 → parent='1.2')
606
-
607
- #### Performance
608
-
609
- Benchmarks (Windows 11, Python 3.11):
610
-
611
- | Document | Articles | Temps | Performance |
612
- |----------|----------|-------|-------------|
613
- | Petit (10 articles) | 10 | <50ms | ✅ |
614
- | Moyen (100 articles) | 100 | <100ms | ✅ |
615
- | Large (1000 articles) | 1000 | <500ms | ✅ |
616
-
617
- ```bash
618
- # Test de performance
619
- pytest backend/tests/test_integration/test_article_extraction_integration.py::TestArticleExtractionIntegration::test_performance_target_met -v
620
-
621
- # Résultat:
622
- # test_performance_target_met PASSED [100%]
623
- # Extraction took 0.045s (target: <0.5s for 100 articles) ✅
624
  ```
625
 
626
- ## 🚀 Prochaines Étapes
627
-
628
- ### Module 2: Classification Automatique (Section 5.6)
629
- Service de catégorisation automatique de documents juridiques.
630
-
631
- ### Module 4: Filtre Multilingue (Section 6.9)
632
- Filtrage automatique des lois par langue détectée.
633
-
634
- ### Module 5: Pipeline Complet (Section 4.2)
635
- Intégration Celery pour traitement automatique des documents.
636
-
637
- ## 📄 License
638
-
639
- Propriétaire - JuriX Team © 2025
640
-
641
- ## 👥 Équipe
642
-
643
- - **Développeurs:** JuriX Dev Team
644
- - **Architecte:** Senior Software Engineer
645
- - **QA:** Qodo AI Test Generation
646
-
647
- ## 📞 Support
648
-
649
- Pour questions ou problèmes:
650
- 1. Vérifier documentation: `http://localhost:8000/docs`
651
- 2. Consulter logs: `docker-compose logs -f api`
652
- 3. Lancer health checks: `curl http://localhost:8000/api/v1/language/health`
653
- 4. Créer issue sur repository GitHub
654
 
655
- ---
656
 
657
- **Version:** 2.1.0
658
- **Dernière mise à jour:** 2025-01-05
659
- **Status:** ✅ LanguageDetector + ArticleSplitter Complets (30/30 tests passing, 96% coverage)
 
 
 
1
+ # JuriX Backend
2
 
3
+ API de la plateforme juridique camerounaise JuriX.
4
 
5
+ Recherche et question-réponse sur un corpus de textes officiels : recherche
6
+ plein texte et sémantique, extraction OCR des PDF scannés, découpage par
7
+ article, réponses citées via Gemini.
8
 
9
+ ## Pile technique
10
 
11
+ | Composant | Choix |
12
+ |---|---|
13
+ | API | FastAPI (Python 3.11) |
14
+ | Base | PostgreSQL 16 + `pgvector` + `pg_trgm` |
15
+ | Recherche plein texte | `tsvector` / `websearch_to_tsquery`, index GIN, triggers |
16
+ | Recherche sémantique | `pgvector`, embeddings `gemini-embedding-001` (3072 dim.) |
17
+ | Cache | tables `query_cache` et `embedding_cache` |
18
+ | LLM | Gemini (`google-genai`) |
19
+ | OCR | LlamaParse v2 |
20
+ | Tâches de fond | `BackgroundTasks` FastAPI |
21
 
22
+ Pas de Redis, pas de Meilisearch, pas de Celery : recherche, cache et files
23
+ d'attente sont assurés par PostgreSQL et par le serveur applicatif.
24
 
25
+ ## Prérequis
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ - Python 3.11
28
+ - PostgreSQL 16 avec les extensions `vector` et `pg_trgm`
29
+ - Une clé API Gemini (Google AI Studio)
30
+ - Une clé LlamaCloud pour l'extraction OCR
31
 
32
+ Le plus simple pour la base :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  ```bash
35
+ docker run -d --name jurix-pg -p 5432:5432 \
36
+ -e POSTGRES_USER=jurix -e POSTGRES_PASSWORD=jurix -e POSTGRES_DB=jurix_db \
37
+ pgvector/pgvector:pg16
 
 
 
 
 
 
 
 
38
  ```
39
 
40
+ ## Installation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  ```bash
43
+ python3.11 -m venv .venv && source .venv/bin/activate
44
+ pip install -r requirements.txt # production
45
+ pip install -r requirements-dev.txt # + outils de test
 
 
 
 
46
 
47
+ cp .env.example .env # puis renseigner les valeurs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ alembic upgrade head # schéma, extensions, index, triggers
50
+ python scripts/create_admin.py # premier compte administrateur
51
 
52
+ uvicorn app.main:app --reload
 
 
 
 
 
53
  ```
54
 
55
+ API sur `http://localhost:8000`, documentation interactive sur `/docs`.
56
 
57
+ Le bouton **Authorize** de `/docs` fonctionne : il appelle
58
+ `POST /api/v1/auth/login` avec le compte créé ci-dessus.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ ### Modèle de détection de langue
61
 
62
+ `langdetect` + fastText. Le modèle fastText (131 Mo) n'est pas versionné :
63
 
64
  ```bash
65
+ mkdir -p models/fasttext
66
+ curl -L -o models/fasttext/lid.176.bin \
67
+ https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  ```
 
 
 
 
 
 
 
 
 
 
69
 
70
+ Sans lui, `POST /api/v1/language/detect` échoue ; le pipeline d'ingestion
71
+ retombe sur une heuristique par mots vides.
72
 
73
+ ## Configuration
 
 
 
 
 
74
 
75
+ Toutes les variables de `.env.example` sont réellement lues par
76
+ `app/core/config.py`. Les principales :
77
 
78
+ | Variable | Rôle |
79
+ |---|---|
80
+ | `DATABASE_URL` | `postgresql+asyncpg://…` — le driver asyncpg est obligatoire |
81
+ | `GEMINI_API_KEY` / `GEMINI_MODEL` | LLM des réponses. Modèles Flash actuels : `gemini-3.8-flash`, `3.7`, `3.6`, `3.5` |
82
+ | `LLAMA_CLOUD_API_KEY` / `LLAMA_PARSE_TIER` | OCR. `cost_effective` est le tier recommandé |
83
+ | `SECRET_KEY` | Signature JWT. **Obligatoire hors développement** : l'application refuse de démarrer si la valeur du dépôt est conservée |
84
+ | `ADMIN_EMAIL` / `ADMIN_PASSWORD` | Valeurs par défaut de `scripts/create_admin.py` |
85
 
86
+ ## Authentification
 
 
87
 
88
+ JWT porteur, trois rôles : `user`, `admin`, `superadmin`.
 
89
 
90
+ | Route | Usage |
91
+ |---|---|
92
+ | `POST /api/v1/auth/login` | Formulaire OAuth2 (utilisé par `/docs`) |
93
+ | `POST /api/v1/auth/login/json` | JSON (utilisé par le front) |
94
+ | `GET /api/v1/auth/me` | Compte associé au jeton |
95
 
96
+ Les écritures, les endpoints d'administration, l'OCR et l'upload exigent un
97
+ rôle administrateur. Il n'existe pas d'inscription publique : les comptes sont
98
+ créés par `POST /api/v1/admin/users` ou par `scripts/create_admin.py`.
99
 
100
+ ## Chaîne d'ingestion
 
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  ```
103
+ upload → LlamaParse → normalisation → découpage par article
104
+ → embeddings → tsvector (trigger) → published
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  ```
106
 
107
+ Un document est créé en `processing`. Il passe à `published` en cas de succès,
108
+ à `refused` avec `processing_error` en cas d'échec — jamais de contenu
109
+ partiellement extrait dans le corpus public.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ **Aucun repli dégradé.** LlamaParse réessaie 4 fois avec un délai croissant ;
112
+ au-delà, l'ingestion échoue. La couche texte des PDF scannés du corpus a été
113
+ mesurée à environ 20 % de rappel (filigrane inséré au milieu des phrases,
114
+ cachets lus comme du charabia, un document sur cinq sans aucun texte
115
+ exploitable) : la publier serait pire que de ne rien publier.
116
 
117
+ Les extractions sont mises en cache par `sha256` : un fichier déjà traité n'est
118
+ jamais repayé.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
+ ## Tests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ La suite tourne sur un **vrai PostgreSQL** — le cœur du produit est du SQL
123
+ PostgreSQL brut, et les objets dont il dépend (`search_vector`, index GIN,
124
+ triggers, extensions) n'existent que dans les migrations, jamais dans
125
+ `Base.metadata`.
126
 
127
  ```bash
128
+ docker run -d --name jurix-pg-test -p 5433:5432 \
129
+ -e POSTGRES_USER=jurix -e POSTGRES_PASSWORD=jurix -e POSTGRES_DB=jurix_test \
130
+ pgvector/pgvector:pg16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ export TEST_DATABASE_URL=postgresql+asyncpg://jurix:jurix@localhost:5433/jurix_test
133
+ pytest # tout
134
+ pytest -m "not integration" # sans base
135
+ pytest --cov=app --cov-report=term-missing
 
 
 
 
 
 
 
136
  ```
137
 
138
+ Sans base joignable, les tests qui en dépendent sont **ignorés avec la commande
139
+ à lancer** — jamais silencieusement verts.
140
 
141
+ ## Structure
 
 
142
 
 
 
 
 
 
 
143
  ```
144
+ app/
145
+ api/routes/ endpoints HTTP
146
+ core/ configuration, base de données, authentification
147
+ models/ modèles SQLAlchemy
148
+ schemas/ schémas Pydantic
149
+ services/ recherche, embeddings, RAG, OCR, classification
150
+ tasks/ pipeline d'ingestion
151
+ utils/ découpage en articles, raffinage des chunks, fichiers
152
+ alembic/versions/ migrations
153
+ scripts/ administration et maintenance
154
+ tests/ unitaires et intégration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  ```
156
 
157
+ ## Déploiement
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ `Dockerfile` fourni. Points à vérifier avant de déployer :
160
 
161
+ - `alembic upgrade head` n'est pas lancé par le conteneur — à exécuter séparément.
162
+ - `data/` est éphémère : monter un volume ou un stockage objet, sinon les PDF
163
+ uploadés disparaissent au redéploiement alors que les lignes en base subsistent.
164
+ - `--workers 1` : plusieurs états sont en mémoire du processus (connexions
165
+ WebSocket du suivi de lot, registre des tâches de fond).