Class: FlowEngine::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/flowengine/engine.rb,
lib/flowengine/engine/state_serializer.rb

Overview

Runtime session that drives flow navigation: holds definition, answers, and current step. Validates each answer via an optional Validation::Adapter, then advances using node transitions.

Defined Under Namespace

Modules: StateSerializer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, validator: Validation::NullAdapter.new) ⇒ Engine



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/flowengine/engine.rb', line 12

def initialize(definition, validator: Validation::NullAdapter.new)
  @definition = definition
  @answers = {}
  @history = []
  @current_step_id = definition.start_step_id
  @validator = validator
  @introduction_text = nil
  @clarification_round = 0
  @conversation_history = []
  @active_intake_step_id = nil
  @history << @current_step_id
end

Instance Attribute Details

#answers ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def answers
  @answers
end

#clarification_round ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def clarification_round
  @clarification_round
end

#conversation_history ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def conversation_history
  @conversation_history
end

#current_step_id ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def current_step_id
  @current_step_id
end

#definition ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def definition
  @definition
end

#history ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def history
  @history
end

#introduction_text ⇒ Object (readonly)

rubocop:disable Metrics/ClassLength



7
8
9
# File 'lib/flowengine/engine.rb', line 7

def introduction_text
  @introduction_text
end

Class Method Details

.from_state(definition, state_hash, validator: Validation::NullAdapter.new) ⇒ Engine

Rebuilds an engine from a previously saved state.



120
121
122
123
124
125
# File 'lib/flowengine/engine.rb', line 120

def self.from_state(definition, state_hash, validator: Validation::NullAdapter.new)
  state = StateSerializer.symbolize_state(state_hash)
  engine = allocate
  engine.send(:restore_state, definition, state, validator)
  engine
end

Instance Method Details

#answer(value) ⇒ Object

Submits an answer for the current step, validates it, stores it, and advances to the next step.

Raises:

  • (AlreadyFinishedError) —

    if the flow has already finished

  • (ValidationError) —

    if the validator rejects the value



42
43
44
45
46
47
48
49
50
# File 'lib/flowengine/engine.rb', line 42

def answer(value)
  raise Errors::AlreadyFinishedError, "Flow is already finished" if finished?

  result = @validator.validate(current_step, value)
  raise Errors::ValidationError, "Validation failed: #{result.errors.join(", ")}" unless result.valid?

  answers[@current_step_id] = value
  advance_step
end

#current_step ⇒ Node?



26
27
28
29
30
# File 'lib/flowengine/engine.rb', line 26

def current_step
  return nil if finished?

  definition.step(@current_step_id)
end

#finished? ⇒ Boolean



33
34
35
# File 'lib/flowengine/engine.rb', line 33

def finished?
  @current_step_id.nil?
end

#submit_ai_intake(text, llm_client:) ⇒ ClarificationResult

Submits free-form text for the current AI intake step. Returns a ClarificationResult.



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/flowengine/engine.rb', line 71

def submit_ai_intake(text, llm_client:)
  node = current_step
  raise Errors::EngineError, "Current step is not an AI intake step" unless node&.ai_intake?

  LLM::SensitiveDataFilter.check!(text)

  @active_intake_step_id = @current_step_id
  @clarification_round = 1
  @conversation_history = [{ role: :user, text: text }]

  perform_intake_round(text, llm_client, node)
end

#submit_clarification(text, llm_client:) ⇒ ClarificationResult

Submits a clarification response for an ongoing AI intake conversation.



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/flowengine/engine.rb', line 89

def submit_clarification(text, llm_client:)
  raise Errors::EngineError, "No active AI intake conversation to clarify" unless @active_intake_step_id

  LLM::SensitiveDataFilter.check!(text)

  node = @definition.step(@active_intake_step_id)
  @clarification_round += 1
  @conversation_history << { role: :user, text: text }

  perform_intake_round(text, llm_client, node)
end

#submit_introduction(text, llm_client:) ⇒ Object

Submits free-form introduction text, filters sensitive data, calls the LLM to extract answers, and auto-advances through pre-filled steps.



57
58
59
60
61
62
63
64
# File 'lib/flowengine/engine.rb', line 57

def submit_introduction(text, llm_client:)
  validate_introduction_length!(text)
  LLM::SensitiveDataFilter.check!(text)
  @introduction_text = text
  extracted = llm_client.parse_introduction(definition: @definition, introduction_text: text)
  @answers.merge!(extracted)
  auto_advance_prefilled
end

#to_state ⇒ Object

Serializable state for persistence or resumption.



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/flowengine/engine.rb', line 102

def to_state
  {
    current_step_id: @current_step_id,
    answers: @answers,
    history: @history,
    introduction_text: @introduction_text,
    clarification_round: @clarification_round,
    conversation_history: @conversation_history,
    active_intake_step_id: @active_intake_step_id
  }
end