Class: Strata::CLI::AI::Services::TableGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/strata/cli/ai/services/table_generator.rb

Overview

Generates semantic model fields from database table metadata using AI. Returns structured JSON for use in the field editor.

Constant Summary collapse

SYSTEM_PROMPT =
<<~PROMPT
  You are a semantic modeling expert for the Strata Business Intelligence platform.
  Analyze database columns and generate semantic field definitions.

  You must understand that Names are extremely important in the Strata application.  When the same named dimension 
  is mapped to multiple tables, Strata treats them as the same entity but with multiple potential tables. Strata
  will at query generation time choose the best table.  Therefore it is extremely important to consider whether a 
  potential dimension or measure is the same as an existing one. You can try and infer that based on the table name, 
  column name, and the existing fields.  In general, you should use existing names for the same schema type if it makes sense.
  The edge case is when you have a Dimension table like Customer with column first_name and another Dimension
  table called Billed Customer with first_name, then they are likely not the same. A dimension created from
  Billed Customer should be given an appropriately prefixed name: Billed Customer First Name.

  Next, if the column is something highly ambigous and the current table is likely a dimension table, we should prefix 
  then dimension name with approprite prefix based on the name of the dimension table.
  Example:
    table name: item_dim
    column name: color
    dimension name: Item Color
  Example:
    table name: customer
    column name: first name
    dimension name: Customer First Name

  Given the above for each column, determine:
  - name: Human-friendly field name (e.g., "customer_id" → "Customer ID")
  - description: Brief description of what this field represents
  - schema_type: "dimension" for categorical/text, "measure" for numeric aggregations
  - data_type: string, integer, bigint, decimal, date, date_time, boolean. These are the data types supported
    by strata. They do not have precision nor scale. Simply the types listed here. Do not apply any other data types.
  - expression: SQL expression (for measures include aggregation like "sum(amount)")
  - synonyms: Array of 0-3 alternative names users might use to refer to this field.
    These help AI search and natural language queries find the right field.
    Example: "Revenue" → ["sales"]
    Example: "Created At" → ["created date", "date"]
    Example: "State" in a geography table → ["province"]
    Example: "Customer Return Date" → [] -- name has high specificity already so no synonyms needed 

  In cases where a dimension already exists (i.e. a dimension with same name exists), omit everything 
  except the following fields: name, schema_type, data_type, expression.

  Output ONLY valid JSON array, no explanations.
PROMPT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client: Client.new) ⇒ TableGenerator

Returns a new instance of TableGenerator.



59
60
61
# File 'lib/strata/cli/ai/services/table_generator.rb', line 59

def initialize(client: Client.new)
  @client = client
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



57
58
59
# File 'lib/strata/cli/ai/services/table_generator.rb', line 57

def client
  @client
end

Instance Method Details

#ai_available?Boolean

Check if AI is available

Returns:

  • (Boolean)


123
124
125
# File 'lib/strata/cli/ai/services/table_generator.rb', line 123

def ai_available?
  @client.enabled?
end

#call(table_name:, columns:, datasource:, user_context: nil) ⇒ Hash?

Generate field definitions from table metadata

Parameters:

  • table_name (String)

    Full table name (schema.table)

  • columns (Array<Hash>)

    Column metadata with :name, :type

  • datasource (String)

    Datasource key

  • user_context (Hash, nil) (defaults to: nil)

    Optional user-provided context with :description

Returns:

  • (Hash, nil)

    Generated model data or nil if AI disabled



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/strata/cli/ai/services/table_generator.rb', line 69

def call(table_name:, columns:, datasource:, user_context: nil)
  unless ai_available?
    warn "AI not available - using rule-based fallback"
    return fallback_fields(columns)
  end

  existing_models = load_existing_models_context
  prompt = build_prompt(
    table_name: table_name,
    columns: columns,
    existing_models_context: existing_models,
    user_context: user_context
  )

  begin
    response = @client.complete(prompt, system_prompt: SYSTEM_PROMPT)
    parse_response(response, columns)
  rescue => e
    # Fallback to basic field generation on AI error
    warn "AI failed to generate fields: #{e.message} - performing basic field generation"
    fallback_fields(columns)
  end
end

#call_with_prompt(table_name:, columns:, datasource:, user_prompt:, current_fields:) ⇒ Array<Hash>

Generate fields with user prompt for modification/regeneration

Parameters:

  • table_name (String)

    Full table name

  • columns (Array<Hash>)

    Column metadata

  • datasource (String)

    Datasource key

  • user_prompt (String)

    User’s natural language prompt

  • current_fields (Array<Hash>)

    Current field definitions

Returns:

  • (Array<Hash>)

    Regenerated fields



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/strata/cli/ai/services/table_generator.rb', line 100

def call_with_prompt(table_name:, columns:, datasource:, user_prompt:, current_fields:)
  unless ai_available?
    warn "AI not available - could not use prompt mode"
    return nil
  end

  prompt = build_prompt_with_user_input(
    table_name: table_name,
    columns: columns,
    user_prompt: user_prompt,
    current_fields: current_fields
  )

  begin
    response = @client.complete(prompt, system_prompt: SYSTEM_PROMPT)
    parse_response(response, columns)
  rescue => e
    warn "AI failed to generate fields: #{e.message}"
    nil
  end
end