class FlowEngine::Node

@attr_reader max_clarifications [Integer] max follow-up rounds for :ai_intake steps (default: 0)
@attr_reader visibility_rule [Rules::Base, nil] rule controlling whether this node is visible (DAG mode)
@attr_reader transitions [Array<Transition>] ordered list of conditional next-step rules
@attr_reader fields [Array, nil] field names for number_matrix etc.; nil otherwise
@attr_reader option_labels [Hash, nil] key => display label mapping (nil when options are plain strings)
@attr_reader options [Array, nil] option keys for select steps; nil for other types
@attr_reader question [String] prompt text for the step
@attr_reader type [Symbol] input type (e.g. :multi_select, :number_matrix, :ai_intake)
@attr_reader id [Symbol] unique step identifier
Used by {Engine} to determine the next step and by UI/export to render the step.
A single step in the flow: question metadata, input config, and conditional transitions.

def ai_intake?

Returns:
  • (Boolean) - true if this is an AI intake step
def ai_intake?
  type == :ai_intake
end

def extract_options(raw)

an Array is stored as-is with nil option_labels.
Normalizes options: a Hash is split into keys (options) and the full hash (option_labels);
def extract_options(raw)
  case raw
  when Hash
    @options = raw.keys.map(&:to_s).freeze
    @option_labels = raw.transform_keys(&:to_s).freeze
  when Array
    @options = raw.freeze
    @option_labels = nil
  else
    @options = nil
    @option_labels = nil
  end
end

def initialize(id:, # rubocop:disable Metrics/ParameterLists

Parameters:
  • max_clarifications (Integer) -- max follow-up rounds for :ai_intake (default: 0)
  • visibility_rule (Rules::Base, nil) -- optional rule for visibility (default: always visible)
  • transitions (Array) -- conditional next-step transitions (default: [])
  • fields (Array, nil) -- field list for matrix-style steps
  • options (Array, Hash, nil) -- option list or key=>label hash for select steps
  • decorations (Object, nil) -- optional UI decorations (not used by engine)
  • question (String) -- label/prompt
  • type (Symbol) -- step/input type
  • id (Symbol) -- step id
def initialize(id:, # rubocop:disable Metrics/ParameterLists
               type:,
               question:,
               decorations: nil,
               options: nil,
               fields: nil,
               transitions: [],
               visibility_rule: nil,
               max_clarifications: 0)
  @id = id
  @type = type
  @question = question
  @decorations = decorations
  extract_options(options)
  @fields = fields&.freeze
  @transitions = transitions.freeze
  @visibility_rule = visibility_rule
  @max_clarifications = max_clarifications
  freeze
end

def next_step_id(answers)

Returns:
  • (Symbol, nil) - id of the next step, or nil if no transition matches (flow end)

Parameters:
  • answers (Hash) -- current answer state (step_id => value)
def next_step_id(answers)
  match = transitions.find { |t| t.applies?(answers) }
  match&.target
end

def visible?(answers)

Returns:
  • (Boolean) - true if no visibility_rule, else result of rule evaluation

Parameters:
  • answers (Hash) -- current answer state
def visible?(answers)
  return true if visibility_rule.nil?
  visibility_rule.evaluate(answers)
end