Annotations sémantiques Saiku
Le bloc générique <Annotation> de Mondrian est un sac clé/valeur en forme libre attaché à n’importe quel élément de schéma (Cube, Dimension, Hierarchy, Level, Measure, …). Saiku réserve l’espace de noms saiku.semantic.* à l’intérieur de ce sac pour un petit vocabulaire typé qui pilote :
- La couche AI Ask — les descriptions et les synonymes donnent au LLM un vrai contexte métier ; les types d’agrégation, les grains et les filtres requis gardent ses suggestions de requêtes sensées.
- La protection PII — le marqueur
saiku.semantic.pii=truebascule un niveau ou une mesure en mode « structurellement présent mais opaque » dans les surfaces destinées aux agents et empêche le drillthrough, l’exposition par partage/embed et les chemins de téléchargement de laisser fuir les valeurs sous-jacentes.
Toutes les clés sont optionnelles. Un schéma sans aucune annotation saiku.semantic.* tourne identiquement à un schéma sans le bloc d’annotation ; l’espace de noms est purement additif.
En un coup d’œil
| Clé d’annotation | Valide sur | Type / valeurs | Objectif |
|---|---|---|---|
saiku.semantic.description | Dimension · Measure · Level | texte libre | Description métier ; exposée dans /ai/schema pour que le LLM ait du contexte |
saiku.semantic.synonyms | Dimension · Measure · Level | liste CSV | Noms alternatifs pour que l’IA puisse résoudre « country » → « Store Country » |
saiku.semantic.unit | Measure | texte libre (par ex. USD, kWh, count) | Chaîne d’unité ; alimente la forme de cellule IA {value, formatted, unit} |
saiku.semantic.currency | Measure | code ISO 4217 (par ex. USD, EUR) | Indice de devise pour les mesures monétaires |
saiku.semantic.aggregation_kind | Measure | enum : sum · count · distinct-count · non-additive | Sémantique d’agrégation. Pilote la sémantique tri/total dans la couche IA. |
saiku.semantic.cardinality | Level | enum : low · medium · high | Indice de nombre de membres. Pilote l’UX du sélecteur et les indices de coût IA. |
saiku.semantic.grain | Level | enum : year · quarter · month · week · day · hour · minute | Grain temporel pour la modale de filtre de date et l’inférence de graphique de série temporelle |
saiku.semantic.required_filters | Level | CSV de paires (hierarchy, level) | Niveaux que l’IA doit inclure dans filters chaque fois que ce niveau est touché |
saiku.semantic.pii | Level · Measure | booléen (true / false) | Marqueur PII — voir Protection PII ci-dessous |
Les valeurs inconnues pour les champs de type enum sont loggées en WARN avec la liste autorisée et sinon ignorées — une coquille ne casse pas le schéma.
Où les placer
Les annotations s’attachent à n’importe quel élément de schéma via un bloc enfant <Annotations> (XML) ou une clé annotations: (YAML).
<Measure name="Quantity" column="quantity_units" aggregator="sum" formatString="#,##0"> <Annotations> <Annotation name="saiku.semantic.description">Total units prescribed (across all dispensings).</Annotation> <Annotation name="saiku.semantic.synonyms">units, quantity, scripts</Annotation> <Annotation name="saiku.semantic.unit">count</Annotation> <Annotation name="saiku.semantic.aggregation_kind">sum</Annotation> </Annotations></Measure>measures: - name: Quantity column: quantity_units aggregator: sum formatString: "#,##0" annotations: saiku.semantic.description: "Total units prescribed (across all dispensings)." saiku.semantic.synonyms: "units, quantity, scripts" saiku.semantic.unit: count saiku.semantic.aggregation_kind: sumLes niveaux acceptent la même forme <Annotations> :
<Level name="Year" column="cal_year" type="Numeric" uniqueMembers="true" levelType="TimeYears"> <Annotations> <Annotation name="saiku.semantic.description">Calendar year.</Annotation> <Annotation name="saiku.semantic.synonyms">annual, yearly, fiscal year, y</Annotation> <Annotation name="saiku.semantic.cardinality">low</Annotation> <Annotation name="saiku.semantic.grain">year</Annotation> </Annotations></Level>- name: Year column: cal_year type: Numeric uniqueMembers: true levelType: TimeYears annotations: saiku.semantic.description: "Calendar year." saiku.semantic.synonyms: "annual, yearly, fiscal year, y" saiku.semantic.cardinality: low saiku.semantic.grain: yearProtection PII
Marquer un niveau ou une mesure avec saiku.semantic.pii=true l’inscrit dans la pile d’application PII de Saiku. L’annotation est la déclaration — Saiku superpose par-dessus quatre points d’application.
1. Projection du schéma IA — captions et échantillons retirés
L’endpoint /ai/schema/{connection/catalog/schema/cube} alimente le LLM avec la structure du cube. Pour tout élément marqué PII, il retire :
captiondescriptionsynonyms- les membres échantillons (les valeurs d’exemple que les guidances agent montrent habituellement)
Le niveau ou la mesure apparaît toujours structurellement (l’agent sait qu’il existe et peut le référencer par son nom) mais les valeurs elles-mêmes restent opaques. L’agent peut planifier des requêtes qui incluent la colonne PII dans la projection sans jamais voir de lignes réelles ni de membres échantillons.
2. returns= du drillthrough bloqué
Les requêtes de drillthrough portent un paramètre returns= listant les colonnes à exporter. Si un jeton dans returns= se résout en un niveau ou une mesure marqué PII, le résolveur lève AiPiiException et l’endpoint renvoie 400 avec la colonne fautive nommée dans le corps. L’agent peut réessayer avec une projection plus étroite qui omet la colonne PII.
HTTP 400{ "error": "Column 'Prescriber' is annotated PII (saiku.semantic.pii=true) and cannot be returned via drillthrough."}3. Les jetons embed/share auto-caviardent
Lorsque vous frappez un jeton embed ou share lié à une requête qui touche un niveau marqué PII, l’inspecteur escalade la politique de caviardage du jeton à FORCE_ON quelle que soit la valeur par défaut de l’opérateur. Les destinataires du jeton voient des cellules caviardées même si l’embed n’était pas explicitement configuré pour le caviardage. Élimine le piège du « j’ai oublié d’activer le caviardage » sur les liens partagés.
4. Gating de la politique de données IA
Les annotations PII sont des faits au niveau du cube. Elles s’associent à la variable d’environnement SAIKU_AI_POLICY, qui est le cadran au niveau du déploiement :
SAIKU_AI_POLICY | Ce que l’IA voit |
|---|---|
schema-only | Schéma + membres échantillons uniquement. Aucune valeur de cellset n’atteint le fournisseur. |
aggregated | Les totaux de cellules agrégés atteignent le fournisseur ; les cellules au niveau ligne sont retirées. |
row-level | Les cellules complètes atteignent le fournisseur. Les annotations PII caviardent quand même les détails. |
L’annotation dit « ceci est sensible » ; la politique dit « voici la quantité de données sensibles qu’on laisse passer ». Ensemble, elles décident ce que l’IA voit par tour. Le défaut est schema-only — les déploiements doivent opter pour des paliers plus permissifs.
Scanner pré-vol
PiiScanner s’exécute sur le schéma d’un cube au démarrage et logge des lignes WARN pour toute colonne dont le motif de nom ressemble à du PII (correspond à *name*, *ssn*, *email*, *npi*, *dob*, etc.) mais n’est pas annotée. L’avertissement inclut un extrait XML prêt à coller pour que l’auteur du schéma puisse inscrire la colonne sans chercher dans la spec :
WARN [PiiScanner] Likely PII column 'prescribername' on level [Prescriber].[Prescriber] is not annotated. Add: <Annotations> <Annotation name="saiku.semantic.pii">true</Annotation> </Annotations>Le scanner ne mute jamais le schéma — il ne fait que faire remonter des candidats. Les auteurs de schéma décident de ce qui est réellement sensible.
Exemple détaillé — cube de démo Pharma
Le schéma de démo Pharma est livré avec des annotations PII sur le nom du prescripteur + le NPI, et laisse la spécialité + le décile non annotés (ceux-ci sont sûrs à partager sur le plan opérationnel). Extrait de saiku-home/data/Pharma.xml :
<Dimension name="Prescriber" foreignKey="prescriberkey"> <Hierarchy hasAll="true" primaryKey="prescriberkey"> <Table name="dim_prescriber" schema="public"/> <Level name="Specialty" column="specialty" uniqueMembers="false"/> <Level name="Decile" column="decile" type="Numeric" uniqueMembers="false"/> <Level name="Prescriber" column="prescriberkey" nameColumn="prescribername" type="Numeric" uniqueMembers="true"> <Annotations> <Annotation name="saiku.semantic.pii">true</Annotation> </Annotations> </Level> </Hierarchy> <Hierarchy name="NPI" hasAll="true" primaryKey="prescriberkey"> <Table name="dim_prescriber" schema="public"/> <Level name="NPI" column="prescribernpi" type="Numeric" uniqueMembers="true"> <Annotations> <Annotation name="saiku.semantic.pii">true</Annotation> </Annotations> </Level> </Hierarchy></Dimension>Avec cela en place, un agent IA demandant « ventile Quantity par Specialty » réussit normalement, mais « ventile Quantity par Prescriber et télécharge les lignes » soit refuse (blocage du drillthrough) soit renvoie des seaux agrégés avec les noms de prescripteurs supprimés (selon le palier de politique IA).
Exemple détaillé — cube FoodMart
FoodMart est livré avec l’ensemble complet d’annotations descriptives (pas de colonnes PII — données synthétiques). Extrait de saiku-home/data/FoodMart4.xml :
<Level name="Store Country" column="store_country" uniqueMembers="true"> <Annotations> <Annotation name="saiku.semantic.description">Store's country.</Annotation> <Annotation name="saiku.semantic.synonyms">nation, country code</Annotation> <Annotation name="saiku.semantic.cardinality">low</Annotation> </Annotations></Level><Level name="Year" column="the_year" type="Numeric" uniqueMembers="true" levelType="TimeYears"> <Annotations> <Annotation name="saiku.semantic.description">Calendar year.</Annotation> <Annotation name="saiku.semantic.synonyms">annual, yearly, fiscal year, y</Annotation> <Annotation name="saiku.semantic.cardinality">low</Annotation> <Annotation name="saiku.semantic.grain">year</Annotation> </Annotations></Level>Le même motif passe à l’échelle sur chaque dim + niveau. Chaque ligne est indépendamment optionnelle, donc vous pouvez déployer les annotations de manière incrémentale.
Validation et valeurs inconnues
- Les champs de type enum (
aggregation_kind,cardinality,grain) valident par rapport à leur liste autorisée. Les valeurs inconnues loggent unWARNavec la valeur fautive + la liste autorisée et le champ revient à non défini. - Les champs booléens (
pii) acceptenttrue/falseinsensibles à la casse après trim. Toute autre chose vautfalse. Les auteurs de schéma obtiennent le défaut conservateur s’ils font une faute de frappe. - Les champs de texte libre (
description,unit,currency) sont trimés ; les chaînes vides deviennentnull. synonymsetrequired_filterssont parsés comme CSV ; les entrées vides sont abandonnées.
Voir aussi
- Export vers Apache Ossie — comment les annotations
saiku.semantic.*survivent au round-trip dans du YAML Ossie portable (description + synonymes montent dansai_context; tout le reste voyage danscustom_extensionscomme données fournisseur SAIKU). - Extensions Ossie bien connues — l’équivalent côté Ossie (
saiku.display,saiku.roles,saiku.pii) pour les modèles YAML. Même intention, format de fichier différent. - Extensions : fonctions et formatteurs — le bloc générique
<Annotation>sur lequelsaiku.semantic.*se construit (et sa convention de métadonnées de locale). - Schémas YAML — référence YAML complète, y compris
annotations:sur chaque élément. - Contrôle d’accès et rôles — application d’accès au niveau du schéma (complète les annotations PII : les ACL contrôlent qui peut interroger un cube ; les annotations PII contrôlent ce que le résultat expose une fois qu’ils le peuvent).