lex-cognitive-rhythm 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b10cd9e09140796135a50743e3221fb1898e54330483eb221a75cecc2ccbb4ba
4
+ data.tar.gz: 7e80c1170efa27c27c9ad8ef80a90f27799cff2215f10e320c5b966a6df217e6
5
+ SHA512:
6
+ metadata.gz: 38dd4deb2b004ce766ec60114adb12be669caa24159ccc558a990d318824e737682437d535a7d413d70fc6572de647bdc18ed9a3b212b7caeeeb21c5904f1f30
7
+ data.tar.gz: e95dbb8690f885fcf0a4bb95d58f1a39abcfcca132c1beaf96e385e7184f5ac56d4a07092d58072eaa0a961486edf939939250ecefc47af57499e9df3424adb1
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ class Client
7
+ include Runners::CognitiveRhythm
8
+
9
+ def initialize(engine: nil)
10
+ @engine = engine || Helpers::RhythmEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ module Helpers
7
+ module Constants
8
+ ULTRADIAN_PERIOD = 5400
9
+ CIRCADIAN_PERIOD = 86_400
10
+ MIN_AMPLITUDE = 0.0
11
+ MAX_AMPLITUDE = 1.0
12
+ DEFAULT_PHASE_OFFSET = 0.0
13
+ MAX_RHYTHMS = 20
14
+ MAX_HISTORY = 200
15
+
16
+ RHYTHM_TYPES = %i[ultradian circadian custom].freeze
17
+
18
+ COGNITIVE_DIMENSIONS = %i[alertness creativity focus analytical social emotional].freeze
19
+
20
+ PHASE_LABELS = {
21
+ (0.0...0.25) => :rising,
22
+ (0.25...0.5) => :peak,
23
+ (0.5...0.75) => :falling,
24
+ (0.75..1.0) => :trough
25
+ }.freeze
26
+
27
+ AMPLITUDE_LABELS = {
28
+ (0.8..) => :high,
29
+ (0.6...0.8) => :moderate_high,
30
+ (0.4...0.6) => :moderate,
31
+ (0.2...0.4) => :moderate_low,
32
+ (..0.2) => :low
33
+ }.freeze
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ module Helpers
7
+ class Rhythm
8
+ include Constants
9
+
10
+ attr_reader :id, :name, :rhythm_type, :dimension, :period,
11
+ :amplitude, :phase_offset, :created_at
12
+
13
+ def initialize(id:, name:, rhythm_type:, dimension:, period:,
14
+ amplitude: 0.5, phase_offset: 0.0)
15
+ @id = id
16
+ @name = name
17
+ @rhythm_type = rhythm_type
18
+ @dimension = dimension
19
+ @period = period
20
+ @amplitude = amplitude.to_f.clamp(MIN_AMPLITUDE, MAX_AMPLITUDE)
21
+ @phase_offset = phase_offset.to_f
22
+ @created_at = Time.now.utc
23
+ end
24
+
25
+ def value_at(time)
26
+ elapsed = time.to_f - @created_at.to_f + @phase_offset
27
+ @amplitude * (0.5 + (0.5 * Math.sin(2 * Math::PI * (elapsed / @period))))
28
+ end
29
+
30
+ def current_value
31
+ value_at(Time.now.utc)
32
+ end
33
+
34
+ def phase_at(time)
35
+ elapsed = time.to_f - @created_at.to_f + @phase_offset
36
+ (elapsed % @period) / @period
37
+ end
38
+
39
+ def current_phase
40
+ phase_at(Time.now.utc)
41
+ end
42
+
43
+ def phase_label
44
+ phase = current_phase
45
+ PHASE_LABELS.each { |range, label| return label if range.cover?(phase) }
46
+ :trough
47
+ end
48
+
49
+ def amplitude_label
50
+ AMPLITUDE_LABELS.each { |range, label| return label if range.cover?(@amplitude) }
51
+ :low
52
+ end
53
+
54
+ def peak?
55
+ phase_label == :peak
56
+ end
57
+
58
+ def trough?
59
+ phase_label == :trough
60
+ end
61
+
62
+ def rising?
63
+ phase_label == :rising
64
+ end
65
+
66
+ def falling?
67
+ phase_label == :falling
68
+ end
69
+
70
+ def to_h
71
+ {
72
+ id: @id,
73
+ name: @name,
74
+ rhythm_type: @rhythm_type,
75
+ dimension: @dimension,
76
+ period: @period,
77
+ amplitude: @amplitude,
78
+ phase_offset: @phase_offset,
79
+ current_value: current_value.round(4),
80
+ current_phase: current_phase.round(4),
81
+ phase_label: phase_label,
82
+ amplitude_label: amplitude_label
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ module Helpers
7
+ class RhythmEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @rhythms = {}
12
+ @counter = 0
13
+ @history = []
14
+ end
15
+
16
+ def add_rhythm(name:, rhythm_type:, dimension:, period: nil, amplitude: 0.5, phase_offset: 0.0)
17
+ return nil if @rhythms.size >= MAX_RHYTHMS
18
+
19
+ resolved_period = resolve_period(rhythm_type, period)
20
+ return nil if resolved_period.nil?
21
+
22
+ @counter += 1
23
+ id = :"rhythm_#{@counter}"
24
+ rhythm = Rhythm.new(
25
+ id: id,
26
+ name: name,
27
+ rhythm_type: rhythm_type,
28
+ dimension: dimension,
29
+ period: resolved_period,
30
+ amplitude: amplitude,
31
+ phase_offset: phase_offset
32
+ )
33
+ @rhythms[id] = rhythm
34
+ rhythm
35
+ end
36
+
37
+ def remove_rhythm(rhythm_id:)
38
+ removed = @rhythms.delete(rhythm_id)
39
+ removed ? { success: true, rhythm_id: rhythm_id } : { success: false, reason: :not_found }
40
+ end
41
+
42
+ def dimension_value(dimension:)
43
+ matching = @rhythms.values.select { |r| r.dimension == dimension }
44
+ return 0.0 if matching.empty?
45
+
46
+ matching.sum(&:current_value) / matching.size
47
+ end
48
+
49
+ def current_state
50
+ COGNITIVE_DIMENSIONS.to_h do |dim|
51
+ [dim, dimension_value(dimension: dim).round(4)]
52
+ end
53
+ end
54
+
55
+ def optimal_for(dimension:)
56
+ matching = @rhythms.values.select { |r| r.dimension == dimension }
57
+ return false if matching.empty?
58
+
59
+ matching.any?(&:peak?)
60
+ end
61
+
62
+ def best_time_for(dimension:, within: 3600)
63
+ matching = @rhythms.values.select { |r| r.dimension == dimension }
64
+ return nil if matching.empty?
65
+
66
+ now = Time.now.utc.to_f
67
+ step = 60
68
+ steps = within / step
69
+
70
+ best_time = nil
71
+ best_value = -1.0
72
+
73
+ (0..steps).each do |i|
74
+ t = now + (i * step)
75
+ value = matching.sum { |r| r.value_at(t) } / matching.size
76
+ if value > best_value
77
+ best_value = value
78
+ best_time = t
79
+ end
80
+ end
81
+
82
+ best_time ? Time.at(best_time).utc : nil
83
+ end
84
+
85
+ def synchronize(rhythm_ids:)
86
+ rhythms = rhythm_ids.map { |id| @rhythms[id] }.compact
87
+ return { success: false, reason: :not_found } if rhythms.empty?
88
+
89
+ reference_phase = rhythms.first.current_phase
90
+ rhythms.each do |r|
91
+ current = r.current_phase
92
+ offset_adjustment = (reference_phase - current) * r.period
93
+ instance_variable_set_phase_offset(r, r.phase_offset + offset_adjustment)
94
+ end
95
+
96
+ { success: true, synchronized: rhythms.map(&:id) }
97
+ end
98
+
99
+ def cognitive_profile
100
+ current = current_state
101
+ current.each_with_object({}) do |(dim, value), hash|
102
+ phase_label = dominant_phase_label(dim)
103
+ amplitude_label = dominant_amplitude_label(dim)
104
+ hash[dim] = {
105
+ value: value,
106
+ phase_label: phase_label,
107
+ amplitude_label: amplitude_label,
108
+ optimal: optimal_for(dimension: dim)
109
+ }
110
+ end
111
+ end
112
+
113
+ def peak_dimensions
114
+ COGNITIVE_DIMENSIONS.select { |dim| optimal_for(dimension: dim) }
115
+ end
116
+
117
+ def trough_dimensions
118
+ COGNITIVE_DIMENSIONS.select do |dim|
119
+ matching = @rhythms.values.select { |r| r.dimension == dim }
120
+ matching.any?(&:trough?)
121
+ end
122
+ end
123
+
124
+ def to_h
125
+ {
126
+ rhythm_count: @rhythms.size,
127
+ rhythms: @rhythms.values.map(&:to_h),
128
+ current_state: current_state
129
+ }
130
+ end
131
+
132
+ private
133
+
134
+ def resolve_period(rhythm_type, period)
135
+ case rhythm_type
136
+ when :ultradian then ULTRADIAN_PERIOD
137
+ when :circadian then CIRCADIAN_PERIOD
138
+ when :custom then period
139
+ end
140
+ end
141
+
142
+ def dominant_phase_label(dimension)
143
+ matching = @rhythms.values.select { |r| r.dimension == dimension }
144
+ return :none if matching.empty?
145
+
146
+ matching.max_by(&:current_value)&.phase_label || :none
147
+ end
148
+
149
+ def dominant_amplitude_label(dimension)
150
+ matching = @rhythms.values.select { |r| r.dimension == dimension }
151
+ return :none if matching.empty?
152
+
153
+ matching.max_by(&:amplitude)&.amplitude_label || :none
154
+ end
155
+
156
+ def instance_variable_set_phase_offset(rhythm, new_offset)
157
+ rhythm.instance_variable_set(:@phase_offset, new_offset.to_f)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ module Runners
7
+ module CognitiveRhythm
8
+ include Helpers::Constants
9
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
10
+
11
+ def add_cognitive_rhythm(name:, rhythm_type:, dimension:, period: nil,
12
+ amplitude: 0.5, phase_offset: 0.0, **)
13
+ rhythm = engine.add_rhythm(
14
+ name: name,
15
+ rhythm_type: rhythm_type,
16
+ dimension: dimension,
17
+ period: period,
18
+ amplitude: amplitude,
19
+ phase_offset: phase_offset
20
+ )
21
+ return { success: false, reason: :limit_reached_or_invalid } unless rhythm
22
+
23
+ { success: true, rhythm_id: rhythm.id, name: rhythm.name, dimension: rhythm.dimension }
24
+ end
25
+
26
+ def remove_cognitive_rhythm(rhythm_id:, **)
27
+ result = engine.remove_rhythm(rhythm_id: rhythm_id)
28
+ result[:success] ? result : { success: false, reason: result[:reason] }
29
+ end
30
+
31
+ def current_rhythm_state(**)
32
+ { success: true, state: engine.current_state }
33
+ end
34
+
35
+ def dimension_rhythm_value(dimension:, **)
36
+ value = engine.dimension_value(dimension: dimension)
37
+ { success: true, dimension: dimension, value: value.round(4) }
38
+ end
39
+
40
+ def optimal_for_task(dimension:, **)
41
+ { success: true, dimension: dimension, optimal: engine.optimal_for(dimension: dimension) }
42
+ end
43
+
44
+ def best_time_for_task(dimension:, within: 3600, **)
45
+ time = engine.best_time_for(dimension: dimension, within: within)
46
+ return { success: false, reason: :no_rhythms_for_dimension } unless time
47
+
48
+ { success: true, dimension: dimension, best_time: time.iso8601, within_seconds: within }
49
+ end
50
+
51
+ def cognitive_rhythm_profile(**)
52
+ { success: true, profile: engine.cognitive_profile }
53
+ end
54
+
55
+ def peak_cognitive_dimensions(**)
56
+ dims = engine.peak_dimensions
57
+ { success: true, dimensions: dims, count: dims.size }
58
+ end
59
+
60
+ def trough_cognitive_dimensions(**)
61
+ dims = engine.trough_dimensions
62
+ { success: true, dimensions: dims, count: dims.size }
63
+ end
64
+
65
+ def cognitive_rhythm_stats(**)
66
+ { success: true }.merge(engine.to_h)
67
+ end
68
+
69
+ private
70
+
71
+ def engine
72
+ @engine ||= Helpers::RhythmEngine.new
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveRhythm
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-rhythm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
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: Ultradian and circadian cognitive rhythm engine for LegionIO — models
27
+ attention peaks, energy troughs, and creative windows
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/legion/extensions/cognitive_rhythm/client.rb
35
+ - lib/legion/extensions/cognitive_rhythm/helpers/constants.rb
36
+ - lib/legion/extensions/cognitive_rhythm/helpers/rhythm.rb
37
+ - lib/legion/extensions/cognitive_rhythm/helpers/rhythm_engine.rb
38
+ - lib/legion/extensions/cognitive_rhythm/runners/cognitive_rhythm.rb
39
+ - lib/legion/extensions/cognitive_rhythm/version.rb
40
+ homepage: https://github.com/LegionIO/lex-cognitive-rhythm
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-rhythm
45
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-rhythm
46
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-rhythm/blob/master/README.md
47
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-rhythm/blob/master/CHANGELOG.md
48
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-rhythm/issues
49
+ rubygems_mfa_required: 'true'
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3.4'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.6.9
65
+ specification_version: 4
66
+ summary: Cognitive rhythm modelling for LegionIO
67
+ test_files: []