Class: Ace::Assign::Models::QueueState

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/assign/models/queue_state.rb

Overview

Queue state model representing a snapshot of the work queue.

Pure data carrier with no business logic (ATOM pattern). Provides convenient accessors for queue analysis.

Examples:

state = QueueState.new(steps: steps, assignment: assignment)
state.current  # => Step with status :in_progress
state.pending  # => Array of pending steps

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(steps:, assignment:) ⇒ QueueState

Returns a new instance of QueueState.

Parameters:

  • steps (Array<Step>)

    All steps in queue order

  • assignment (Assignment)

    Assignment metadata



20
21
22
23
24
# File 'lib/ace/assign/models/queue_state.rb', line 20

def initialize(steps:, assignment:)
  @steps = steps.freeze
  @assignment = assignment
  @children_index = build_children_index(steps)
end

Instance Attribute Details

#assignmentObject (readonly)

Returns the value of attribute assignment.



16
17
18
# File 'lib/ace/assign/models/queue_state.rb', line 16

def assignment
  @assignment
end

#stepsObject (readonly)

Returns the value of attribute steps.



16
17
18
# File 'lib/ace/assign/models/queue_state.rb', line 16

def steps
  @steps
end

Instance Method Details

#all_numbersArray<String>

Get all step numbers as an array

Returns:

  • (Array<String>)

    All step numbers



280
281
282
# File 'lib/ace/assign/models/queue_state.rb', line 280

def all_numbers
  steps.map(&:number)
end

#ancestor_chain(number) ⇒ Array<String>

Build ancestor chain from closest parent to root.

Parameters:

  • number (String)

    Step number

Returns:

  • (Array<String>)

    Ancestor numbers, nearest first



228
229
230
231
232
233
234
235
236
# File 'lib/ace/assign/models/queue_state.rb', line 228

def ancestor_chain(number)
  chain = []
  parent = Atoms::StepNumbering.parent_of(number)
  while parent
    chain << parent
    parent = Atoms::StepNumbering.parent_of(parent)
  end
  chain
end

#assignment_stateSymbol

Computed assignment state based on step statuses

States (checked in priority order):

  • :empty - No steps in queue

  • :completed - All steps complete (done or failed)

  • :failed - Has failed step(s) but NOT all complete (stuck)

  • :running - Has in_progress step with recent activity (< 1 hour)

  • :stalled - Has in_progress step but stale (> 1 hour)

  • :paused - Has pending but no in_progress (interrupted)

Returns:

  • (Symbol)

    Assignment state



115
116
117
118
119
120
121
122
123
# File 'lib/ace/assign/models/queue_state.rb', line 115

def assignment_state
  return :empty if empty?
  return :completed if complete?
  return :failed if failed.any?
  return :running if current && recently_active?
  return :stalled if current

  :paused
end

#children_of(parent_number) ⇒ Array<Step>

Get all direct children of a step (O(1) via index)

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Array<Step>)

    Direct child steps



149
150
151
# File 'lib/ace/assign/models/queue_state.rb', line 149

def children_of(parent_number)
  @children_index[parent_number] || []
end

#complete?Boolean

Check if all steps are complete (no pending or in_progress)

Returns:

  • (Boolean)


70
71
72
# File 'lib/ace/assign/models/queue_state.rb', line 70

def complete?
  steps.all?(&:complete?)
end

#currentStep?

Get current in-progress step

Returns:

  • (Step, nil)

    Current step or nil



28
29
30
# File 'lib/ace/assign/models/queue_state.rb', line 28

def current
  in_progress_steps.first
end

#current_in_subtree(root_number) ⇒ Step?

Get the current in-progress step within a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Step, nil)

    In-progress step inside subtree, if any



200
201
202
# File 'lib/ace/assign/models/queue_state.rb', line 200

def current_in_subtree(root_number)
  in_progress_in_subtree(root_number).first
end

#descendants_of(parent_number) ⇒ Array<Step>

Get all descendants (children, grandchildren, etc.) of a step

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Array<Step>)

    All descendant steps



156
157
158
# File 'lib/ace/assign/models/queue_state.rb', line 156

def descendants_of(parent_number)
  steps.select { |s| Atoms::StepNumbering.child_of?(s.number, parent_number) }
end

#doneArray<Step>

Get all done steps

Returns:

  • (Array<Step>)

    Completed steps



46
47
48
# File 'lib/ace/assign/models/queue_state.rb', line 46

def done
  steps.select { |s| s.status == :done }
end

#empty?Boolean

Check if queue is empty

Returns:

  • (Boolean)


64
65
66
# File 'lib/ace/assign/models/queue_state.rb', line 64

def empty?
  steps.empty?
end

#failedArray<Step>

Get all failed steps

Returns:

  • (Array<Step>)

    Failed steps



52
53
54
# File 'lib/ace/assign/models/queue_state.rb', line 52

def failed
  steps.select { |s| s.status == :failed }
end

#find_by_number(number) ⇒ Step?

Get step by number

Parameters:

  • number (String)

    Step number (e.g., “010”, “040”)

Returns:

  • (Step, nil)

    Found step



77
78
79
80
81
82
83
84
# File 'lib/ace/assign/models/queue_state.rb', line 77

def find_by_number(number)
  # Normalize to string without leading zeros for comparison
  normalized = number.to_s.sub(/^0+/, "")
  steps.find do |s|
    next unless s.number
    s.number.sub(/^0+/, "") == normalized || s.number == number.to_s
  end
end

#has_incomplete_children?(parent_number) ⇒ Boolean

Check if a step has any incomplete children

Parameters:

  • parent_number (String)

    Parent step number

Returns:

  • (Boolean)

    True if any child is not done



259
260
261
# File 'lib/ace/assign/models/queue_state.rb', line 259

def has_incomplete_children?(parent_number)
  children_of(parent_number).any? { |s| s.status != :done }
end

#hierarchicalArray<Hash>

Build hierarchical structure for display

Returns:

  • (Array<Hash>)

    Nested structure with :step and :children keys



292
293
294
# File 'lib/ace/assign/models/queue_state.rb', line 292

def hierarchical
  build_hierarchy(nil)
end

#in_progress_in_subtree(root_number) ⇒ Array<Step>

Get all in-progress steps within a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Array<Step>)

    In-progress steps inside subtree



208
209
210
211
# File 'lib/ace/assign/models/queue_state.rb', line 208

def in_progress_in_subtree(root_number)
  subtree_steps(root_number)
    .select { |s| s.status == :in_progress }
end

#in_progress_stepsArray<Step>

Get all in-progress steps

Returns:

  • (Array<Step>)

    In-progress steps



34
35
36
# File 'lib/ace/assign/models/queue_state.rb', line 34

def in_progress_steps
  steps.select { |s| s.status == :in_progress }
end

#in_subtree?(root_number, step_number) ⇒ Boolean

Check whether a step number belongs to a subtree rooted at root_number.

Parameters:

  • root_number (String)

    Subtree root step number

  • step_number (String)

    Candidate step number

Returns:

  • (Boolean)

    True when candidate is root or descendant of root



165
166
167
# File 'lib/ace/assign/models/queue_state.rb', line 165

def in_subtree?(root_number, step_number)
  step_number == root_number || Atoms::StepNumbering.child_of?(step_number, root_number)
end

#lastStep?

Get last step in queue

Returns:

  • (Step, nil)

    Last step



88
89
90
# File 'lib/ace/assign/models/queue_state.rb', line 88

def last
  steps.last
end

#last_doneStep?

Get last completed step

Returns:

  • (Step, nil)

    Last done step



94
95
96
# File 'lib/ace/assign/models/queue_state.rb', line 94

def last_done
  done.last
end

#nearest_fork_ancestor(number) ⇒ Step?

Find nearest ancestor (or self) that has context: fork.

Parameters:

  • number (String)

    Step number

Returns:

  • (Step, nil)

    Nearest fork-scoped step



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/ace/assign/models/queue_state.rb', line 242

def nearest_fork_ancestor(number)
  step = find_by_number(number)
  return nil unless step

  return step if step.fork?

  ancestor_chain(number).each do |ancestor_number|
    ancestor = find_by_number(ancestor_number)
    return ancestor if ancestor&.fork?
  end

  nil
end

#next_pendingStep?

Get next pending step

Returns:

  • (Step, nil)

    Next step to work on



58
59
60
# File 'lib/ace/assign/models/queue_state.rb', line 58

def next_pending
  pending.first
end

#next_workableStep?

Get next workable step considering hierarchy. A step is workable if it’s pending and has no incomplete children. Prefers children of current/recent work.

Returns:

  • (Step, nil)

    Next step to work on



267
268
269
270
271
272
273
274
275
276
# File 'lib/ace/assign/models/queue_state.rb', line 267

def next_workable
  # First, find pending steps
  pending_steps = pending

  # Filter to steps that don't have incomplete children
  workable = pending_steps.reject { |s| has_incomplete_children?(s.number) }

  # Return first workable step (already sorted by number)
  workable.first
end

#next_workable_in_subtree(root_number) ⇒ Step?

Get next workable step constrained to a subtree.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Step, nil)

    Next pending workable step inside subtree



217
218
219
220
221
222
# File 'lib/ace/assign/models/queue_state.rb', line 217

def next_workable_in_subtree(root_number)
  subtree_steps(root_number)
    .select { |s| s.status == :pending }
    .reject { |s| has_incomplete_children?(s.number) }
    .first
end

#pendingArray<Step>

Get all pending steps

Returns:

  • (Array<Step>)

    Pending steps



40
41
42
# File 'lib/ace/assign/models/queue_state.rb', line 40

def pending
  steps.select { |s| s.status == :pending }
end

#recently_active?(threshold: 3600) ⇒ Boolean

Check if the current in_progress step has recent activity

Parameters:

  • threshold (Integer) (defaults to: 3600)

    Seconds since started_at to consider active (default: 1 hour)

Returns:

  • (Boolean)


128
129
130
131
132
# File 'lib/ace/assign/models/queue_state.rb', line 128

def recently_active?(threshold: 3600)
  return false unless current&.started_at

  (Time.now - current.started_at) < threshold
end

#sizeInteger

Total step count

Returns:

  • (Integer)

    Number of steps



100
101
102
# File 'lib/ace/assign/models/queue_state.rb', line 100

def size
  steps.size
end

#subtree_complete?(root_number) ⇒ Boolean

Check whether all steps in a subtree are complete.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Boolean)

    True when every subtree step is complete



181
182
183
184
185
186
# File 'lib/ace/assign/models/queue_state.rb', line 181

def subtree_complete?(root_number)
  scoped = subtree_steps(root_number)
  return false if scoped.empty?

  scoped.all?(&:complete?)
end

#subtree_failed?(root_number) ⇒ Boolean

Check whether a subtree has at least one failed step.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Boolean)

    True when any subtree step failed



192
193
194
# File 'lib/ace/assign/models/queue_state.rb', line 192

def subtree_failed?(root_number)
  subtree_steps(root_number).any? { |s| s.status == :failed }
end

#subtree_steps(root_number) ⇒ Array<Step>

Get all steps in a subtree (root + descendants), preserving queue order.

Parameters:

  • root_number (String)

    Subtree root step number

Returns:

  • (Array<Step>)

    Subtree steps in queue order



173
174
175
# File 'lib/ace/assign/models/queue_state.rb', line 173

def subtree_steps(root_number)
  steps.select { |s| in_subtree?(root_number, s.number) }
end

#summaryHash

Summary for display

Returns:

  • (Hash)

    Summary statistics



136
137
138
139
140
141
142
143
144
# File 'lib/ace/assign/models/queue_state.rb', line 136

def summary
  {
    total: size,
    done: done.size,
    in_progress: in_progress_steps.size,
    pending: pending.size,
    failed: failed.size
  }
end

#top_levelArray<Step>

Get top-level (root) steps only

Returns:

  • (Array<Step>)

    Steps with no parent



286
287
288
# File 'lib/ace/assign/models/queue_state.rb', line 286

def top_level
  steps.select { |s| Atoms::StepNumbering.top_level?(s.number) }
end