# RÉFÉRENCE KERAL — MODÈLE D'OCCUPATION

**Usage :** document de référence destiné à être fourni à un assistant IA (contexte ou prompt système) pour qu'il configure, audite ou corrige les occupations de types de chambre, puis projette cette configuration vers n'importe quel canal de distribution (Booking.com, Expedia, Agoda, Airbnb, Hotelbeds…).

**Démarrage rapide :** section 0 pour les règles, section 11 pour le contrat d'entrée/sortie et le prompt d'amorçage prêt à copier.

**Principe fondateur, à mémoriser avant tout le reste :**

> `occ_adults` / `occ_children` / `occ_infants` décrivent des **couchages physiques**, pas une **composition de clientèle autorisée**. Toute erreur ici se propage mécaniquement à tous les canaux connectés.

---

## 0. RÈGLES EXÉCUTABLES

Ces dix règles suffisent à traiter 95 % des cas. Le reste du document les justifie et les détaille.

| # | Règle |
|---|---|
| **R1** | `occ_adults` = nombre total de personnes pouvant dormir dans la chambre, tous âges confondus. Un enfant occupe un couchage adulte. |
| **R2** | `occ_children` = nombre de couchages utilisables **exclusivement** par un enfant, **en supplément** de `occ_adults`. Par défaut : `0`. |
| **R3** | `occ_infants` = nombre de lits bébé installables. En pratique `0` ou `1`. Ne consomme pas de couchage. |
| **R4** | Capacité totale annoncée aux canaux = `occ_adults + occ_children`. |
| **R5** | `default_occupancy ≤ occ_adults`. Contrainte dure : toute violation est refusée à l'enregistrement. |
| **R6** | Les options d'occupation d'un plan tarifaire sont exprimées en **nombre d'occupants** et plafonnées par `occ_adults`. `occ_children` ne crée aucune option tarifaire. |
| **R7** | Ne jamais répartir une capacité en « X adultes + Y enfants » pour représenter une chambre familiale polyvalente. Mettre toute la capacité dans `occ_adults`. |
| **R8** | Diminuer `occ_adults` ou `occ_children` après mappage à un canal renvoie une erreur de validation ; forcer l'opération détruit les mappages existants. → **configurer juste avant de connecter les canaux**. |
| **R9** | Les tarifs enfant/bébé (`children_fee`, `infant_fee`) sont des champs du **plan tarifaire**, pas du type de chambre. |
| **R10** | Aucune tranche d'âge n'est stockée côté type de chambre. Les seuils adulte / enfant / bébé sont définis dans l'extranet de chaque canal. |

---

## 1. MODÈLE DE DONNÉES

```
Property (hotel_policy: is_adults_only, max_count_of_guests)
└── Room Type            → occ_adults, occ_children, occ_infants,
    │                       default_occupancy, count_of_rooms,
    │                       room_kind, capacity
    └── Rate Plan         → sell_mode, rate_mode, children_fee, infant_fee
        └── options[]     → { occupancy, is_primary, rate, derived_option }
                            occupancy ∈ [1 .. occ_adults]
```

L'occupation est déclarée **une seule fois**, au niveau du type de chambre. Les plans tarifaires ne font que la décliner en points de prix. Les canaux ne font que consommer le résultat.

### Objet Room Type (extrait normatif)

```json
{
  "room_type": {
    "property_id": "<property_id>",
    "title": "Standard Room",
    "count_of_rooms": 20,
    "occ_adults": 3,
    "occ_children": 0,
    "occ_infants": 1,
    "default_occupancy": 2,
    "room_kind": "room",
    "capacity": null,
    "facilities": [],
    "content": { "description": "...", "photos": [] }
  }
}
```

`POST /room_types` · `PUT /room_types/:id` · `GET /room_types?filter[property_id]=…`

---

## 2. SÉMANTIQUE EXACTE DES CHAMPS

| Champ | Type | Requis | Sémantique | Invariant |
|---|---|---|---|---|
| `occ_adults` | entier > 0 | oui | Couchages adultes, donc capacité réelle de la chambre | plafonne `default_occupancy` et `options[].occupancy` |
| `occ_children` | entier ≥ 0 | oui | Couchages exclusivement enfant, additionnels. S'il n'y a pas de lit exclusivement enfant : `0` | ne génère aucune option tarifaire |
| `occ_infants` | entier ≥ 0 | oui | Berceaux disponibles | hors capacité en couchages |
| `default_occupancy` | entier > 0 | oui | occupation standard sans lit d'appoint | `≤ occ_adults` |
| `count_of_rooms` | entier > 0 | oui | unités physiques vendables de ce type ; facturé pour les locations de vacances | plafonne la disponibilité |
| `room_kind` | `room` \| `dorm` | non | type d'inventaire | `capacity` n'a de sens que si `dorm` |
| `capacity` | entier \| null | non | nombre de lits dans une chambre physique de dortoir | `null` si `room` |

### Distinction critique : `occ_adults` vs `occ_children`

Le mot « adultes » est trompeur. Le test discriminant n'est pas l'âge du client mais la **nature du lit** :

- Un lit qu'un adulte peut occuper → compte dans `occ_adults`, même s'il est en pratique occupé par un enfant.
- Un lit qu'un adulte ne peut **pas** occuper (couchette étroite, lit escamotable enfant, mezzanine basse) → compte dans `occ_children`.

Conséquence : `occ_children > 0` est **rare**. Sur un parc hôtelier classique, la valeur correcte est `0` dans la quasi-totalité des cas.

---

## 3. INVARIANTS ET COMPORTEMENTS DE VALIDATION

| Invariant | Comportement en cas de violation |
|---|---|
| `default_occupancy ≤ occ_adults` | erreur de validation |
| `options[].occupancy ≤ occ_adults` | erreur de validation sur le plan tarifaire |
| Réduction d'une occupation déjà mappée à un canal | erreur de validation, sauf écriture forcée qui supprime l'option **et** les mappages canaux (irréversible) |
| Suppression d'un type de chambre mappé | bloquée, sauf suppression forcée |
| `sell_mode: per_room` | une seule option d'occupation, correspondant à l'occupation maximale |
| `sell_mode: per_person` | une option par nombre d'occupants tarifé, de 1 à `occ_adults` ; exactement une avec `is_primary: true` |

Règle d'ordonnancement pour un agent IA : **augmenter une capacité est sûr, la réduire est destructif**. En cas de doute sur la valeur cible, proposer la correction à l'utilisateur avant toute écriture forcée.

---

## 4. ALGORITHME DE DÉRIVATION

Entrée : description en langage naturel d'une chambre (nom, lits, capacité annoncée, photos).
Sortie : objet d'occupation.

```
function deriveOccupancy(chambre):

  # Étape 1 — capacité totale en couchages
  N = nombre maximum de personnes pouvant dormir dans la chambre,
      tous âges confondus, lits d'appoint inclus

  # Étape 2 — couchages exclusivement enfant
  K = nombre de lits qu'un adulte ne peut physiquement pas occuper
      (0 dans la très grande majorité des cas)

  # Étape 3 — berceaux
  I = nombre de lits bébé installables      # 0 ou 1

  # Étape 4 — occupation standard
  D = nombre de personnes accueillies sans déployer de lit d'appoint
      si inconnu → D = N - K

  return {
    occ_adults:        N - K,
    occ_children:      K,
    occ_infants:       I,
    default_occupancy: min(D, N - K)
  }

# Assertions de sortie — bloquer l'écriture si l'une échoue
assert occ_adults >= 1
assert default_occupancy <= occ_adults
assert occ_adults + occ_children == N
assert occ_children == 0 OR (un lit exclusivement enfant est explicitement documenté)
```

### Arbre de décision abrégé

```
La chambre annonce « max P personnes » ?
├── oui, sans distinction d'âge  → occ_adults = P, occ_children = 0
└── oui, « A adultes + C enfants »
    └── un lit est-il réservé aux enfants ?
        ├── oui → occ_adults = A, occ_children = C
        └── non → occ_adults = A + C, occ_children = 0     ← cas le plus fréquent
```

---

## 5. ANTI-PATTERNS À DÉTECTER

Un agent qui audite un compte existant doit chercher ces signatures.

| Signature détectée | Diagnostic | Correction |
|---|---|---|
| `occ_adults` = 1 alors que le titre ou la description indique triple, familiale, suite, appartement | La capacité a été répartie en adultes/enfants | `occ_adults` = capacité totale, `occ_children` = 0 |
| `occ_adults + occ_children` > capacité annoncée par l'hôtel | Double comptage des enfants | `occ_children` = 0 |
| `occ_children` > 0 sans lit enfant identifiable | Confusion « enfants autorisés » / « lits enfant » | `occ_children` = 0, reporter la capacité sur `occ_adults` |
| `occ_infants` ≥ 2 | Déclaration irréaliste de berceaux | `occ_infants` = 1 |
| `default_occupancy` = `occ_adults` alors qu'un lit d'appoint payant existe | Perte du différentiel tarifaire | `default_occupancy` = capacité sans lit d'appoint |
| `count_of_rooms` = valeur ronde suspecte (10, 50, 100) sur plusieurs types | Valeur laissée par défaut | Nombre réel d'unités physiques |
| Plan tarifaire `per_person` avec une seule option alors que `occ_adults` > 1 | Grille tarifaire incomplète | Une option par nombre d'occupants |

---

## 6. PROJECTION VERS LES CANAUX

La configuration d'occupation est la source de vérité ; chaque canal en consomme une projection. La règle générale est invariante :

> **La capacité déclarée doit être supérieure ou égale à l'occupation attendue par le canal, et le nombre d'options d'occupation disponibles est plafonné par `occ_adults`.**

| Canal | Modèle d'occupation | Ce que `occ_adults` détermine | Pièges |
|---|---|---|---|
| **Booking.com** | Mapping par chambre + tarif, avec Occupancy Based Mapping : chaque option se mappe à l'occupation correspondante côté Booking. Une option doit être désignée Primary Rate : c'est elle qui porte les restrictions (min stay, stop sell). | Le nombre de lignes d'occupation mappables. `occ_adults: 1` → une seule ligne → prix par occupation impossible. | Tous les tarifs doivent être mappés, y compris certains tarifs dérivés anciens. Après connexion, revérifier les tarifs dérivés. |
| **Agoda** | Mapping toujours multi-occupation. Si l'établissement ne pratique pas le prix par occupation, mapper la même occupation sur toutes les lignes. | Le nombre de lignes à remplir. Un `occ_adults` trop bas rend certaines lignes impossibles à mapper. | Ne rien laisser non mappé. |
| **Expedia** | Mapping direct chambres ↔ tarifs, sans grille d'occupation dans l'interface de mappage. | L'occupation maximale de la chambre Expedia doit correspondre à `occ_adults + occ_children`. | Divergence de capacité entre l'extranet et la configuration → survente ou invisibilité sur certaines recherches. |
| **Airbnb** | Un seul plan tarifaire mappable. Pour un prix par personne : mapper le plan de plus faible occupation, puis régler dans l'annonce « Guests Included » et « Price Per Extra Guest ». | La capacité de l'annonce doit refléter `occ_adults + occ_children`. | « Price Per Extra Guest » est une valeur unique : impossible d'avoir un montant différent pour le 1er et le 2e invité supplémentaire. |
| **Hotelbeds** | Mapping chambre ↔ chambre/tarif du contrat. Un canal = un contrat. | L'occupation est portée par le contrat ; la capacité doit rester cohérente avec lui. | Certains tarifs contractuels refusent les prix. Les allotements peuvent empêcher la fermeture de dates et provoquer des surventes. |

### Procédure de propagation d'une correction d'occupation

```
1. Corriger le type de chambre (source de vérité).
2. Compléter la grille d'options du plan tarifaire jusqu'au nouveau occ_adults.
3. Aligner la capacité déclarée dans l'extranet de chaque canal.
4. Rouvrir la page de mappage de chaque canal :
   les nouvelles lignes d'occupation apparaissent et doivent être mappées.
5. Désigner le Primary Rate là où le canal l'exige (Booking.com).
6. Réactiver / resynchroniser le canal (full sync des prix, dispo, restrictions).
7. Contrôler le journal du canal : toute erreur d'occupation apparaît ici.
```

---

## 7. RÉCEPTION DES RÉSERVATIONS

Une réservation porte l'occupation à deux niveaux : la réservation entière et chaque chambre réservée.

```json
{
  "occupancy": { "adults": 2, "children": 1, "infants": 0 }
}
```

- Les trois clés sont toujours présentes : `adults`, `children`, `infants`.
- Si `children` ≠ 0, l'objet `occupancy` de la chambre contient en plus une liste `ages` avec l'âge des enfants.

Contrôles à effectuer par un agent qui consomme le flux de réservations :

```
assert booking.room.occupancy.adults <= room_type.occ_adults
assert adults + children <= occ_adults + occ_children
assert infants <= occ_infants        # sinon : berceaux sous-déclarés
```

Un dépassement systématique de `occ_adults` sur les réservations entrantes est le signal d'une capacité sous-déclarée.

---

## 8. CE QUE LE MODÈLE NE GÈRE PAS

À ne pas chercher, sous peine d'inventer des champs :

- **Aucune tranche d'âge.** Les seuils adulte / enfant / bébé sont définis canal par canal, dans chaque extranet.
- **Aucun tarif enfant sur le type de chambre.** `children_fee` et `infant_fee` sont des champs du plan tarifaire.
- **Aucune règle de composition** du type « 2 adultes maximum parmi 4 personnes ». Seuls des couchages sont modélisés ; les restrictions de composition, quand elles existent, sont propres au canal.
- **Aucune configuration automatique de la capacité côté canal.** Prix, disponibilités et restrictions sont transmis ; la capacité des chambres reste déclarée dans chaque extranet et doit être alignée manuellement.

---

## 9. EXEMPLES CANONIQUES

| Configuration réelle | `occ_adults` | `occ_children` | `occ_infants` | `default_occupancy` | Capacité |
|---|---|---|---|---|---|
| 1 lit simple | 1 | 0 | 1 | 1 | 1 |
| 1 lit double | 2 | 0 | 1 | 2 | 2 |
| 2 lits simples (twin) | 2 | 0 | 1 | 2 | 2 |
| 1 double + 1 simple | 3 | 0 | 1 | 3 | 3 |
| 1 double + lit d'appoint payant | 3 | 0 | 1 | 2 | 3 |
| Familiale 4 personnes, toutes combinaisons | 4 | 0 | 1 | 4 | 4 |
| 1 double + couchette enfant exclusive | 2 | 1 | 1 | 2 | 3 |
| Suite 2 chambres, 5 personnes | 5 | 0 | 1 | 4 | 5 |
| Dortoir 6 lits (`room_kind: dorm`, `capacity: 6`) | 6 | 0 | 0 | 6 | 6 |

---

## 10. CHECKLIST DE VALIDATION

Assertions à exécuter avant toute écriture, pour chaque type de chambre :

```
[ ] occ_adults == nombre total de personnes pouvant dormir dans la chambre
[ ] occ_children == 0, sauf lit exclusivement enfant documenté
[ ] occ_infants ∈ {0, 1}, sauf justification explicite
[ ] default_occupancy <= occ_adults
[ ] occ_adults + occ_children == capacité annoncée sur le site et les OTA
[ ] count_of_rooms == nombre d'unités physiques réellement vendables
[ ] le plan tarifaire possède une option par nombre d'occupants à tarifer
[ ] exactement une option porte is_primary: true
[ ] la capacité déclarée dans chaque extranet canal est alignée
[ ] aucune erreur d'occupation dans le journal des canaux après resynchronisation
```

---

## 11. CONTRAT D'ENTRÉE / SORTIE POUR UNE IA

Cette section rend le traitement reproductible : elle définit ce que l'agent reçoit, comment il doit se comporter quand l'information manque, et sous quelle forme il doit répondre. **Un agent qui respecte ce contrat ne devine jamais en silence.**

### 11.1 Entrée attendue

L'agent a besoin de deux informations par chambre, et de deux seulement : **la capacité totale en couchages** et **la nature des lits**. Tout le reste s'en déduit.

| Information | Source recommandée | Si absente |
|---|---|---|
| Capacité maximale en personnes | Liste des chambres | **Bloquant** — poser la question |
| Configuration des lits | Extranet du canal, ou fiche technique de l'établissement | Appliquer `occ_children = 0` et marquer l'hypothèse |
| Berceau possible | Fiche de la chambre ou déclaration de l'hôtelier | Proposer `occ_infants = 1`, à confirmer |
| Occupation standard (hors lit d'appoint) | Déclaration de l'hôtelier | `default_occupancy = occ_adults` |
| Nombre d'unités physiques | Liste des chambres | Ne pas modifier le champ existant |
| Identifiant du type de chambre | Liste des chambres | **Bloquant** pour écrire via l'API |

Format textuel recommandé, une ligne par chambre :

```
<titre> | <id> | <lits> | <capacité max> | <berceau: oui/non> | <unités>

Familiale Supérieure | 152492411 | 1 double + 2 simples | 4 | oui | 4
Triple Comfort       | 152492410 | 1 double + 1 simple  | 3 | oui | 3
```

### 11.2 Politique d'incertitude

L'agent classe chaque chambre dans un et un seul niveau de confiance, et adapte son comportement.

| Niveau | Condition | Comportement |
|---|---|---|
| `CERTAIN` | Capacité **et** configuration des lits connues | Produire les valeurs cibles et le payload |
| `DÉDUIT` | Capacité connue, lits inconnus | Produire les valeurs avec `occ_children = 0`, énoncer l'hypothèse en une ligne, marquer le bloc `DÉDUIT` |
| `MANQUANT` | Capacité inconnue ou contradictoire | Ne produire **aucune** valeur, formuler la question précise |

Interdits absolus :

- Ne jamais proposer `occ_children > 0` sans un lit exclusivement enfant explicitement documenté.
- Ne jamais proposer `occ_infants > 1` sans déclaration explicite.
- Ne jamais fusionner plusieurs chambres au motif que leurs noms se ressemblent.
- Ne jamais compléter une capacité manquante par la moyenne des autres chambres.

### 11.3 Format de sortie exigé

Un bloc par chambre, puis deux sections de synthèse. Aucune prose hors de ce gabarit.

```
CHAMBRE   : <titre> (<id>)
CONFIANCE : CERTAIN | DEDUIT | MANQUANT
ACTUEL    : occ_adults=? occ_children=? occ_infants=? default=? unites=?
CIBLE     : occ_adults=? occ_children=? occ_infants=? default=?
DELTA     : AUCUN | AUGMENTATION | REDUCTION (confirmation requise)
HYPOTHESE : <une ligne, ou "aucune">
PAYLOAD   :
  PUT /room_types/<id>
  { "room_type": { ... } }
```

```
QUESTIONS BLOQUANTES
  1. <question précise, chambre nommée>

ORDRE D'EXECUTION
  1. Chambres en AUGMENTATION ou AUCUN delta  → sans risque
  2. Chambres en REDUCTION                    → confirmation nominative requise
  3. Grilles d'options des plans tarifaires   → après correction des chambres
  4. Alignement des extranets canaux          → manuel
```

### 11.4 Règles d'exécution

1. N'envoyer aucune requête d'écriture avant validation explicite de l'utilisateur.
2. Traiter les augmentations de capacité d'abord : elles sont sans effet destructif.
3. Ne jamais forcer une réduction sans confirmation portant sur la chambre nommée, et rappeler que les mappages canaux seront supprimés.
4. Après chaque écriture, relire le type de chambre et comparer aux valeurs cibles.
5. En cas d'erreur de validation, restituer le message d'erreur sans le réinterpréter.
6. Ne jamais modifier `count_of_rooms` sans instruction explicite : ce champ est facturé pour les locations de vacances.

### 11.5 Prompt d'amorçage

À copier tel quel, avec ce document en pièce jointe ou en contexte.

```
Tu appliques la REFERENCE KERAL — MODELE D'OCCUPATION fournie en contexte.

Entrée : les captures et lignes de chambres ci-dessous.
Tâche  : pour chaque chambre, déterminer occ_adults, occ_children,
         occ_infants et default_occupancy, puis produire le payload PUT.

Contraintes :
- Respecter strictement le format de sortie de la section 11.3.
- Classer chaque chambre en CERTAIN, DEDUIT ou MANQUANT (section 11.2).
- Ne rien deviner en silence : toute hypothèse est écrite, toute information
  manquante devient une question bloquante.
- N'exécute aucune requête. Tu produis les payloads, je les valide.

[coller ici les captures et/ou les lignes de chambres]
```

---

## 12. GLOSSAIRE

| Terme | Définition dans ce document |
|---|---|
| Couchage | Place de lit utilisable par une personne pour la nuit |
| Couchage exclusivement enfant | Couchage qu'un adulte ne peut physiquement pas occuper |
| Capacité totale | `occ_adults + occ_children` |
| Occupation standard | `default_occupancy` — nombre de personnes sans lit d'appoint déployé |
| Option d'occupation | Point de prix d'un plan tarifaire pour un nombre donné d'occupants |
| Primary Rate | Option d'occupation désignée comme porteuse des restrictions côté Booking.com |
| Mappage | Association entre une entité de configuration et son équivalent chez un canal |
