/** * Semantic Layer: metric and dimension definitions parsed from YAML configs. * Maps to the architecture spec's semantic-layer/ directory structure. */ import { type SQLDialect } from './sql-dialect.js'; export interface SemanticSourceMetadata { provider: string; objectType: string; objectId: string; objectName?: string; importedAt?: string; extra?: Record; } export interface MetricDefinition { name: string; label: string; description: string; domain: string; status?: string; sql: string; type: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'custom'; table: string; filters?: Record; tags?: string[]; owner?: string; cube?: string; /** * Semantic models that own the metric's input measures. A single-model * derived metric also carries `cube`; cross-model metrics intentionally do * not, but retain every owner here for discovery and runtime qualification. */ semanticModelIds?: string[]; aggregation?: string; metricType?: string; typeParams?: Record; filter?: string | Record | Array>; aggTimeDimension?: string; /** Display contract (currency/percent/decimals) from dbt meta or DQL YAML. */ displayFormat?: SemanticDisplayFormat; /** * Distinguishes a real dbt metric from a dbt MEASURE that was projected into * the metrics map for native composition (see {@link SemanticLayer.addCube}). * `listMetrics({ includeMeasures: false })` filters on this so the UI metric * picker and the agent's metric matcher stop seeing measures as duplicate * metrics. Undefined is treated as a real metric for backward compatibility. */ objectKind?: 'metric' | 'measure'; source?: SemanticSourceMetadata; } /** * How a metric/measure value should be RENDERED. Declared once in metadata * (dbt `meta: {format: currency, currency: USD, decimals: 2}` or a DQL YAML * `format:` block) so every surface — narration, synthesis, tables, charts — * formats identically instead of each layer guessing from the column name. */ export interface SemanticDisplayFormat { kind: 'currency' | 'percent' | 'number' | 'count' | 'duration'; currency?: string; decimals?: number; } export interface DimensionDefinition { name: string; label: string; description: string; domain?: string; status?: string; sql: string; type: 'string' | 'number' | 'date' | 'boolean'; table: string; tags?: string[]; owner?: string; cube?: string; expr?: string; isTimeDimension?: boolean; /** * MetricFlow adapter group-by reference for this dimension, single-hop: * `__` (e.g. `bcm_hdr__customer_name`). Additive — the * provider-neutral registry identity remains `.`. * Multi-hop MetricFlow references (grouping a metric on a joined model's * dimension) are computed per-request by the compatibility service. */ qualifiedName?: string; /** Primary-entity name of the owning semantic model (source of qualifiedName). */ entityLink?: string; typeParams?: Record; source?: SemanticSourceMetadata; } export interface MeasureDefinition { name: string; label: string; description: string; domain?: string; agg: string; expr?: string; table: string; cube?: string; aggTimeDimension?: string; createMetric?: boolean; nonAdditiveDimension?: Record; filter?: string | Record | Array>; /** Display contract (currency/percent/decimals) from dbt meta or DQL YAML. */ displayFormat?: SemanticDisplayFormat; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface EntityDefinition { name: string; label: string; description: string; domain?: string; type: 'primary' | 'unique' | 'foreign' | 'natural' | string; expr?: string; table: string; cube?: string; role?: string; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface SemanticModelDefinition { name: string; label: string; description: string; domain?: string; model?: string; table: string; defaults?: Record; entities: string[]; measures: string[]; dimensions: string[]; timeDimensions: string[]; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface SavedQueryDefinition { name: string; label: string; description: string; domain?: string; metrics: string[]; dimensions: string[]; timeDimension?: string; granularity?: string; filters?: Array> | Record | string; orderBy?: Array<{ name: string; direction: 'asc' | 'desc'; }>; limit?: number; exports?: Array>; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export type HierarchyRollupType = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'none'; export interface HierarchyLevelDefinition { name: string; label: string; description: string; dimension: string; sql?: string; order: number; tags?: string[]; metadata?: Record; } export interface HierarchyDrillPathDefinition { name: string; levels: string[]; } export interface HierarchyDefinition { name: string; label: string; description: string; domain?: string; levels: HierarchyLevelDefinition[]; drillPaths?: HierarchyDrillPathDefinition[]; defaultDrillPath?: string; defaultRollup?: HierarchyRollupType; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface SegmentDefinition { name: string; label: string; description: string; domain?: string; cube: string; sql: string; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface PreAggregationDefinition { name: string; label: string; description: string; domain?: string; cube: string; measures?: string[]; dimensions?: string[]; timeDimension?: string; granularity?: string; refreshKey?: string; sql?: string; tags?: string[]; owner?: string; source?: SemanticSourceMetadata; } export interface JoinDefinition { name: string; left: string; right: string; type: 'inner' | 'left' | 'right' | 'full'; sql: string; /** * Foreign-entity name this join traverses, when derived from a dbt entity * (e.g. `bcm_hdr`). The compatibility service concatenates these along a join * path to build MetricFlow multi-hop group-by names (`bcm_hdr__customer_name`). */ entity?: string; } export interface TimeDimensionDefinition extends DimensionDefinition { granularities: ('day' | 'week' | 'month' | 'quarter' | 'year')[]; /** * The dbt-declared base grain (`type_params.time_granularity`). A column * stored at month grain cannot be truncated finer than month, so * `granularities` advertises only grains ≥ this. Undefined ⇒ base unknown * and all five grains are offered (backward-compatible default). */ baseGranularity?: 'day' | 'week' | 'month' | 'quarter' | 'year'; primaryTime?: boolean; } export interface CubeDefinition { name: string; label: string; description: string; sql: string; table: string; domain: string; measures: MetricDefinition[]; dimensions: DimensionDefinition[]; timeDimensions: TimeDimensionDefinition[]; joins: JoinDefinition[]; segments: SegmentDefinition[]; preAggregations: PreAggregationDefinition[]; defaultTimeDimension?: string; owner?: string; tags?: string[]; source?: SemanticSourceMetadata; } export interface ComposeQueryOptions { metrics: string[]; dimensions: string[]; timeDimension?: { name: string; granularity: string; }; filters?: Array<{ dimension: string; operator: string; values: string[]; }>; orderBy?: Array<{ name: string; direction: 'asc' | 'desc'; }>; limit?: number; /** SQL dialect for the target database. Defaults to DuckDB if not specified. */ dialect?: SQLDialect; /** Shorthand: driver name (e.g. 'snowflake', 'bigquery') to auto-resolve dialect. */ driver?: string; /** Maps semantic table names to actual database table names (e.g. 'stg_orders' → 'main.stg_orders'). */ tableMapping?: Record; } export interface ComposeQueryResult { sql: string; joins: string[]; tables: string[]; /** Compilation strategy used to protect multi-fact metric grain. */ strategy?: 'direct_join' | 'aggregate_islands'; /** * Present when a direct_join composition includes JOIN clauses. Executors * SHOULD run this one-row probe (base_rows, joined_rows) before trusting the * composed SQL: joined_rows > base_rows means the join multiplies fact rows * (non-unique key on the joined side) and every aggregated value is inflated. */ fanoutProbeSql?: string; /** Dimension/time aliases that form the aggregate-island join grain. */ grainKeys?: string[]; } export interface BlockCompanionDefinition { name: string; block: string; domain?: string; description: string; owner?: string; tags?: string[]; glossary?: string[]; source?: SemanticSourceMetadata; semanticMetrics?: string[]; semanticDimensions?: string[]; semanticMappings?: Record; lineage?: string[]; notes?: string[]; reviewStatus?: 'draft' | 'review' | 'approved'; } export interface SemanticLayerConfig { metrics: MetricDefinition[]; dimensions: DimensionDefinition[]; hierarchies?: HierarchyDefinition[]; segments?: SegmentDefinition[]; preAggregations?: PreAggregationDefinition[]; measures?: MeasureDefinition[]; entities?: EntityDefinition[]; semanticModels?: SemanticModelDefinition[]; savedQueries?: SavedQueryDefinition[]; } export interface SemanticSearchOptions { domains?: string[]; tags?: string[]; types?: Array<'metric' | 'dimension' | 'hierarchy' | 'measure' | 'entity' | 'semantic_model' | 'saved_query'>; } export interface SemanticSearchResults { metrics: MetricDefinition[]; dimensions: DimensionDefinition[]; hierarchies: HierarchyDefinition[]; measures: MeasureDefinition[]; entities: EntityDefinition[]; semanticModels: SemanticModelDefinition[]; savedQueries: SavedQueryDefinition[]; } /** * Parse a YAML-like metric definition object into a MetricDefinition. * In production, this would use a YAML parser. For now, accepts plain objects. */ export declare function parseMetricDefinition(raw: Record): MetricDefinition; export declare function parseDimensionDefinition(raw: Record): DimensionDefinition; export declare function parseHierarchyDefinition(raw: Record): HierarchyDefinition; export declare function parseSegmentDefinition(raw: Record): SegmentDefinition; export declare function parsePreAggregationDefinition(raw: Record): PreAggregationDefinition; export declare function parseBlockCompanionDefinition(raw: Record): BlockCompanionDefinition; /** * SemanticLayer holds all metric and dimension definitions and provides * lookup and search capabilities for the AI agent pipeline. */ export declare class SemanticLayer { private metrics; private dimensions; /** * A dbt project may legitimately declare the same dimension name on many * semantic models (for example `report_date`). The flat map above remains a * backwards-compatible lookup, while this index preserves every model-owned * variant so composition can resolve the dimension relative to the selected * metric instead of whichever model happened to load last. */ private dimensionVariants; private hierarchies; private segments; private preAggregations; private cubes; /** * Memo for `explainCompatibleDimensions`, keyed by the sorted metric set. * * The computation BFSs the join graph once per dimension variant, and the * agent asks for it many times per question — once per candidate metric while * building the member-selection catalog, again for the final selection, and * twice more on the modeling-gap refusal path. It was uncached, so a wide * catalog paid that cost repeatedly for the same answer. Invalidated by every * mutator, since the layer can still be added to after construction. */ private compatibilityCache; private measures; /** * Backwards-compatible bare-name entity lookup. dbt/MetricFlow legitimately * repeats an entity name as a foreign key on one model and as the primary * entity on another, so this map cannot be the authoritative registry. */ private entities; /** Every model-owned entity variant, keyed by its authored entity name. */ private entityVariants; private semanticModels; private savedQueries; private joinGraph; constructor(config?: SemanticLayerConfig); addMetric(metric: MetricDefinition): void; addDimension(dimension: DimensionDefinition): void; addCube(cube: CubeDefinition): void; addMeasure(measure: MeasureDefinition): void; private registerDimension; addEntity(entity: EntityDefinition): void; addSemanticModel(model: SemanticModelDefinition): void; addSavedQuery(savedQuery: SavedQueryDefinition): void; getCube(name: string): CubeDefinition | undefined; listCubes(): CubeDefinition[]; addSegment(segment: SegmentDefinition): void; addPreAggregation(preAggregation: PreAggregationDefinition): void; getSegment(name: string): SegmentDefinition | undefined; getPreAggregation(name: string): PreAggregationDefinition | undefined; listSegments(domain?: string): SegmentDefinition[]; listPreAggregations(domain?: string): PreAggregationDefinition[]; /** BFS shortest join path between two cube names. Returns empty array if same cube. */ findJoinPath(fromCube: string, toCube: string): JoinDefinition[]; /** Compose a multi-metric, cross-table SQL query using join graph traversal. */ composeQuery(options: ComposeQueryOptions): ComposeQueryResult | null; /** * Cheap capability probe for large semantic catalogs. This intentionally * avoids generating SQL: catalog/readiness surfaces may call it for thousands * of metrics, while full composition is reserved for a selected metric. */ canComposeMetric(name: string): boolean; /** * Resolve the DISPLAY format for a metric/measure name: explicit * declaration first (metric, then its backing measure), then a conservative * inference from metric type (ratio → percent). Returns undefined when * nothing is declared — callers keep their name-based fallback for ad-hoc * columns, but governed values format from the contract, not the guess. */ displayFormatFor(name: string): SemanticDisplayFormat | undefined; /** * dbt simple metrics may store physical ownership on their input measure * instead of duplicating it on the metric node. Materialize that contract for * native composition; derived/ratio/cumulative metrics remain MetricFlow-only. */ private resolveComposableMetric; /** * Resolve a possibly repeated dimension name from the owning metric model. * Explicit model-scoped references (`model.dimension` or * `model__dimension`) win. For the common unqualified form, the metric's own * table/cube wins, followed by the shortest declared semantic join path. */ private resolveDimensionForMetrics; private cubeNameForTable; private composeAggregateIslands; addHierarchy(hierarchy: HierarchyDefinition): void; getMetric(name: string): MetricDefinition | undefined; getDimension(name: string): DimensionDefinition | undefined; getHierarchy(name: string): HierarchyDefinition | undefined; getMeasure(name: string): MeasureDefinition | undefined; getEntity(name: string, cube?: string): EntityDefinition | undefined; getSemanticModel(name: string): SemanticModelDefinition | undefined; getSavedQuery(name: string): SavedQueryDefinition | undefined; /** * List metrics. By default returns everything in the metrics map — including * measures projected in by {@link addCube} — for backward compatibility. * Pass `{ includeMeasures: false }` to exclude measure-derived entries so the * UI metric picker and the agent's metric matcher see only real metrics. */ listMetrics(domain?: string, opts?: { includeMeasures?: boolean; }): MetricDefinition[]; /** * Resolve a group-by reference that may be a bare dimension name * (`customer_name`) OR a MetricFlow-qualified name (`bcm_hdr__customer_name`). * The single tolerance point that lets native composition accept both * spellings without changing what it emits. Returns the best-matching * dimension variant, or undefined when nothing matches. */ resolveGroupBy(name: string): DimensionDefinition | undefined; listDimensions(domain?: string, options?: { includeVariants?: boolean; }): DimensionDefinition[]; listHierarchies(domain?: string): HierarchyDefinition[]; listMeasures(domain?: string): MeasureDefinition[]; listEntities(domain?: string): EntityDefinition[]; listTimeDimensions(domain?: string, options?: { includeVariants?: boolean; }): TimeDimensionDefinition[]; /** The time-dimension record for a name, including its real `granularities`. */ getTimeDimension(name: string): TimeDimensionDefinition | undefined; /** * Provider-neutral identity persisted in DQL artifacts and APIs. A business * label and a MetricFlow adapter spelling are aliases, never replacements for * this model-owned registry reference. */ dimensionReference(dimension: DimensionDefinition): string; /** * Resolve a dimension relative to the exact selected metric set. This is the * public boundary used by semantic adapters so repeated leaf names never bind * according to catalog load order. */ resolveDimension(reference: string, metricNames?: string[]): DimensionDefinition | undefined; listSemanticModels(domain?: string): SemanticModelDefinition[]; listSavedQueries(domain?: string): SavedQueryDefinition[]; listDomains(): string[]; listTags(): string[]; resolveDrillPath(hierarchyName: string, drillPathName?: string): HierarchyLevelDefinition[]; nextDrillLevel(hierarchyName: string, currentLevelName?: string, drillPathName?: string): HierarchyLevelDefinition | null; /** * Search metrics and dimensions by text query. */ search(query: string): { metrics: MetricDefinition[]; dimensions: DimensionDefinition[]; }; searchAdvanced(query: string, options?: SemanticSearchOptions): SemanticSearchResults; listCompatibleDimensions(metricNames: string[]): DimensionDefinition[]; /** * Authoritative per-metric dimension compatibility. Returns the dimensions a * metric (or set of metrics) can be grouped by — each carrying its MetricFlow * qualified name and entity path — PLUS the dimensions that are NOT compatible * and why. This is the single native source the compatibility endpoint, the * query-qualification boundary, and the agent all consume. * * Reasons: * - `no_join_path` the dimension's model is unreachable from * every selected metric's model. * - `not_shared_across_metrics` reachable from some but not all metrics. * - `metric_unresolved` a requested metric name is unknown. */ explainCompatibleDimensions(metricNames: string[]): { compatible: Array; incompatible: Array<{ name: string; qualifiedName?: string; reason: 'no_join_path' | 'not_shared_across_metrics' | 'metric_unresolved'; }>; }; private computeCompatibleDimensions; /** * Validate that all metric references in a SQL query resolve to known metrics/dimensions. */ validateReferences(references: string[]): { valid: string[]; unknown: string[]; }; /** * Generate SQL for a metric with optional dimension grouping. * Delegates to composeQuery() to leverage the join graph when cubes are available. */ generateMetricSQL(metricName: string, groupBy?: string[]): string | null; } export declare function parseCubeDefinition(raw: Record): CubeDefinition; /** Stable provider-neutral registry reference for a model-owned dimension. */ export declare function semanticDimensionReference(dimension: Pick): string; /** * Normalize a display-format declaration from dbt `meta` or DQL YAML. Accepts * `meta.format: "currency"`, `meta: {format: {kind: percent, decimals: 1}}`, * `meta.currency: "EUR"`, and common shorthands ("usd", "$", "%"). */ export declare function parseSemanticDisplayFormat(meta: unknown): SemanticDisplayFormat | undefined; //# sourceMappingURL=semantic-layer.d.ts.map