Class: Insika::HarvestEngine

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/harvest_engine.rb

Overview

the trigger — finds due sessions and mines them. It spawns a worker fiber (supervisor child, Tick#start shape) whose loop claims a window and runs ONE due session's harvest ON the worker fiber — off the tick's critical path and off every customer turn's path (D2: the mining model call never blocks a customer turn).

The scan uses the engine default idle_hours (24) as the LOWER BOUND; the per-agent value is re-checked inside RunHarvest. The loop-stop (the H-harvest kill) is the profile's enabled: false — the engine only respects data (D4).

Constant Summary collapse

SCOPE =
"harvest_engine"
KEY =
"claim"
DEFAULT_WINDOW =

seconds — the O(n) scan never rides the 60 s loop

300
DEFAULT_IDLE_HOURS =
24
DEFAULT_MIN_MESSAGES =
3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store:, harvest_store:, session_store:, runner:, profiles: nil, logger: nil, window: DEFAULT_WINDOW, idle_hours: DEFAULT_IDLE_HOURS, sleeper: nil) ⇒ HarvestEngine

Returns a new instance of HarvestEngine.



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/insika/harvest_engine.rb', line 23

def initialize(store:, harvest_store:, session_store:, runner:,
               profiles: nil, logger: nil, window: DEFAULT_WINDOW,
               idle_hours: DEFAULT_IDLE_HOURS, sleeper: nil)
  @store = store
  @harvest_store = harvest_store
  @session_store = session_store
  @runner = runner
  @profiles = profiles # profile source; nil/empty = nothing mines (parity)
  @logger = logger
  @window = window.to_i
  @idle_hours = idle_hours.to_i
  @sleeper = sleeper || method(:default_sleep)
end

Instance Attribute Details

#runnerObject

The deployment root swaps in its own runner (the bus instance carrying the negative list + the settings-backed miner factory); the base graph's inert one keeps the loop honest until then.



40
41
42
# File 'lib/insika/harvest_engine.rb', line 40

def runner
  @runner
end

Instance Method Details

#run_onceObject

One pass. -> { claimed: false } | { claimed: true, mined: N, skipped: N, errors: N }



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/insika/harvest_engine.rb', line 63

def run_once
  return { claimed: false } unless harvestable?
  return { claimed: false } unless claim_window

  session = due_sessions.first # one model call per pass; the window paces
  return { claimed: true, mined: 0, skipped: 0, errors: 0 } if session.nil?

  begin
    outcome = @runner.call(Insika::Command.build(:run_harvest,
                                                 { agent: session[:agent],
                                                   session_ids: [session[:id]] }))
    if outcome[:mined]
      { claimed: true, mined: 1, skipped: 0, errors: 0 }
    else
      { claimed: true, mined: 0, skipped: 1, errors: 0 }
    end
  rescue StandardError
    # a broken session must not hold the pass; the marker discipline
    # keeps it re-runnable.
    { claimed: true, mined: 0, skipped: 0, errors: 1 }
  end
end

#start(parent:) ⇒ Object

The supervisor child (tick.rb:69-83's shape). Returns false when idle_hours <= 0 (the engine default OFF switch — parity) OR when no profile declares an enabled harvest (a scan with nothing to mine would re-read every session record every window forever).



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/insika/harvest_engine.rb', line 46

def start(parent:)
  return false unless harvestable?

  @task = parent.async do |t|
    t.annotate("insika-harvest")
    loop do
      @sleeper.call(@window)
      run_once
    rescue StandardError => e
      log(:warn, "harvest pass failed: #{e.class}: #{e.message}")
    end
  end
  true
end