> ## Documentation Index
> Fetch the complete documentation index at: https://docs.solya.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Spécifications d'ingestion — étapes de promotion

> Le catalogue complet des étapes de transformation de promotion, leurs paramètres, sous-options et comportement d'écriture.

Une **promotion** transforme les données analysées et les écrit dans une table cible. Une spécification peut avoir plusieurs promotions (une par dataset de sortie), et chacune exécute une **liste ordonnée d'étapes**.

## Forme de promotion

```json theme={null}
{
  "dataset_id": "brands",
  "domain": "catalog",
  "conformed_table": "brands",
  "source_table": "marque",
  "source_tables": ["marque"],
  "merge_keys": ["organization_id", "code"],
  "update_columns": ["name", "updated_at"],
  "steps": [ /* étapes de transformation ordonnées */ ]
}
```

| Champ                            | Signification                                                            |
| -------------------------------- | ------------------------------------------------------------------------ |
| `dataset_id`                     | Nom du dataset logique.                                                  |
| `domain`                         | Domaine logique (ex. `catalog`, `sales`, `inventory`).                   |
| `conformed_table`                | La table silver/gold cible.                                              |
| `source_table` / `source_tables` | La(les) table(s) source que cette promotion lit.                         |
| `merge_keys`                     | Colonnes qui identifient une ligne pour l'upsert.                        |
| `update_columns`                 | Lors de l'upsert, quelles colonnes mettre à jour (`null`/omis = toutes). |
| `steps`                          | Le pipeline ordonné (ci-dessous).                                        |

### Comportement d'écriture

Les promotions écrivent selon l'un de trois modes — **merge** (upsert sur `merge_keys`), **append** (insérer uniquement), ou **overwrite partition** (remplacer une partition). En pratique, une promotion avec `merge_keys` effectue un upsert sur ces clés ; `update_columns` restreint quelles colonnes sont écrites en cas de correspondance (ex. enrichir uniquement `postal_code` sans toucher au reste de la ligne).

### Ordonnancement

Les étapes s'exécutent **de haut en bas**. Chaque étape voit la sortie de la précédente, donc l'ordre importe — ex. ajouter/renommer les colonnes avant `generate_id`, et exécuter une `id_mapping_output` uniquement après l'existence de l'id.

***

## Catalogue d'étapes

Chaque étape est un objet avec un `type`. Ci-dessous, groupées par objectif.

### Mise en forme

<AccordionGroup>
  <Accordion title="coerce_types — convertir les colonnes en types SQL">
    ```json theme={null}
    { "type": "coerce_types", "columns": { "quantity": "INT", "price": "DOUBLE", "date": "TIMESTAMP" } }
    ```

    Convertit chaque colonne listée au type Spark SQL donné.
  </Accordion>

  <Accordion title="filter — conserver les lignes correspondant à une condition">
    ```json theme={null}
    { "type": "filter", "condition": "quantity > 0 AND date IS NOT NULL" }
    ```

    `condition` est une expression SQL `WHERE` (sans le mot-clé `WHERE`).
  </Accordion>

  <Accordion title="deduplicate — supprimer les lignes dupliquées">
    ```json theme={null}
    { "type": "deduplicate", "columns": ["no_ligmvt"], "strategy": "keep_last", "order_by": ["updated_at"], "order_desc": true }
    ```

    `strategy` : `drop` (par défaut), `keep_first`, ou `keep_last`. `order_by` / `order_desc`
    décident quelle ligne survit pour `keep_first`/`keep_last`.
  </Accordion>

  <Accordion title="add_column — ajouter une colonne calculée">
    ```json theme={null}
    { "type": "add_column", "column": "created_at", "expression": "current_timestamp()" }
    ```

    `expression` est toute expression scalaire SQL (littéraux, fonctions, `CASE`, références à
    d'autres colonnes).
  </Accordion>

  <Accordion title="rename_columns — renommer les colonnes">
    ```json theme={null}
    { "type": "rename_columns", "columns": { "no_marque": "code", "nom": "name" } }
    ```

    Mappe les noms `source → cible`.
  </Accordion>

  <Accordion title="select_columns — conserver uniquement ces colonnes">
    ```json theme={null}
    { "type": "select_columns", "columns": ["id", "code", "name"] }
    ```

    Supprime tout ce qui n'est pas listé.
  </Accordion>

  <Accordion title="drop_columns — supprimer ces colonnes">
    ```json theme={null}
    { "type": "drop_columns", "columns": ["_tmp", "_extra"] }
    ```
  </Accordion>

  <Accordion title="inject_value — injecter une valeur d'exécution">
    ```json theme={null}
    { "type": "inject_value", "column": "organization_id", "value_key": "organization_id" }
    ```

    Écrit une valeur du contexte d'exécution (ex. `organization_id`, `created_by`) comme
    colonne littérale.
  </Accordion>
</AccordionGroup>

### Jointure

<AccordionGroup>
  <Accordion title="join — joindre un autre dataset">
    ```json theme={null}
    {
      "type": "join",
      "target_dataset": "shops",
      "source_column": "shop_code",
      "target_column": "code",
      "join_type": "left",
      "select_columns": [ { "source": "id", "alias": "shop_id" }, { "source": "name" } ],
      "columns_to_drop": ["_tmp"],
      "deduplicate_on": ["code"]
    }
    ```

    `join_type` : `left` (par défaut), `inner`, `outer`, `right`. `select_columns` choisit (et
    optionnellement ajoute un alias à) les colonnes du dataset joint.
  </Accordion>

  <Accordion title="sequential_join — jointure flexible avec fallback et expressions">
    ```json theme={null}
    {
      "type": "sequential_join",
      "target_dataset": "libtaille",
      "join_column": "no_libtaille",
      "target_join_column": null,
      "fallback_join_column": null,
      "select_columns": [],
      "select_expressions": { "_size_label": "libelle" },
      "deduplicate_on": ["no_libtaille"],
      "join_type": "left"
    }
    ```

    Ajoute deux capacités par rapport à `join` : une `fallback_join_column` (clé secondaire
    de style COALESCE) et `select_expressions` (expressions SQL personnalisées sélectionnées
    de la cible).
  </Accordion>

  <Accordion title="aggregation_join — grouper + agréger, puis joindre">
    ```json theme={null}
    {
      "type": "aggregation_join",
      "target_dataset": "detcde",
      "group_by_columns": ["no_ligcde"],
      "aggregate_columns": [
        { "source_column": "pa", "function": "avg", "alias": "_detcde_pa", "cast_type": "double" }
      ],
      "join_column": "no_ligcde",
      "join_type": "left",
      "order_by": null,
      "order_desc": false
    }
    ```

    Agrège la cible avec `function` ∈ `avg`, `max`, `min`, `sum`, `count`, `first`,
    puis joint le résultat sur `join_column`.
  </Accordion>
</AccordionGroup>

### Remodèlement

<AccordionGroup>
  <Accordion title="unpivot — large vers long">
    ```json theme={null}
    {
      "type": "unpivot",
      "id_columns": ["product_id"],
      "value_columns": ["jan", "feb", "mar"],
      "variable_column": "month",
      "value_column": "value"
    }
    ```

    Transforme `{ id, col1, col2, … }` en lignes `{ id, variable, value }`.
    `variable_column`/`value_column` sont par défaut `variable`/`value`.
  </Accordion>
</AccordionGroup>

### Identité et taxonomie

<AccordionGroup>
  <Accordion title="generate_id — UUID déterministe à partir de colonnes clés">
    ```json theme={null}
    { "type": "generate_id", "output_column": "id", "key_columns": ["code"], "namespace": null }
    ```

    Dérive un id **déterministe** à partir de `key_columns` — les mêmes valeurs de clés
    produisent le même id entre les exécutions (idempotent), ce qui rend la ré-ingestion
    sûre. Un `namespace` optionnel cadre la dérivation (UUID v5). Par défaut :
    `output_column` = `id`, `key_columns` = toutes les colonnes non nulles.
  </Accordion>

  <Accordion title="id_mapping_output — persister un mapping code → id">
    ```json theme={null}
    { "type": "id_mapping_output", "mapping_type": "brands", "code_column": "code", "id_column": "id", "extra_columns": [] }
    ```

    Persiste un mapping de `code_column → id_column` sous `mapping_type`, pour que les
    promotions ultérieures (dans cette spécification ou une autre) puissent résoudre les
    clés étrangères par rapport à lui.
  </Accordion>

  <Accordion title="id_mapping_join — résoudre une FK via un mapping persisté">
    ```json theme={null}
    { "type": "id_mapping_join", "mapping_type": "brands", "source_column": "brand_code", "output_column": "brand_id", "extra_output_columns": ["shop_id"], "deduplicate": true }
    ```

    Cherche `source_column` dans le mapping créé par une `id_mapping_output` antérieure et
    écrit l'id résolu dans `output_column`.
  </Accordion>

  <Accordion title="taxonomy_mapping — résoudre un label en id de taxonomie">
    ```json theme={null}
    { "type": "taxonomy_mapping", "taxonomy_type": "size", "source_column": "_size_label", "source_id_column": "no_libtaille", "output_column": "taxonomy_id" }
    ```

    Résout un label textuel (ex. une taille ou une couleur) en id de taxonomie Solya.
    `source_id_column` fournit optionnellement la référence POS pour la désambiguïsation.
  </Accordion>
</AccordionGroup>

## Référence des sous-options

| Énumération                | Valeurs                                      |
| -------------------------- | -------------------------------------------- |
| Stratégie de déduplication | `drop`, `keep_first`, `keep_last`            |
| Type de jointure           | `left`, `inner`, `outer`, `right`            |
| Fonction d'agrégation      | `avg`, `max`, `min`, `sum`, `count`, `first` |
| Mode d'écriture            | `merge`, `append`, `overwrite_partition`     |

<Note>
  Les clés étrangères inter-spécifications fonctionnent via la paire `id_mapping_output` → `id_mapping_join` : une
  promotion publie le mapping, les promotions ultérieures le consomment. Voir les
  [exemples](/fr/developers/ingestion-specs/examples) travaillés.
</Note>
