Class: Legion::Gaia::ProactiveDispatcher

Inherits:
Object
  • Object
show all
Includes:
Logging::Helper
Defined in:
lib/legion/gaia/proactive_dispatcher.rb

Constant Summary collapse

MAX_PER_DAY =
3
MIN_INTERVAL =

2 hours

7200
IGNORE_COOLDOWN =

24 hours

86_400
MAX_PENDING =
5
DAY_WINDOW =

24 hours

86_400

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_per_day: MAX_PER_DAY, min_interval: MIN_INTERVAL, ignore_cooldown: IGNORE_COOLDOWN) ⇒ ProactiveDispatcher

Returns a new instance of ProactiveDispatcher.



18
19
20
21
22
23
24
25
26
27
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 18

def initialize(max_per_day: MAX_PER_DAY, min_interval: MIN_INTERVAL,
               ignore_cooldown: IGNORE_COOLDOWN)
  @max_per_day     = max_per_day
  @min_interval    = min_interval
  @ignore_cooldown = ignore_cooldown
  @dispatch_log    = []
  @pending_buffer  = []
  @last_ignored_at = nil
  @mutex           = Mutex.new
end

Instance Attribute Details

#ignore_cooldownObject (readonly)

Returns the value of attribute ignore_cooldown.



16
17
18
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 16

def ignore_cooldown
  @ignore_cooldown
end

#max_per_dayObject (readonly)

Returns the value of attribute max_per_day.



16
17
18
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 16

def max_per_day
  @max_per_day
end

#min_intervalObject (readonly)

Returns the value of attribute min_interval.



16
17
18
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 16

def min_interval
  @min_interval
end

#pending_bufferObject (readonly)

Returns the value of attribute pending_buffer.



16
17
18
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 16

def pending_buffer
  @pending_buffer
end

Instance Method Details

#can_dispatch?Boolean

Returns:

  • (Boolean)


29
30
31
32
33
34
35
36
37
38
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 29

def can_dispatch?
  @mutex.synchronize do
    prune_old_dispatches!
    return false if @dispatch_log.size >= @max_per_day
    return false if @dispatch_log.any? && (Time.now.utc - @dispatch_log.last[:at]) < @min_interval
    return false if @last_ignored_at && (Time.now.utc - @last_ignored_at) < @ignore_cooldown
  end

  true
end

#dispatch_with_gate(intent) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 81

def dispatch_with_gate(intent)
  unless can_dispatch?
    log.info("ProactiveDispatcher skipped intent reason=#{intent.dig(:trigger, :reason)} status=rate_limited")
    return { dispatched: false, reason: :rate_limited }
  end

  content = generate_content(intent)
  unless content
    log.info("ProactiveDispatcher skipped intent reason=#{intent.dig(:trigger, :reason)} status=no_content")
    return { dispatched: false, reason: :no_content }
  end

  partner_id = resolve_partner_id
  channel_id = resolve_partner_channel
  target_failure = validate_dispatch_target(intent, partner_id: partner_id, channel_id: channel_id)
  return target_failure if target_failure

  result = proactive_module.send_notification(
    content: content,
    priority: intent.dig(:trigger, :priority) || :low,
    channel_id: channel_id,
    user_id: partner_id
  )

  delivery_failure = failed_dispatch_response(
    intent, result, partner_id: partner_id, channel_id: channel_id, content: content
  )
  return delivery_failure if delivery_failure

  record_dispatch!
  log.info(
    'ProactiveDispatcher dispatched intent ' \
    "reason=#{intent.dig(:trigger, :reason)} user_id=#{partner_id} channel_id=#{channel_id}"
  )
  { dispatched: true, content: content, result: result, user_id: partner_id, channel_id: channel_id }
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'gaia.proactive_dispatcher.dispatch_with_gate')
  { dispatched: false, reason: :error, error: e.message }
end

#dispatches_todayObject



54
55
56
57
58
59
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 54

def dispatches_today
  @mutex.synchronize do
    prune_old_dispatches!
    @dispatch_log.size
  end
end

#drain_pendingObject



71
72
73
74
75
76
77
78
79
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 71

def drain_pending
  drained = @mutex.synchronize do
    copy = @pending_buffer.dup
    @pending_buffer.clear
    copy
  end
  log.info("ProactiveDispatcher drained pending count=#{drained.size}") if drained.any?
  drained
end

#queue_intent(intent) ⇒ Object



61
62
63
64
65
66
67
68
69
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 61

def queue_intent(intent)
  pending = @mutex.synchronize do
    @pending_buffer << intent
    @pending_buffer.shift while @pending_buffer.size > MAX_PENDING
    @pending_buffer.size
  end
  log.info("ProactiveDispatcher queued intent reason=#{intent.dig(:trigger,
                                                                  :reason)} pending=#{pending}")
end

#record_dispatch!Object



40
41
42
43
44
45
46
47
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 40

def record_dispatch!
  count = @mutex.synchronize do
    prune_old_dispatches!
    @dispatch_log << { at: Time.now.utc }
    @dispatch_log.size
  end
  log.info("ProactiveDispatcher recorded dispatch count=#{count}")
end

#record_ignored!Object



49
50
51
52
# File 'lib/legion/gaia/proactive_dispatcher.rb', line 49

def record_ignored!
  @mutex.synchronize { @last_ignored_at = Time.now.utc }
  log.info('ProactiveDispatcher recorded ignored interaction')
end