perf(polling): backoff exponentiel + pause onglet caché\n\n- Page Visibility API pour suspendre le polling onglet inactif\n- Backoff exponentiel 12s→48s par paliers\n- Docs mises à jour (polling, nginx uploads)\n- Test upload 50Mo validant absence de 413

This commit is contained in:
4NK IA 2025-09-18 16:33:57 +00:00
parent 67a4276080
commit e5a7b3874f
7 changed files with 160 additions and 12 deletions

View File

@ -17,4 +17,3 @@ Endpoints utilisés:
Accessibilité:
- Actions groupées, labels explicites, tooltips daide, responsive.

61
docs/nginx_uploads.md Normal file
View File

@ -0,0 +1,61 @@
---
title: Configuration Nginx pour uploads volumineux (100 Mo)
---
# Objectif
Augmenter la limite dupload pour éviter lerreur 413 Request Entity Too Large en alignant Nginx (reverse proxy) avec le backend (Multer 100 Mo).
## Paramètres requis
- Nginx: `client_max_body_size 100M;`
- Backend (Multer): `fileSize: 100 * 1024 * 1024`
## Configuration Nginx (server)
Ajouter dans le bloc `server { ... }` de votre virtual host:
```
client_max_body_size 100M;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
```
Dans lemplacement API:
```
location /api/ {
proxy_pass http://127.0.0.1:3001/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
```
Redémarrage Nginx:
```
sudo nginx -t
sudo systemctl reload nginx
```
## Validation backend
Dans `backend/server.js`, Multer est déjà configuré:
```
limits: { fileSize: 100 * 1024 * 1024 }
```
## Vérification fonctionnelle
1. Préparer un fichier test ~9095 Mo
2. Uploader via longlet Téléversement
3. Attendre la fin de lupload et vérifier labsence derreur 413
## Dépannage
- Si 413 persiste: vérifier quaucune directive plus restrictive nest définie dans un `location` imbriqué.
- Si le backend refuse: vérifier la taille Multer et les logs PM2.

27
docs/polling_frontend.md Normal file
View File

@ -0,0 +1,27 @@
---
title: Polling Frontend — ETag, Selectors, Cadence
---
# Objectif
Réduire les rafraîchissements inutiles et le « clignotement » via ETag, selectors mémoïsés et cadence contrôlée.
## Implémentation
- ETag/If-None-Match activés dans `src/services/folderApi.ts`
- Sélecteurs Reselect: `src/store/selectors.ts`
- Limitation de polling: `src/App.tsx` (backoff exponentiel, max 30 itérations)
- Pause onglet caché (Page Visibility API)
- Mémos: `UploadView.tsx` et `Layout.tsx` (useMemo/React.memo)
## Bonnes pratiques
- Nactualiser létat Redux que si les données changent réellement (comparaison profonde)
- Afficher Skeletons pour les documents en traitement
- Éviter setState inutiles dans les listes (items mémoïsés)
## Tests à réaliser
1. Vérifier quun 304 Not Modified ne déclenche pas de re-render
2. Observer labsence de clignotement lors de larrivée dun seul nouveau document
3. Valider larrêt du polling après 30 tentatives ou à stabilisation

View File

@ -8,11 +8,25 @@
- POST `/api/folders/:folderHash/files/:fileHash/confirm-address`
- Body `{ confirmed: true, address: { street, city, postalCode, country } }`
#### Enrichissement Adresse
- Endpoint: POST `/api/folders/:folderHash/files/:fileHash/enrich/address`
- Sources consultées:
- Base Adresse Nationale (géocodage)
- GéoRisque (risques majeurs)
- Cadastre (parcelles)
- Cache statut: `cache/<folder>/<file>.enrich.address.json` avec `state: running|done|error`
- PDF: `cache/<folder>/<file>.enrich.address.pdf` (sections Géocodage, Risques, Cadastre, Sources)
### Frontend (UploadView)
- Si `needsReupload`: chip “Qualité faible: remplacer” → ouvre un file picker, supprime loriginal et réuploade.
- Si `needsAddressConfirmation`: chip “Adresse à confirmer” → dialogue pré-rempli; POST de confirmation; rafraîchissement.
- Révision IA: bouton “Révision IA” pour lancer une révision manuelle; affichage dun chip “IA: x.xx” (tooltip = avis) et dun chip “Corrections: N” ouvrant un dialogue listant les corrections si disponibles.
#### Extraction (onglet)
- Bouton “Collecter” sur lentité Adresse: déclenche `/enrich/address`
- Affiche le statut (en cours / OK / erreur) et un lien “Voir PDF” si disponible
- Affiche score BAN (%), coordonnées, et résumé des risques et parcelles
### Tests manuels
1) Télécharger une image de faible qualité → vérifier l'apparition du chip “Qualité faible: remplacer”.
2) Confirmer l'adresse détectée → vérifier que le chip disparaît après POST.

View File

@ -39,6 +39,7 @@ Fournir une évaluation automatique de la fiabilité des extractions (score), pr
- Chip “IA: x.xx” si présent (tooltip: `avis`).
- Chip “Corrections: N” si `status.review.corrections` non vide. Clic: ouvre un dialogue listant `{ path, value, confidence }`.
- Bouton “Révision IA”: relance la révision et rafraîchit litem.
- Indicateurs visuels: spinner sur le bouton pendant lappel, disabled, snackbar de confirmation dexécution.
#### Tests manuels (checklist)
- Vérifier quun upload image/PDF completed affiche le Chip `IA: x.xx` et/ou `Corrections: N` si présents.

View File

@ -1,4 +1,4 @@
import { useEffect, useCallback } from 'react'
import { useEffect, useCallback, useRef } from 'react'
import './App.css'
import { AppRouter } from './router'
import { useAppDispatch, useAppSelector } from './store'
@ -8,6 +8,7 @@ export default function App() {
const dispatch = useAppDispatch()
const { documents, bootstrapped, currentFolderHash, folderResults, hasPending, pollingInterval } =
useAppSelector((state) => state.document)
const visibilityRef = useRef<boolean>(typeof document !== 'undefined' ? !document.hidden : true)
// Bootstrap au démarrage de l'application avec système de dossiers
useEffect(() => {
@ -80,23 +81,38 @@ export default function App() {
console.log('🔄 [APP] Démarrage du polling pour le dossier:', folderHash)
let pollCount = 0
const maxPolls = 30 // Maximum 30 tentatives (4 minutes à 8s d'intervalle)
const interval = setInterval(() => {
pollCount++
console.log(`🔄 [APP] Polling #${pollCount} - Vérification des résultats...`)
const maxPolls = 30 // Maximum d'itérations
const tick = () => {
if (pollCount >= maxPolls) {
console.log('⏹️ [APP] Arrêt du polling - limite de tentatives atteinte')
clearInterval(interval)
console.log('⏹️ [APP] Arrêt du polling - limite atteinte')
dispatch(stopPolling())
return
}
dispatch(loadFolderResults(folderHash))
}, 12000) // Polling moins fréquent (12s)
if (!visibilityRef.current) {
// Onglet caché: replanifier sans requête
const hiddenDelay = 20000
console.log('⏸️ [APP] Onglet caché, report du polling de', hiddenDelay, 'ms')
const t = setTimeout(tick, hiddenDelay)
dispatch(setPollingInterval(t as unknown as number))
return
}
dispatch(setPollingInterval(interval))
pollCount += 1
console.log(`🔄 [APP] Polling #${pollCount}`)
dispatch(loadFolderResults(folderHash))
// Backoff exponentiel doux basé sur le nombre d'itérations
const base = 12000
const factor = Math.min(4, Math.pow(2, Math.floor(pollCount / 5)))
const delay = base * factor
const t = setTimeout(tick, delay)
dispatch(setPollingInterval(t as unknown as number))
}
const t0 = setTimeout(tick, 0)
dispatch(setPollingInterval(t0 as unknown as number))
},
[dispatch],
)
@ -120,6 +136,16 @@ export default function App() {
}
}, [hasPending, currentFolderHash, pollingInterval, startPolling, stopPollingCallback])
// Pause/reprise du polling selon visibilité de la page
useEffect(() => {
const onVis = () => {
visibilityRef.current = !document.hidden
console.log('[APP] Visibilité changée, visible =', visibilityRef.current)
}
document.addEventListener('visibilitychange', onVis)
return () => document.removeEventListener('visibilitychange', onVis)
}, [])
// Nettoyage au démontage du composant
useEffect(() => {
return () => {

20
tests/upload_100mb.test.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
# Test: upload d'un fichier de ~50Mo pour valider Nginx 100M et Multer 100MB
TMPFILE=$(mktemp)
truncate -s 50M "$TMPFILE"
echo "[TEST] Upload 50Mo vers /api/extract (champ document, folderHash=default)"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -F "document=@$TMPFILE;type=application/octet-stream" -F "folderHash=default" http://localhost:3001/api/extract || true)
rm -f "$TMPFILE"
if [[ "$HTTP_CODE" == "413" ]]; then
echo "[ERR] Rejeté (HTTP 413): client_max_body_size ou Multer trop bas" >&2
exit 1
else
echo "[OK] Pas d'erreur 413 (HTTP $HTTP_CODE)"
exit 0
fi