---
suivi: 1587
date: 2026-09-16
sujet: Import parcelles enregistré, progression lot par lot
chantier: foncier / parcelles / import
type: feature
statut: poussé
hash: b1c7a64b
fichiers:
  - database/migrations/2026_09_16_210000_extend_parcelle_import_sessions_for_lots.php
  - app/Models/ParcelleImportSession.php
  - app/Services/Foncier/ParcelleImportLotService.php
  - app/Services/Foncier/ParcelleImportAnalysisService.php
  - app/Services/Foncier/ParcelleImportFileExtractor.php
  - app/Services/Foncier/ParcelleImportClaudeClient.php
  - app/Services/Foncier/ParcelleImportCommitService.php
  - app/Http/Controllers/Foncier/ParcelleImportController.php
  - app/Http/Middleware/DeclencheFoncierAlertes.php
  - config/foncier.php
  - routes/web.php
  - resources/js/Pages/Foncier/Parcelles/Import.vue
  - tests/Unit/ParcelleImport1587ProgressionSourceTest.php
  - tests/Unit/ParcelleImport1585TimeoutSourceTest.php
  - tests/Unit/ParcelleImport1580SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - .env.example
  - public/build/
  - docs/suivi/SUIVI_1587_import_enregistre_et_progression.md
---

# SUIVI #1587 — Import enregistré et progression

## PROMPT ENVOYÉ

L’analyse n’est plus une action qu’on attend : dépôt persisté, un
appel HTTP par lot, barre de progression, reprise, lazy onglet fermé,
zone glisser-déposer, cases à cocher, import en une fois. Pas de
file d’attente. Prompt #1580 inchangé.

## FICHES LUES

- **#1585 LEÇON** — N × timeout dans 300 s a fait échouer 83 lignes
  (0 token en 120 s). Impact : un lot = une requête web ; supprimer
  le plafond 84.
- **#1580 LEÇON** — session `parcelle_import_sessions`, pas de fichier
  stocké, lots Anthropic. Impact : étendre la table, pas en créer une
  seconde.
- **#1077** — `DeclencheFoncierAlertes` terminate PHP-FPM, verrou SQL,
  HTTPS. Impact : y brancher un lot d’import (pas Sage 4 h).

## DÉCLENCHEUR LAZY RETENU (avant code)

Retenu : `DeclencheFoncierAlertes` (`app/Http/Middleware/DeclencheFoncierAlertes.php`).

Citation : « Pas d'artisan : évaluation directe en terminate() (HTTPS
OK en PHP-FPM). » Enregistré dans `bootstrap/app.php` sur le groupe
web. GET authentifié.

Écarté : sync Sage (`FacturePaiementStatutBatchService::shouldRunLazyAuto`,
`paiement_sage_batch_lazy_hours` = 4) — uniquement depuis l’écran
Factures Global, trop rare, hors Foncier.

Cadence alertes = 1×/jour (`foncier_alerte_runs.date_run`) : trop
lent pour 9 lots. L’import n’utilise PAS ce verrou quotidien. Un
lot par GET terminate, hors pages `/foncier/parcelles/import*`
(là, le navigateur pilote). Verrou `analyse_locked_at` (TTL 120 s)
empêche navigateur + lazy sur le même lot.

## VERROU

`UPDATE parcelle_import_sessions SET analyse_locked_at = now()
WHERE id = ? AND statut IN (en_attente, en_cours) AND
(analyse_locked_at IS NULL OR analyse_locked_at < now() − TTL)`.
Une ligne mise à jour = claim. Sinon busy, réessai.

## DÉCOUPAGE

| Clé | Env | Défaut |
|---|---|---|
| timeout | FONCIER_IMPORT_TIMEOUT | 90 s |
| lignes_par_lot | FONCIER_IMPORT_LIGNES_PAR_LOT | 10 |
| max_lignes | FONCIER_IMPORT_MAX_LIGNES | 500 |
| purge_jours | FONCIER_IMPORT_PURGE_JOURS | 7 |
| lock_ttl_s | FONCIER_IMPORT_LOCK_TTL_S | 120 |

83 lignes → ceil(83/10) = **9 lots**. Plus de refus à 85.

## MIGRATION

Extension de `parcelle_import_sessions` : statut, fichier_nom/taille,
nb_lignes, extrait JSON (pas le fichier disque), lots, compteurs,
analyse_locked_at, last_touched_at. Justifiée : reprendre sans
reposter le fichier ; PII purgée à l’import et à 7 jours.

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan migrate --force
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
php artisan journal:sync
```

## LEÇON

Un timeout par appel ne sauve pas un fichier entier dans une requête
web. Persister le plan de lots, un HTTP à la fois, et un lazy web
(terminate FPM) pour l’onglet fermé. Ne pas caler N lots sur 300 s.
