lex-temporal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/legion/extensions/temporal/client.rb +17 -0
- data/lib/legion/extensions/temporal/helpers/constants.rb +64 -0
- data/lib/legion/extensions/temporal/helpers/temporal_pattern.rb +121 -0
- data/lib/legion/extensions/temporal/helpers/temporal_store.rb +86 -0
- data/lib/legion/extensions/temporal/helpers/time_perception.rb +148 -0
- data/lib/legion/extensions/temporal/runners/temporal.rb +118 -0
- data/lib/legion/extensions/temporal/version.rb +9 -0
- data/lib/legion/extensions/temporal.rb +17 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5f4a4b711b75ed1f26943f373aaebb70ee08274151a67c952a5894ba64cb45d2
|
|
4
|
+
data.tar.gz: 8b78d44cbde78674c8d7b8149da24ff36bd80b4b587e24d5d238715518586d3b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 627401699a4d741f3db2e29533cd79eb01eb153fa4f7218b395a6148ebb8134db5fb692c7fb5e8ee2eb5638c5dbd06d1bf50e3adfbc05a719467bf6d5d5d8e70
|
|
7
|
+
data.tar.gz: ec4a7c83958e5b54da0fb0afc997653ff24e8ac141c90b809885a46d53350c333c40b781e9c0f400274ed99a2170de1aa857d25fe43ac61513261778b49d90d2
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::Temporal
|
|
8
|
+
|
|
9
|
+
attr_reader :temporal_store
|
|
10
|
+
|
|
11
|
+
def initialize(temporal_store: nil, **)
|
|
12
|
+
@temporal_store = temporal_store || Helpers::TemporalStore.new
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
# Time horizons for urgency classification
|
|
9
|
+
URGENCY_HORIZONS = {
|
|
10
|
+
immediate: 10, # seconds
|
|
11
|
+
short_term: 60, # 1 minute
|
|
12
|
+
medium: 300, # 5 minutes
|
|
13
|
+
long_term: 3600, # 1 hour
|
|
14
|
+
distant: 86_400 # 1 day
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
# Urgency levels (higher = more urgent)
|
|
18
|
+
URGENCY_LEVELS = %i[none low moderate high critical].freeze
|
|
19
|
+
|
|
20
|
+
# Deadline urgency thresholds (fraction of remaining time)
|
|
21
|
+
DEADLINE_URGENCY = {
|
|
22
|
+
critical: 0.1, # <= 10% time remaining
|
|
23
|
+
high: 0.25, # <= 25%
|
|
24
|
+
moderate: 0.5, # <= 50%
|
|
25
|
+
low: 0.75, # <= 75%
|
|
26
|
+
none: 1.0 # > 75%
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# Temporal pattern types
|
|
30
|
+
PATTERN_TYPES = %i[periodic bursty circadian random].freeze
|
|
31
|
+
|
|
32
|
+
# Minimum occurrences to detect a pattern
|
|
33
|
+
MIN_PATTERN_OBSERVATIONS = 5
|
|
34
|
+
|
|
35
|
+
# Maximum tracked events per domain
|
|
36
|
+
MAX_EVENTS_PER_DOMAIN = 100
|
|
37
|
+
|
|
38
|
+
# Maximum active deadlines
|
|
39
|
+
MAX_DEADLINES = 50
|
|
40
|
+
|
|
41
|
+
# Maximum temporal patterns tracked
|
|
42
|
+
MAX_PATTERNS = 30
|
|
43
|
+
|
|
44
|
+
# How many recent events to keep globally
|
|
45
|
+
MAX_GLOBAL_EVENTS = 500
|
|
46
|
+
|
|
47
|
+
# Coefficient of variation threshold for periodic vs bursty
|
|
48
|
+
PERIODIC_CV_THRESHOLD = 0.3
|
|
49
|
+
|
|
50
|
+
# Burst detection: events within this multiplier of mean interval
|
|
51
|
+
BURST_MULTIPLIER = 0.25
|
|
52
|
+
|
|
53
|
+
# Subjective time dilation factor ranges
|
|
54
|
+
# When arousal is high, time feels slower (more ticks perceived)
|
|
55
|
+
# When bored/low arousal, time flies
|
|
56
|
+
DILATION_RANGE = { min: 0.5, max: 2.0 }.freeze
|
|
57
|
+
|
|
58
|
+
# EMA alpha for subjective time tracking
|
|
59
|
+
SUBJECTIVE_ALPHA = 0.2
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
module Helpers
|
|
7
|
+
class TemporalPattern
|
|
8
|
+
attr_reader :domain, :event, :pattern_type, :mean_interval,
|
|
9
|
+
:observation_count, :last_predicted, :accuracy_count, :total_predictions
|
|
10
|
+
|
|
11
|
+
def initialize(domain:, event:)
|
|
12
|
+
@domain = domain
|
|
13
|
+
@event = event
|
|
14
|
+
@intervals = []
|
|
15
|
+
@pattern_type = :random
|
|
16
|
+
@mean_interval = nil
|
|
17
|
+
@observation_count = 0
|
|
18
|
+
@last_predicted = nil
|
|
19
|
+
@accuracy_count = 0
|
|
20
|
+
@total_predictions = 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_observation(timestamps)
|
|
24
|
+
return if timestamps.size < 2
|
|
25
|
+
|
|
26
|
+
@intervals = compute_intervals(timestamps)
|
|
27
|
+
@observation_count = timestamps.size
|
|
28
|
+
@mean_interval = @intervals.sum / @intervals.size.to_f
|
|
29
|
+
@pattern_type = classify_pattern
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def predict_next(from: Time.now.utc)
|
|
33
|
+
return nil unless @mean_interval && @observation_count >= Constants::MIN_PATTERN_OBSERVATIONS
|
|
34
|
+
|
|
35
|
+
predicted = from + @mean_interval
|
|
36
|
+
@last_predicted = predicted
|
|
37
|
+
@total_predictions += 1
|
|
38
|
+
{ predicted_at: predicted, confidence: prediction_confidence, pattern: @pattern_type }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def record_actual(actual_time)
|
|
42
|
+
return unless @last_predicted
|
|
43
|
+
|
|
44
|
+
error = (actual_time - @last_predicted).abs
|
|
45
|
+
tolerance = (@mean_interval || 60) * 0.3
|
|
46
|
+
@accuracy_count += 1 if error <= tolerance
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def prediction_accuracy
|
|
50
|
+
return 0.0 if @total_predictions.zero?
|
|
51
|
+
|
|
52
|
+
@accuracy_count.to_f / @total_predictions
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def periodic?
|
|
56
|
+
@pattern_type == :periodic
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def bursty?
|
|
60
|
+
@pattern_type == :bursty
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_h
|
|
64
|
+
{
|
|
65
|
+
domain: @domain,
|
|
66
|
+
event: @event,
|
|
67
|
+
pattern_type: @pattern_type,
|
|
68
|
+
mean_interval: @mean_interval,
|
|
69
|
+
observation_count: @observation_count,
|
|
70
|
+
accuracy: prediction_accuracy
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def compute_intervals(timestamps)
|
|
77
|
+
sorted = timestamps.sort
|
|
78
|
+
sorted.each_cons(2).map { |a, b| b - a }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def classify_pattern
|
|
82
|
+
return :random if @intervals.size < Constants::MIN_PATTERN_OBSERVATIONS
|
|
83
|
+
|
|
84
|
+
cv = coefficient_of_variation
|
|
85
|
+
if cv < Constants::PERIODIC_CV_THRESHOLD
|
|
86
|
+
:periodic
|
|
87
|
+
elsif bursts?
|
|
88
|
+
:bursty
|
|
89
|
+
else
|
|
90
|
+
:random
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def coefficient_of_variation
|
|
95
|
+
return Float::INFINITY if @intervals.empty? || @mean_interval.nil? || @mean_interval.zero?
|
|
96
|
+
|
|
97
|
+
std_dev = Math.sqrt(@intervals.map { |i| (i - @mean_interval)**2 }.sum / @intervals.size.to_f)
|
|
98
|
+
std_dev / @mean_interval
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def bursts?
|
|
102
|
+
return false unless @mean_interval
|
|
103
|
+
|
|
104
|
+
burst_threshold = @mean_interval * Constants::BURST_MULTIPLIER
|
|
105
|
+
burst_count = @intervals.count { |i| i < burst_threshold }
|
|
106
|
+
burst_count > @intervals.size * 0.3
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def prediction_confidence
|
|
110
|
+
base = case @pattern_type
|
|
111
|
+
when :periodic then 0.8
|
|
112
|
+
when :bursty then 0.4
|
|
113
|
+
else 0.2
|
|
114
|
+
end
|
|
115
|
+
base * [1.0, @observation_count / 10.0].min
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
module Helpers
|
|
7
|
+
class TemporalStore
|
|
8
|
+
attr_reader :perception, :patterns
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@perception = TimePerception.new
|
|
12
|
+
@patterns = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def record_event(event, domain: :general)
|
|
16
|
+
key = @perception.mark_event(event, domain: domain)
|
|
17
|
+
update_pattern(domain, event)
|
|
18
|
+
key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def elapsed(event, domain: :general)
|
|
22
|
+
@perception.elapsed_since(event, domain: domain)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def predict_next(event, domain: :general)
|
|
26
|
+
pattern_key = "#{domain}:#{event}"
|
|
27
|
+
pattern = @patterns[pattern_key]
|
|
28
|
+
return nil unless pattern
|
|
29
|
+
|
|
30
|
+
pattern.predict_next
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def record_prediction_outcome(event, actual_time:, domain: :general)
|
|
34
|
+
pattern_key = "#{domain}:#{event}"
|
|
35
|
+
pattern = @patterns[pattern_key]
|
|
36
|
+
pattern&.record_actual(actual_time)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def detect_patterns_for(event, domain: :general)
|
|
40
|
+
pattern_key = "#{domain}:#{event}"
|
|
41
|
+
@patterns[pattern_key]&.to_h
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def all_patterns
|
|
45
|
+
@patterns.values.map(&:to_h)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def periodic_patterns
|
|
49
|
+
@patterns.values.select(&:periodic?).map(&:to_h)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def temporal_summary
|
|
53
|
+
{
|
|
54
|
+
perception: @perception.to_h,
|
|
55
|
+
pattern_count: @patterns.size,
|
|
56
|
+
periodic_count: @patterns.values.count(&:periodic?),
|
|
57
|
+
bursty_count: @patterns.values.count(&:bursty?),
|
|
58
|
+
overall_urgency: @perception.overall_urgency,
|
|
59
|
+
overdue_deadlines: @perception.overdue_deadlines.size
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def update_pattern(domain, event)
|
|
66
|
+
pattern_key = "#{domain}:#{event}"
|
|
67
|
+
event_key = "#{domain}:#{event}"
|
|
68
|
+
timestamps = @perception.events[event_key]
|
|
69
|
+
return unless timestamps && timestamps.size >= 2
|
|
70
|
+
|
|
71
|
+
@patterns[pattern_key] ||= TemporalPattern.new(domain: domain, event: event)
|
|
72
|
+
@patterns[pattern_key].add_observation(timestamps)
|
|
73
|
+
evict_patterns_if_needed
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def evict_patterns_if_needed
|
|
77
|
+
return unless @patterns.size > Constants::MAX_PATTERNS
|
|
78
|
+
|
|
79
|
+
weakest = @patterns.min_by { |_k, p| p.observation_count }
|
|
80
|
+
@patterns.delete(weakest.first) if weakest
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
module Helpers
|
|
7
|
+
class TimePerception
|
|
8
|
+
attr_reader :events, :deadlines, :subjective_rate
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@events = {}
|
|
12
|
+
@deadlines = {}
|
|
13
|
+
@subjective_rate = 1.0
|
|
14
|
+
@tick_count = 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def mark_event(event, domain: :general)
|
|
18
|
+
key = "#{domain}:#{event}"
|
|
19
|
+
@events[key] ||= []
|
|
20
|
+
@events[key] << Time.now.utc
|
|
21
|
+
trim_events(key)
|
|
22
|
+
key
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def elapsed_since(event, domain: :general)
|
|
26
|
+
key = "#{domain}:#{event}"
|
|
27
|
+
timestamps = @events[key]
|
|
28
|
+
return nil unless timestamps&.any?
|
|
29
|
+
|
|
30
|
+
Time.now.utc - timestamps.last
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def time_since_first(event, domain: :general)
|
|
34
|
+
key = "#{domain}:#{event}"
|
|
35
|
+
timestamps = @events[key]
|
|
36
|
+
return nil unless timestamps&.any?
|
|
37
|
+
|
|
38
|
+
Time.now.utc - timestamps.first
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def event_count(event, domain: :general)
|
|
42
|
+
key = "#{domain}:#{event}"
|
|
43
|
+
@events.fetch(key, []).size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_deadline(id, at:, description: nil)
|
|
47
|
+
@deadlines[id] = { at: at, created: Time.now.utc, description: description }
|
|
48
|
+
evict_deadlines_if_needed
|
|
49
|
+
id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def remove_deadline(id)
|
|
53
|
+
@deadlines.delete(id)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def deadline_urgency(id)
|
|
57
|
+
dl = @deadlines[id]
|
|
58
|
+
return nil unless dl
|
|
59
|
+
|
|
60
|
+
remaining = dl[:at] - Time.now.utc
|
|
61
|
+
return :overdue if remaining <= 0
|
|
62
|
+
|
|
63
|
+
total = dl[:at] - dl[:created]
|
|
64
|
+
return :critical if total <= 0
|
|
65
|
+
|
|
66
|
+
fraction = remaining / total
|
|
67
|
+
classify_deadline_urgency(fraction)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def overdue_deadlines
|
|
71
|
+
now = Time.now.utc
|
|
72
|
+
@deadlines.select { |_id, dl| dl[:at] <= now }
|
|
73
|
+
.map { |id, dl| { id: id, overdue_by: now - dl[:at], description: dl[:description] } }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def upcoming_deadlines(within: 3600)
|
|
77
|
+
now = Time.now.utc
|
|
78
|
+
cutoff = now + within
|
|
79
|
+
@deadlines.select { |_id, dl| dl[:at] > now && dl[:at] <= cutoff }
|
|
80
|
+
.map { |id, dl| { id: id, remaining: dl[:at] - now, description: dl[:description] } }
|
|
81
|
+
.sort_by { |d| d[:remaining] }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_subjective_time(arousal: 0.5, cognitive_load: 0.5, **)
|
|
85
|
+
@tick_count += 1
|
|
86
|
+
raw_dilation = compute_dilation(arousal, cognitive_load)
|
|
87
|
+
@subjective_rate = ema(@subjective_rate, raw_dilation, Constants::SUBJECTIVE_ALPHA)
|
|
88
|
+
@subjective_rate
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def subjective_elapsed(real_seconds)
|
|
92
|
+
real_seconds * @subjective_rate
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def overall_urgency
|
|
96
|
+
urgencies = @deadlines.keys.map { |id| deadline_urgency(id) }.compact
|
|
97
|
+
return :none if urgencies.empty?
|
|
98
|
+
|
|
99
|
+
priority = Constants::URGENCY_LEVELS
|
|
100
|
+
urgencies.min_by { |u| u == :overdue ? -1 : priority.index(u) || 99 }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_h
|
|
104
|
+
{
|
|
105
|
+
event_domains: @events.size,
|
|
106
|
+
active_deadlines: @deadlines.size,
|
|
107
|
+
overdue_count: overdue_deadlines.size,
|
|
108
|
+
subjective_rate: @subjective_rate,
|
|
109
|
+
overall_urgency: overall_urgency,
|
|
110
|
+
tick_count: @tick_count
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def trim_events(key)
|
|
117
|
+
return unless @events[key].size > Constants::MAX_EVENTS_PER_DOMAIN
|
|
118
|
+
|
|
119
|
+
@events[key] = @events[key].last(Constants::MAX_EVENTS_PER_DOMAIN)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def evict_deadlines_if_needed
|
|
123
|
+
return unless @deadlines.size > Constants::MAX_DEADLINES
|
|
124
|
+
|
|
125
|
+
oldest_key = @deadlines.min_by { |_id, dl| dl[:created] }.first
|
|
126
|
+
@deadlines.delete(oldest_key)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def classify_deadline_urgency(fraction)
|
|
130
|
+
Constants::DEADLINE_URGENCY.each do |level, threshold|
|
|
131
|
+
return level if fraction <= threshold
|
|
132
|
+
end
|
|
133
|
+
:none
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def compute_dilation(arousal, cognitive_load)
|
|
137
|
+
raw = 1.0 + ((arousal - 0.5) * 1.0) + ((cognitive_load - 0.5) * 0.5)
|
|
138
|
+
raw.clamp(Constants::DILATION_RANGE[:min], Constants::DILATION_RANGE[:max])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ema(current, observed, alpha)
|
|
142
|
+
(current * (1.0 - alpha)) + (observed * alpha)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Temporal
|
|
6
|
+
module Runners
|
|
7
|
+
module Temporal
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
9
|
+
|
|
10
|
+
def mark_event(event:, domain: :general, **)
|
|
11
|
+
key = temporal_store.record_event(event, domain: domain)
|
|
12
|
+
count = temporal_store.perception.event_count(event, domain: domain)
|
|
13
|
+
{ marked: true, key: key, event: event, domain: domain, total_occurrences: count }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def elapsed_since(event:, domain: :general, **)
|
|
17
|
+
elapsed = temporal_store.elapsed(event, domain: domain)
|
|
18
|
+
if elapsed
|
|
19
|
+
{ event: event, domain: domain, elapsed_seconds: elapsed, human: humanize_duration(elapsed) }
|
|
20
|
+
else
|
|
21
|
+
{ event: event, domain: domain, elapsed_seconds: nil, reason: :no_record }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_deadline(id:, at:, description: nil, **)
|
|
26
|
+
temporal_store.perception.set_deadline(id, at: at, description: description)
|
|
27
|
+
{ set: true, id: id, at: at, description: description }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_deadlines(**)
|
|
31
|
+
{
|
|
32
|
+
overdue: temporal_store.perception.overdue_deadlines,
|
|
33
|
+
upcoming: temporal_store.perception.upcoming_deadlines,
|
|
34
|
+
urgency: temporal_store.perception.overall_urgency,
|
|
35
|
+
total: temporal_store.perception.deadlines.size
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update_time_perception(tick_results: {}, **)
|
|
40
|
+
arousal = extract_arousal(tick_results)
|
|
41
|
+
load = extract_cognitive_load(tick_results)
|
|
42
|
+
rate = temporal_store.perception.update_subjective_time(arousal: arousal, cognitive_load: load)
|
|
43
|
+
{
|
|
44
|
+
subjective_rate: rate,
|
|
45
|
+
interpretation: interpret_rate(rate),
|
|
46
|
+
overall_urgency: temporal_store.perception.overall_urgency,
|
|
47
|
+
overdue_count: temporal_store.perception.overdue_deadlines.size
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def predict_event(event:, domain: :general, **)
|
|
52
|
+
prediction = temporal_store.predict_next(event, domain: domain)
|
|
53
|
+
if prediction
|
|
54
|
+
{ prediction: prediction, event: event, domain: domain }
|
|
55
|
+
else
|
|
56
|
+
{ prediction: nil, reason: :insufficient_data }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def temporal_patterns(**)
|
|
61
|
+
{
|
|
62
|
+
all: temporal_store.all_patterns,
|
|
63
|
+
periodic: temporal_store.periodic_patterns,
|
|
64
|
+
count: temporal_store.patterns.size
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def temporal_stats(**)
|
|
69
|
+
temporal_store.temporal_summary
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def extract_arousal(tick_results)
|
|
75
|
+
emotion = tick_results[:emotional_evaluation]
|
|
76
|
+
return 0.5 unless emotion.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
emotion[:arousal] || 0.5
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_cognitive_load(tick_results)
|
|
82
|
+
elapsed = tick_results[:elapsed] || 0
|
|
83
|
+
budget = tick_results[:budget] || 1
|
|
84
|
+
return 0.5 if budget.zero?
|
|
85
|
+
|
|
86
|
+
(elapsed.to_f / budget).clamp(0.0, 1.0)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def humanize_duration(seconds)
|
|
90
|
+
if seconds < 60
|
|
91
|
+
"#{seconds.round(1)}s"
|
|
92
|
+
elsif seconds < 3600
|
|
93
|
+
"#{(seconds / 60).round(1)}m"
|
|
94
|
+
elsif seconds < 86_400
|
|
95
|
+
"#{(seconds / 3600).round(1)}h"
|
|
96
|
+
else
|
|
97
|
+
"#{(seconds / 86_400).round(1)}d"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def interpret_rate(rate)
|
|
102
|
+
if rate > 1.5
|
|
103
|
+
:time_crawling
|
|
104
|
+
elsif rate > 1.1
|
|
105
|
+
:time_slow
|
|
106
|
+
elsif rate < 0.7
|
|
107
|
+
:time_flying
|
|
108
|
+
elsif rate < 0.9
|
|
109
|
+
:time_fast
|
|
110
|
+
else
|
|
111
|
+
:normal
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'temporal/version'
|
|
4
|
+
require_relative 'temporal/helpers/constants'
|
|
5
|
+
require_relative 'temporal/helpers/time_perception'
|
|
6
|
+
require_relative 'temporal/helpers/temporal_pattern'
|
|
7
|
+
require_relative 'temporal/helpers/temporal_store'
|
|
8
|
+
require_relative 'temporal/runners/temporal'
|
|
9
|
+
require_relative 'temporal/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module Temporal
|
|
14
|
+
extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-temporal
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matthew Iverson
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: 'Provides time-aware cognitive processing: elapsed awareness, urgency,
|
|
27
|
+
temporal patterns, and deadline tracking'
|
|
28
|
+
email:
|
|
29
|
+
- matt@legionIO.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- lib/legion/extensions/temporal.rb
|
|
35
|
+
- lib/legion/extensions/temporal/client.rb
|
|
36
|
+
- lib/legion/extensions/temporal/helpers/constants.rb
|
|
37
|
+
- lib/legion/extensions/temporal/helpers/temporal_pattern.rb
|
|
38
|
+
- lib/legion/extensions/temporal/helpers/temporal_store.rb
|
|
39
|
+
- lib/legion/extensions/temporal/helpers/time_perception.rb
|
|
40
|
+
- lib/legion/extensions/temporal/runners/temporal.rb
|
|
41
|
+
- lib/legion/extensions/temporal/version.rb
|
|
42
|
+
homepage: https://github.com/LegionIO/lex-temporal
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata:
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.4'
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 3.6.9
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Temporal perception and time reasoning for LegionIO
|
|
64
|
+
test_files: []
|