Anotações semânticas do Saiku
O bloco genérico <Annotation> do Mondrian é um saco livre de chave/valor anexado a qualquer elemento de schema (Cube, Dimension, Hierarchy, Level, Measure, …). O Saiku reserva o namespace saiku.semantic.* dentro desse saco para um pequeno vocabulário tipado que dirige:
- A camada AI Ask — descrições e synonyms dão ao LLM contexto de negócio real; aggregation kinds, grains e required filters mantêm suas sugestões de query sãs.
- Proteção de PII — o marcador
saiku.semantic.pii=truevira um level ou measure para modo “estruturalmente presente mas opaco” em superfícies voltadas ao agente e bloqueia drillthrough, exposição por share/embed e caminhos de download de vazarem os valores subjacentes.
Todas as chaves são opcionais. Um schema sem nenhuma anotação saiku.semantic.* roda identicamente a um sem o bloco de anotação; o namespace é puramente aditivo.
Em resumo
| Chave de anotação | Válida em | Tipo / valores | Propósito |
|---|---|---|---|
saiku.semantic.description | Dimension · Measure · Level | texto livre | Descrição de negócio; exposta em /ai/schema para que o LLM tenha contexto |
saiku.semantic.synonyms | Dimension · Measure · Level | lista CSV | Nomes alternativos para que a AI possa resolver “country” → “Store Country” |
saiku.semantic.unit | Measure | texto livre (ex.: USD, kWh, count) | String de unidade; flui para o formato de célula AI {value, formatted, unit} |
saiku.semantic.currency | Measure | código ISO 4217 (ex.: USD, EUR) | Dica de moeda para measures monetárias |
saiku.semantic.aggregation_kind | Measure | enum: sum · count · distinct-count · non-additive | Semântica de agregação. Dirige semântica de sort/total na camada AI. |
saiku.semantic.cardinality | Level | enum: low · medium · high | Dica de contagem de membros. Dirige UX do picker e dicas de custo da AI. |
saiku.semantic.grain | Level | enum: year · quarter · month · week · day · hour · minute | Grão de tempo para o modal de filtro de data e inferência de gráfico de série temporal |
saiku.semantic.required_filters | Level | CSV de pares (hierarchy, level) | Levels que a AI deve incluir em filters sempre que este level é tocado |
saiku.semantic.pii | Level · Measure | booleano (true / false) | Marcador de PII — veja Proteção de PII abaixo |
Valores desconhecidos para os fields de tipo enum são logados em WARN com a lista permitida e caso contrário ignorados — um typo não quebra o schema.
Onde colocá-las
Anotações se anexam a qualquer elemento de schema via um bloco filho <Annotations> (XML) ou uma chave 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: sumLevels aceitam o mesmo formato <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: yearProteção de PII
Marcar um level ou measure com saiku.semantic.pii=true o inscreve na pilha de enforcement de PII do Saiku. A anotação é a declaração — o Saiku sobrepõe quatro pontos de enforcement em cima dela.
1. Projeção do schema AI — captions e samples removidos
O endpoint /ai/schema/{connection/catalog/schema/cube} alimenta o LLM com a estrutura do cubo. Para qualquer elemento marcado como PII ele remove:
captiondescriptionsynonyms- sample members (os valores de exemplo que a orientação do agente geralmente mostra)
O level ou measure ainda aparece estruturalmente (o agente sabe que existe e pode referenciá-lo por nome) mas os valores em si permanecem opacos. O agente pode planejar queries que incluam a coluna PII na projeção sem nunca ver linhas reais ou sample members.
2. Drillthrough returns= bloqueado
Requisições de drillthrough carregam um parâmetro returns= listando as colunas a exportar. Se qualquer token em returns= resolve para um level ou measure marcado como PII, o resolver lança AiPiiException e o endpoint retorna 400 com a coluna ofensora nomeada no corpo. O agente pode tentar de novo com uma projeção mais estreita que omite a coluna PII.
HTTP 400{ "error": "Column 'Prescriber' is annotated PII (saiku.semantic.pii=true) and cannot be returned via drillthrough."}3. Tokens de embed/share auto-redigem
Quando você cunha um token de embed ou share vinculado a uma query que toca um level marcado como PII, o inspetor escala a política de redação do token para FORCE_ON independentemente do default do operador. Destinatários do token veem células redigidas mesmo se o embed não foi explicitamente configurado para redação. Elimina o footgun “esqueci de ligar a redação” em links compartilhados.
4. Gating de política de dados da AI
Anotações PII são fatos de nível-cubo. Elas se pareiam com a variável de ambiente SAIKU_AI_POLICY, que é o dial de nível-deployment:
SAIKU_AI_POLICY | O que a AI vê |
|---|---|
schema-only | Só schema + sample members. Nenhum valor de cellset alcança o provider. |
aggregated | Totais de células agregadas alcançam o provider; células row-level removidas. |
row-level | Células completas alcançam o provider. Anotações PII ainda redigem especificidades. |
A anotação diz “isto é sensível”; a política diz “isto é o quanto de dados sensíveis deixamos passar”. Juntas elas decidem o que a AI vê por turno. O default é schema-only — deployments devem optar por camadas mais frouxas.
Scanner pré-voo
PiiScanner roda sobre o schema de um cubo no startup e loga linhas WARN para qualquer coluna cujo padrão de nome pareça ter formato de PII (casa contra *name*, *ssn*, *email*, *npi*, *dob*, etc.) mas não é anotada. O aviso inclui um snippet XML pronto para colar para que o autor do schema possa optar pela coluna sem procurar pela spec:
WARN [PiiScanner] Likely PII column 'prescribername' on level [Prescriber].[Prescriber] is not annotated. Add: <Annotations> <Annotation name="saiku.semantic.pii">true</Annotation> </Annotations>O scanner nunca muta o schema — ele apenas expõe candidatos. Autores de schema decidem o que é realmente sensível.
Exemplo trabalhado — cubo demo Pharma
O schema demo Pharma vem com anotações PII em nome do prescriber + NPI, e deixa specialty + decile não-anotados (esses são operacionalmente seguros de compartilhar). Trecho 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>Com isso no lugar, um agente AI perguntando “break down Quantity by Specialty” tem sucesso normalmente, mas “break down Quantity by Prescriber and download the rows” ou recusa (bloqueio de drillthrough) ou retorna buckets agregados com nomes de prescriber suprimidos (dependendo da camada de política da AI).
Exemplo trabalhado — cubo FoodMart
FoodMart vem com o conjunto descritivo completo de anotações (sem colunas PII — dados sintéticos). Retirado 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>O mesmo padrão escala através de cada dim + level. Cada linha é independentemente opcional, então você pode distribuir as anotações incrementalmente.
Validação e valores desconhecidos
- Fields de tipo enum (
aggregation_kind,cardinality,grain) validam contra sua lista permitida. Valores desconhecidos logam umWARNcom o valor ofensor + a lista permitida e o field default para não-definido. - Fields booleanos (
pii) aceitamtrue/falsede forma case-insensitive após trim. Qualquer outra coisa éfalse. Autores de schema recebem o default conservador se digitarem errado. - Fields de texto livre (
description,unit,currency) são aparados; strings vazias viramnull. synonymserequired_filtersparseiam como CSV; entradas vazias são descartadas.
Relacionado
- Exportando para Apache Ossie — como as anotações
saiku.semantic.*sobrevivem ao round-trip em YAML Ossie portável (description + synonyms sobem paraai_context; todo o resto viaja emcustom_extensionscomo dados de fornecedor SAIKU). - Extensões well-known do Ossie — o equivalente do lado Ossie (
saiku.display,saiku.roles,saiku.pii) para modelos YAML. Mesma intenção, formato de arquivo diferente. - Extensões: funções e formatters — o bloco genérico
<Annotation>sobre o qualsaiku.semantic.*constrói (e sua convenção de metadados de locale). - Schemas YAML — referência YAML completa, incluindo
annotations:em cada elemento. - Controle de acesso e roles — enforcement de acesso em nível de schema (complementa anotações PII: ACLs controlam quem pode consultar um cubo; anotações PII controlam o que o resultado expõe uma vez que podem).