# Phase 5B — Ingestion multi-entités & observations sourcées

## But

Faire de l’ingestion contrôlée un moteur générique capable de recevoir à terme les catalogues de n’importe quel provider, sans laisser un fournisseur écrire directement dans l’encyclopédie Coaster World.

## Flux cible

```text
Connecteur/provider
        ↓
Payload brut
        ↓
Normalisation
        ↓
Lot d’ingestion
        ↓
Candidats : parc / attraction / coaster / POI
        ↓
Rapprochement + contexte parc parent
        ↓
Décision humaine : Associer / Créer / Ignorer
        ↓
Identifiant externe + observations sourcées
        ↓
Moteur de fiabilité / conflits
        ↓
Valeur canonique uniquement après sélection explicite
```

## Parent/enfant

Les attractions, coasters et POI peuvent porter :

- `parent_type` ;
- `parent_external_id` ;
- `parent_candidate_id` quand le parc parent se trouve dans le même lot.

La création d’un enfant est interdite tant que son parc parent n’est pas résolu. Les recherches de correspondance par nom sont limitées au parc parent pour éviter les faux positifs entre attractions homonymes de deux parcs différents.

## Coasters

L’architecture existante est conservée : un coaster possède une `Attraction` support et une fiche `Coaster` technique.

Lorsqu’un candidat coaster est créé :

1. l’attraction support est créée dans le parc ;
2. la fiche `coasters` est créée ;
3. l’identifiant externe du provider est rattaché à l’attraction support afin de rester compatible avec les futurs flux Live ;
4. les champs techniques du coaster deviennent des observations sur la fiche `Coaster` ;
5. les champs d’exploitation éventuels peuvent cibler l’`Attraction` support.

## Valeurs candidates

La table `ingestion_candidate_values` conserve chaque valeur avant intégration :

- cible (`park`, `attraction`, `coaster`, `poi`) ;
- clé de champ ;
- locale ;
- valeur brute ;
- valeur normalisée ;
- type ;
- score et raison de confiance ;
- état ;
- observation créée ;
- erreur éventuelle.

Après `Associer` ou `Créer`, `IngestionObservationService` les transmet au `DataReliabilityService`.

**Aucune promotion canonique automatique n’est effectuée.**

## Extensibilité

`IngestionStagingService::stageItems()` constitue désormais le point d’entrée générique pour les futurs connecteurs. Un connecteur spécialisé devra uniquement convertir son payload en items normalisés ; il n’aura pas à gérer lui-même les doublons, décisions humaines, identifiants externes ou observations.

## Sécurité / intégrité

- décisions protégées par l’authentification admin et CSRF existants ;
- opérations d’association/création transactionnelles ;
- contrôle des conflits d’identifiants externes ;
- contrôle du parc parent ;
- aucune donnée brute exécutée ou rendue sans échappement ;
- une erreur de valeur n’annule pas l’ensemble du candidat : elle reste visible et traçable.
