> For the complete documentation index, see [llms.txt](https://docs.kiloiot.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kiloiot.io/kilo-docs-fr/kilo-iot-server/rules-engine/cel-reference.md).

# Référence CEL

Référence de syntaxe CEL pour les règles Kilo IoT — Common Expression Language utilisée dans les passerelles, scripts et alarmes.

Le moteur de règles utilise [CEL (Common Expression Language)](https://cel.dev) pour les expressions à l’intérieur de l’éditeur visuel de workflow — conditions des passerelles, calculs des tâches de script, messages d’alarme, recherches d’enrichissement et définitions des entrées/sorties. CEL est un langage d’expression rapide et sûr, conçu à l’origine par Google pour évaluer des conditions dans des politiques de sécurité et des systèmes d’infrastructure. La spécification complète du langage est disponible sur [GitHub](https://github.com/google/cel-spec).

CEL n’est pas un langage de programmation à usage général. Il évalue des expressions et renvoie des résultats. Il ne peut pas accéder au système de fichiers, effectuer des appels réseau, créer des boucles ni modifier l’état externe. Cela permet d’exécuter en toute sécurité des expressions définies par l’utilisateur, sans risque pour la plateforme ni pour les autres règles.

La plupart des règles Kilo n’utilisent que quelques expressions courtes. La structure du workflow reste visuelle et basée sur BPMN ; CEL est la couche de précision qui rend ces workflows utiles dans de vrais scénarios de production.

***

## Données disponibles

Chaque expression du moteur de règles a accès aux variables de processus via l’ `vars` . Les données disponibles dépendent de l’endroit de la règle où l’expression s’exécute.

Les champs dans `vars` peuvent être accessibles avec **la notation par point** ou **la notation entre crochets**:

```cel
vars.temperature        // notation par point — fonctionne pour les identifiants simples
vars["sensor_id"]       // notation entre crochets — fonctionne pour n’importe quel nom
vars["my-sensor"]       // notation entre crochets requise — tiret dans le nom
```

La notation par point est pratique pour la plupart des noms de champ. La notation entre crochets est requise lorsqu’un nom de champ contient des tirets, des espaces ou d’autres caractères spéciaux, ou lorsque le nom du champ est calculé dynamiquement à partir d’une autre expression.

### Disponible après l’événement de démarrage

**Ce que fournit l’événement de démarrage dépend de sa source de démarrage**, donc vérifiez de quel type de règle il s’agit avant d’utiliser une variable.

**Source de démarrage : lecture du capteur**

| Variable         | Type                   | Description                                                                                                                                                   |
| ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vars.value`     | Varie selon le capteur | La lecture du capteur qui a déclenché la règle. Peut être un nombre (température, humidité), une chaîne (état de la porte) ou un booléen (mouvement détecté). |
| `vars.sensor_id` | chaîne                 | L’identifiant unique du capteur qui a déclenché la règle.                                                                                                     |
| `vars.timestamp` | horodatage             | Le moment où la lecture du capteur a été enregistrée.                                                                                                         |

**Source de démarrage : condition de déclenchement**

| Variable            | Type   | Description                                                                                                                                                                                                       |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vars.device_name`  | chaîne | Le nom de l’appareil qui a satisfait la condition. Sur une [déclencheur](/kilo-docs-fr/kilo-iot-server/rules-engine/triggers.md) règle surveillant plusieurs appareils, cela identifie celui qui s’est déclenché. |
| `vars.subject_kind` | chaîne | Le type de ressource surveillée. Il s’agit actuellement de `appareil`.                                                                                                                                            |
| `vars.subject_id`   | chaîne | L’identifiant de l’appareil surveillé qui a satisfait la condition.                                                                                                                                               |
| `vars.sensor_id`    | chaîne | L’identifiant du capteur utilisé pour associer l’exécution et toute alarme à l’appareil surveillé.                                                                                                                |
| `vars.detector_id`  | chaîne | L’identifiant du déclencheur qui a démarré la règle.                                                                                                                                                              |
| `vars.timestamp`    | int    | L’heure du signal de déclenchement en secondes Unix.                                                                                                                                                              |

> **`vars.value` n’existe pas dans une règle déclenchée par un déclencheur.** Un déclencheur signale une transition de condition plutôt que de fournir à la règle un événement de capteur normalisé. Cela vaut à la fois pour les déclencheurs immédiats et de durée. Une expression faisant référence à `vars.value` échouera à chaque signal de déclenchement — vérifiez cela en premier lors de la conversion d’une règle existante depuis **Lecture du capteur**.

### Disponible après les tâches de script

Si une tâche de script renvoie une map, chaque clé est fusionnée dans `vars`. Par exemple, après une tâche de script avec l’expression `{"level": "critical", "delta": 15.2}`, les nœuds en aval peuvent accéder à `vars.level` et `vars.delta`.

Les sorties de la tâche de script, de la passerelle, de Définir l’alarme, d’Enrichissement et de l’événement de démarrage peuvent également publier des valeurs nommées dans `vars` après l’exécution de ce nœud.

### Disponible après l’enrichissement

Après qu’un nœud d’enrichissement stocke des données sous un nom de variable (par ex. : `outdoor_temp`), les données enrichies sont accessibles comme un objet imbriqué :

| Variable                         | Type   | Description                                    |
| -------------------------------- | ------ | ---------------------------------------------- |
| `vars.outdoor_temp.value`        | Varie  | La lecture la plus récente du capteur enrichi. |
| `vars.outdoor_temp.sensor_id`    | chaîne | L’identifiant du capteur enrichi.              |
| `vars.outdoor_temp.type`         | chaîne | Le type de données du capteur enrichi.         |
| `vars.outdoor_temp.timestamp_ms` | int    | Horodatage de la lecture en millisecondes.     |

### Variables personnalisées

Les expressions d’entrée et de sortie sur les événements de démarrage, les passerelles exclusives, les tâches de script, les nœuds Définir l’alarme et les nœuds d’enrichissement permettent de nommer une valeur calculée. Choisissez entre elles selon la distance que la valeur doit parcourir :

* **Sorties** ajoutent le nom au contexte du workflow. Il est accessible sous la forme `vars.<name>` dans chaque nœud exécuté ensuite.
* **Entrées** restent sur le nœud sur lequel vous les avez définies. Elles sont calculées avant l’exécution de ce nœud et ses propres expressions peuvent les utiliser — les entrées d’une passerelle sont disponibles pour les conditions de flux de cette passerelle — mais les nœuds suivants ne peuvent pas les lire.

Si une valeur calculée sur un nœud est nécessaire plus loin dans la règle, faites-en une sortie. Voir [Référence des nœuds](/kilo-docs-fr/kilo-iot-server/rules-engine/node-reference.md#do-i-need-the-input-and-output-parameters) pour un exemple.

***

## Système de types

CEL prend en charge les types suivants. Toute valeur dans une expression se résout en l’un de ceux-ci.

| Type         | Description                | Exemple                                 |
| ------------ | -------------------------- | --------------------------------------- |
| `bool`       | Booléen vrai ou faux       | `true`, `vars.value > 30`               |
| `int`        | Entier signé               | `42`, `-7`                              |
| `uint`       | Entier non signé           | `42u`                                   |
| `double`     | Nombre à virgule flottante | `30.5`, `-0.7`                          |
| `chaîne`     | Texte                      | `"critical"`, `"Alerte de température"` |
| `bytes`      | Séquence d’octets          | `b"\x00\xff"`                           |
| `list`       | Collection ordonnée        | `[1, 2, 3]`, `["a", "b"]`               |
| `map`        | Paires clé-valeur          | `{"level": "high", "count": 5}`         |
| `null_type`  | Valeur nulle               | `null`                                  |
| `horodatage` | Un instant précis          | `vars.timestamp`                        |
| `duration`   | Une durée                  | `duration("5m")`                        |

### Conversions de type

Utilisez les fonctions de conversion intégrées pour convertir entre les types :

| Fonction   | Description                   | Exemple              |
| ---------- | ----------------------------- | -------------------- |
| `int()`    | Convertir en entier           | `int(vars.value)`    |
| `uint()`   | Convertir en entier non signé | `uint(42)`           |
| `double()` | Convertir en double           | `double(vars.value)` |
| `string()` | Convertir en chaîne           | `string(vars.value)` |
| `type()`   | Renvoie le type d’une valeur  | `type(vars.value)`   |

La conversion en chaîne est particulièrement importante lors de la création de messages de motivation d’alarme, car CEL exige une conversion explicite des nombres en chaînes pour la concaténation.

***

## Opérateurs

### Opérateurs de comparaison

| Opérateur | Signification       | Exemple                    |
| --------- | ------------------- | -------------------------- |
| `==`      | Égal à              | `vars.value == 0`          |
| `!=`      | Différent de        | `vars.status != "offline"` |
| `>`       | Supérieur à         | `vars.value > 30.0`        |
| `>=`      | Supérieur ou égal à | `vars.value >= 100`        |
| `<`       | Inférieur à         | `vars.value < 5`           |
| `<=`      | Inférieur ou égal à | `vars.value <= 25.0`       |

### Opérateurs logiques

| Opérateur | Signification | Exemple                                |
| --------- | ------------- | -------------------------------------- |
| `&&`      | ET logique    | `vars.value > 30 && vars.value < 50`   |
| `\|\|`    | OU logique    | `vars.value < 0 \|\| vars.value > 100` |
| `!`       | NON logique   | `!has(vars.humidity)`                  |

### Opérateurs arithmétiques

| Opérateur | Signification  | Exemple                                |
| --------- | -------------- | -------------------------------------- |
| `+`       | Addition       | `vars.value + 10`                      |
| `-`       | Soustraction   | `vars.value - vars.outdoor_temp.value` |
| `*`       | Multiplication | `vars.value * 1.8 + 32`                |
| `/`       | Division       | `vars.value / 100.0`                   |
| `%`       | Modulo         | `vars.value % 10`                      |

### Opérateur ternaire

L’opérateur ternaire `? :` renvoie l’une de deux valeurs en fonction d’une condition :

```cel
vars.value > 80 ? "critical" : "normal"
```

Les expressions ternaires peuvent être imbriquées pour une classification à plusieurs niveaux :

```cel
vars.value > 80 ? "critical" : vars.value > 50 ? "warning" : "normal"
```

### Opérateurs et fonctions de chaîne

| Opération            | Syntaxe        | Exemple                                 |
| -------------------- | -------------- | --------------------------------------- |
| Concaténation        | `+`            | `"Temp : " + string(vars.value)`        |
| Longueur             | `size()`       | `vars.name.size() > 0`                  |
| Contient             | `contains()`   | `vars.status.contains("error")`         |
| Commence par         | `startsWith()` | `vars.zone.startsWith("warehouse")`     |
| Se termine par       | `endsWith()`   | `vars.device_id.endsWith("-prod")`      |
| Correspondance regex | `matches()`    | `vars.device_id.matches("^WH-[0-9]+$")` |

### Fonctions de collection

| Fonction                   | Description                                      | Exemple                                       |
| -------------------------- | ------------------------------------------------ | --------------------------------------------- |
| `x dans la liste`          | Vérification d’appartenance                      | `vars.zone in ["A", "B", "C"]`                |
| `clé dans la map`          | La clé existe dans la map                        | `"humidity" in vars`                          |
| `list.exists(x, expr)`     | Vrai si un élément satisfait l’expression        | `[10, 25, 40].exists(t, t > 30)`              |
| `list.all(x, expr)`        | Vrai si tous les éléments satisfont l’expression | `[10, 25, 40].all(t, t > 0)`                  |
| `list.filter(x, expr)`     | Renvoie les éléments qui satisfont l’expression  | `[10, 25, 40].filter(t, t > 20)` → `[25, 40]` |
| `list.map(x, expr)`        | Transforme chaque élément                        | `[1, 2, 3].map(x, x * 10)` → `[10, 20, 30]`   |
| `list.exists_one(x, expr)` | Vrai si exactement un élément satisfait          | `[10, 25, 40].exists_one(t, t > 30)` → `true` |

### Variables intermédiaires avec cel.bind

Utilisez `cel.bind()` pour définir une variable temporaire à l’intérieur d’une expression, en évitant un calcul redondant :

```cel
cel.bind(delta, vars.value - vars.baseline.value,
  {"delta": delta, "severity": delta > 20 ? "critical" : delta > 10 ? "warning" : "normal"}
)
```

Le premier argument nomme la variable, le second calcule sa valeur, et le troisième est l’expression qui l’utilise.

### Vérification d’existence

| Fonction | Description                       | Exemple              |
| -------- | --------------------------------- | -------------------- |
| `has()`  | Renvoie `true` si le champ existe | `has(vars.humidity)` |

Utilisez `has()` avant d’accéder à une variable qui pourrait ne pas exister. Après un nœud d’enrichissement avec un événement d’erreur de frontière, par exemple, les données enrichies ne sont disponibles que si l’enrichissement a réussi.

***

## Modèles d’expressions courants

### Vérification de seuil

Le modèle le plus simple et le plus courant. Utilisé dans les conditions de passerelle pour brancher sur une seule valeur.

```cel
vars.value > 30.0
```

Renvoie `true` si la lecture du capteur dépasse 30. Fonctionne dans les conditions de passerelle exclusive pour orienter le flux.

### Vérification d’intervalle

Tester si une lecture se situe dans une plage acceptable.

```cel
vars.value >= 20.0 && vars.value <= 25.0
```

Utile pour la conformité CVC, la surveillance de la chaîne du froid ou tout scénario où les bornes supérieure et inférieure comptent.

### Vérification multi-condition

Combinez les vérifications sur plusieurs variables. Ce modèle apparaît souvent après l’enrichissement, lorsque des données provenant de plus d’un capteur sont disponibles.

```cel
vars.value > 30.0 && has(vars.humidity) && vars.humidity < 20
```

Utilisez toujours `has()` avant de référencer des variables provenant de sources optionnelles (enrichissement, tâches de script précédentes avec sorties conditionnelles).

### Classification de la gravité (tâche de script)

Renvoie une map depuis une tâche de script pour classer une lecture en catégories. Chaque clé devient une variable de processus distincte.

```cel
{"level": vars.value > 80 ? "critical" : vars.value > 50 ? "warning" : "normal"}
```

Après cette tâche de script, `vars.level` est disponible pour les conditions de passerelle ou les messages d’alarme.

### Message d’alarme dynamique (Définir l’alarme)

Construisez une chaîne lisible par l’humain qui inclut des données de capteur en temps réel. Le champ de message de motivation dans un nœud Définir l’alarme utilise ce modèle.

```cel
"Température " + string(vars.value) + " degrés a dépassé le seuil de " + string(vars.threshold)
```

N’oubliez pas d’utiliser `string()` pour convertir les valeurs numériques — CEL ne convertit pas implicitement les nombres en chaînes lors de la concaténation.

### Vérification des données enrichies (après enrichissement)

Référencez une valeur récupérée par un nœud d’enrichissement. Le nom de variable utilisé dans la configuration d’enrichissement devient la clé sous `vars`.

```cel
vars.outdoor_temp.value > 35.0
```

### Valeurs dérivées calculées (tâche de script)

Calculez de nouvelles valeurs à partir de plusieurs entrées et stockez-les pour une utilisation en aval.

```cel
{"delta": vars.value - vars.outdoor_temp.value, "needs_alarm": vars.value - vars.outdoor_temp.value > 10}
```

Après cette tâche de script, la passerelle peut vérifier `vars.needs_alarm` directement, et le message d’alarme peut faire référence à `vars.delta` pour le contexte.

### Combiner la classification avec des valeurs calculées

Une seule tâche de script peut effectuer plusieurs calculs à la fois.

```cel
{
  "severity": vars.value > 90 ? "critical" : vars.value > 70 ? "warning" : "normal",
  "deviation": vars.value - vars.baseline.value,
  "message": "Lecture " + string(vars.value) + ", écart de " + string(vars.value - vars.baseline.value) + " par rapport à la valeur de base"
}
```

***

## Où CEL est utilisé

Les expressions CEL apparaissent à plusieurs endroits dans le moteur de règles. Le contexte détermine ce que l’expression doit renvoyer.

| Emplacement                                  | Type de retour attendu                  | Objectif                                                                                           |
| -------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Événement de démarrage — Entrées**         | N’importe quel type                     | Créer des valeurs d’aide locales lorsque l’événement de capteur entre dans la règle.               |
| **Événement de démarrage — Sorties**         | N’importe quel type                     | Publier des valeurs nommées dans le contexte partagé du workflow.                                  |
| **Tâche de script — Script**                 | N’importe quel type (map de préférence) | Transformer ou classer des données. Les clés de la map fusionnent dans les variables de processus. |
| **Tâche de script — Entrées**                | N’importe quel type                     | Créer des valeurs d’aide locales avant l’exécution du script.                                      |
| **Tâche de script — Sorties**                | N’importe quel type                     | Publier des valeurs nommées supplémentaires après l’exécution de la tâche.                         |
| **Passerelle exclusive — Condition**         | `bool`                                  | Orienter l’exécution. Doit renvoyer `true` ou `false`.                                             |
| **Passerelle exclusive — Entrées**           | N’importe quel type                     | Préparer les valeurs locales utilisées par les conditions de branche.                              |
| **Passerelle exclusive — Sorties**           | N’importe quel type                     | Publier des valeurs après la décision d’orientation.                                               |
| **Définir l’alarme — Message de motivation** | `chaîne`                                | Décrire pourquoi l’alarme a été déclenchée. Affiché aux intervenants.                              |
| **Définir l’alarme — Entrées**               | N’importe quel type                     | Préparer les valeurs avant l’exécution de l’action d’alarme.                                       |
| **Définir l’alarme — Sorties**               | N’importe quel type                     | Publier des valeurs après l’exécution du nœud d’alarme.                                            |
| **Enrichissement — ID du capteur**           | `chaîne`                                | Identifier le capteur à partir duquel récupérer les données.                                       |
| **Enrichissement — Entrées**                 | N’importe quel type                     | Préparer les valeurs avant l’exécution de la recherche.                                            |
| **Enrichissement — Sorties**                 | N’importe quel type                     | Publier des valeurs après que le résultat de l’enrichissement est disponible.                      |

### Portée des entrées et sorties

Les entrées et sorties sur un nœud servent à des fins différentes et ont une visibilité différente :

* **Entrées** créent **locales** des variables limitées au nœud actuel uniquement. Elles ne modifient pas le contexte partagé du workflow. Les expressions d’entrée sont évaluées par rapport à l’état `vars` actuel. Utilisez-les pour préparer des valeurs d’aide ou pré-calculer des résultats intermédiaires avant l’exécution de la logique principale du nœud.
* **Sorties** écrivent des valeurs dans le **partagé** contexte du workflow (`vars`). Les expressions de sortie sont évaluées dans la portée locale du nœud — qui inclut à la fois l'original `vars` et tous les locaux définis par l'entrée. Les valeurs publiées par les sorties persistent et sont accessibles à tous les nœuds en aval.

**Conséquence pratique :** Si vous définissez une entrée nommée `threshold` sur une passerelle exclusive, les nœuds en aval ne peuvent pas voir `vars.threshold` — elle n'existe que pendant l'évaluation de la condition de cette passerelle. Pour rendre une valeur calculée disponible en aval, définissez-la plutôt comme une sortie.

***

## Sécurité et bac à sable

CEL est isolé par conception. Les expressions s'exécutent dans un environnement restreint sans accès à :

* Le système de fichiers
* Les ressources réseau
* Les horloges système (sauf via les variables d'horodatage fournies)
* Les services externes
* L'état mutable en dehors de la propre évaluation de l'expression

Une expression ne peut pas créer de boucles infinies, allouer une mémoire illimitée ou affecter d'autres règles. Si une expression échoue (erreur de type, division par zéro, référence à une variable manquante sans `has()` garde), le nœud qui la contient lève une erreur. Attachez un événement d'erreur de frontière pour gérer ces échecs avec élégance.

***

## Fonctions de la plateforme

En plus de la bibliothèque CEL standard, le moteur de règles fournit deux fonctions spécifiques à la plateforme.

### error(message)

Prend un argument de type chaîne et produit toujours une valeur d'erreur. Lorsqu'une expression de tâche de script s'évalue à une erreur, le moteur vérifie la présence d'un événement d'erreur de frontière attaché. S'il en existe un, l'exécution emprunte le chemin d'erreur. Sinon, l'erreur arrête la règle.

Utilisez `error()` pour une défaillance conditionnelle délibérée — des situations où une condition de données spécifique doit déclencher le chemin de traitement des erreurs plutôt que de poursuivre l'exécution normale.

```cel
vars.temperature > 200 ? error("surchauffe critique détectée") : {"status": "ok"}
```

Dans cet exemple, les températures supérieures à 200 font volontairement échouer la tâche de script. Si un événement d'erreur de frontière est attaché, le chemin d'erreur s'exécute (peut-être en déclenchant une alarme d'urgence). En dessous de 200, la tâche de script produit `{"status": "ok"}` comme normal.

```cel
has(vars.calibration_date) ? {"calibrated": true} : error("capteur non calibré")
```

### random()

Renvoie un nombre à virgule flottante pseudo-aléatoire dans l'intervalle \[0.0, 1.0). Utile pour l'échantillonnage probabiliste, le routage basé sur un pourcentage ou la génération d'identifiants aléatoires.

```cel
random() < 0.1 ? "échantillonné" : "ignoré"
```

```cel
{"random_id": random() * 1000000.0}
```

***

## Conseils pratiques

**Gardez des expressions ciblées.** Une seule expression doit faire une seule chose. Si vous devez classifier une mesure, calculer un delta et construire un message, utilisez plusieurs tâches de script plutôt qu'une seule expression complexe. Cela rend la règle plus facile à lire, à déboguer et à maintenir.

**Privilégiez d'abord la structure visuelle.** Utilisez le canevas pour montrer le workflow, puis utilisez CEL dans les champs pertinents. Un diagramme BPMN lisible avec quelques expressions claires est plus facile à auditer qu'une règle qui cache trop de logique dans une seule immense expression.

**Utilisez `has()` avant les champs facultatifs.** Toute variable provenant d'un enrichissement, de tâches de script conditionnelles ou d'entrées facultatives peut ne pas exister. Y accéder sans `has()` et l'expression lève une erreur.

```cel
has(vars.outdoor_temp) && vars.outdoor_temp.value > 35.0
```

**Convertissez explicitement les types.** CEL n'effectue pas de conversion de type implicite. Lorsque vous construisez des messages d'alerte, convertissez les nombres avec `string()`. Lors d'opérations arithmétiques, assurez-vous que les deux opérandes sont du même type numérique — mélanger `int` et `double` peut produire des résultats inattendus.

**Utilisez des maps pour les sorties à plusieurs valeurs.** Retourner une map depuis une tâche de script est la manière standard de rendre plusieurs valeurs calculées disponibles en aval. Chaque clé devient une variable de processus indépendante.

**Testez les expressions sur des cas limites.** Réfléchissez à ce qui se passe lorsqu'un capteur renvoie zéro, une valeur négative ou un nombre anormalement élevé. Les conditions de passerelle doivent gérer toute la plage des entrées possibles sans orienter vers une branche non souhaitée.

**Nommez les variables de façon descriptive.** Lorsque vous définissez des noms de variables d'enrichissement ou des clés de sortie de tâche de script, utilisez des noms qui décrivent les données — `outdoor_temp`, `humidity_reading`, `severity_level` — pas `x`, `val2`, ou `tmp`. Vos collègues les liront lorsqu'ils examineront ou modifieront la règle.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kiloiot.io/kilo-docs-fr/kilo-iot-server/rules-engine/cel-reference.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
