lex-cognitive-pendulum 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/Gemfile +11 -0
- data/lex-cognitive-pendulum.gemspec +29 -0
- data/lib/legion/extensions/cognitive_pendulum/client.rb +24 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/constants.rb +45 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/pendulum.rb +96 -0
- data/lib/legion/extensions/cognitive_pendulum/helpers/pendulum_engine.rb +85 -0
- data/lib/legion/extensions/cognitive_pendulum/runners/cognitive_pendulum.rb +121 -0
- data/lib/legion/extensions/cognitive_pendulum/version.rb +9 -0
- data/lib/legion/extensions/cognitive_pendulum.rb +17 -0
- data/spec/legion/extensions/cognitive_pendulum/client_spec.rb +25 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/constants_spec.rb +103 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/pendulum_engine_spec.rb +198 -0
- data/spec/legion/extensions/cognitive_pendulum/helpers/pendulum_spec.rb +247 -0
- data/spec/legion/extensions/cognitive_pendulum/runners/cognitive_pendulum_spec.rb +250 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitivePendulum::Helpers::PendulumEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'starts with empty pendulums hash' do
|
|
8
|
+
expect(engine.pendulums).to be_empty
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'reports count of 0' do
|
|
12
|
+
expect(engine.count).to eq(0)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#create_pendulum' do
|
|
17
|
+
it 'creates a pendulum and returns it' do
|
|
18
|
+
p = engine.create_pendulum(pole_pair: :certainty_doubt)
|
|
19
|
+
expect(p).to be_a(Legion::Extensions::CognitivePendulum::Helpers::Pendulum)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'stores the pendulum by id' do
|
|
23
|
+
p = engine.create_pendulum(pole_pair: :focus_diffusion)
|
|
24
|
+
expect(engine.pendulums[p.id]).to eq(p)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'increments count' do
|
|
28
|
+
engine.create_pendulum(pole_pair: :certainty_doubt)
|
|
29
|
+
expect(engine.count).to eq(1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'accepts amplitude, period, and damping' do
|
|
33
|
+
p = engine.create_pendulum(pole_pair: :analysis_intuition, amplitude: 0.8, period: 20.0, damping: 0.05)
|
|
34
|
+
expect(p.amplitude).to eq(0.8)
|
|
35
|
+
expect(p.period).to eq(20.0)
|
|
36
|
+
expect(p.damping).to eq(0.05)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'raises when max pendulums reached' do
|
|
40
|
+
Legion::Extensions::CognitivePendulum::Helpers::Constants::MAX_PENDULUMS.times do
|
|
41
|
+
engine.create_pendulum(pole_pair: :certainty_doubt)
|
|
42
|
+
end
|
|
43
|
+
expect { engine.create_pendulum(pole_pair: :certainty_doubt) }.to raise_error(ArgumentError, /max pendulums/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#swing' do
|
|
48
|
+
let!(:pendulum) { engine.create_pendulum(pole_pair: :focus_diffusion) }
|
|
49
|
+
|
|
50
|
+
it 'swings the pendulum and returns it' do
|
|
51
|
+
result = engine.swing(pendulum.id, force: 0.5)
|
|
52
|
+
expect(result).to eq(pendulum)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'updates position on the pendulum' do
|
|
56
|
+
engine.swing(pendulum.id, force: 0.6)
|
|
57
|
+
expect(pendulum.current_position).to eq(0.6)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'returns nil for unknown id' do
|
|
61
|
+
expect(engine.swing('no-such-id', force: 0.1)).to be_nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#damp_all!' do
|
|
66
|
+
it 'damps every pendulum' do
|
|
67
|
+
p1 = engine.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.8)
|
|
68
|
+
p2 = engine.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.6)
|
|
69
|
+
engine.damp_all!
|
|
70
|
+
expect(p1.amplitude).to be < 0.8
|
|
71
|
+
expect(p2.amplitude).to be < 0.6
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'does nothing with no pendulums' do
|
|
75
|
+
expect { engine.damp_all! }.not_to raise_error
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '#check_resonance' do
|
|
80
|
+
it 'returns ids of resonant pendulums' do
|
|
81
|
+
p = engine.create_pendulum(pole_pair: :analysis_intuition, period: 10.0)
|
|
82
|
+
natural = 1.0 / 10.0
|
|
83
|
+
result = engine.check_resonance(natural)
|
|
84
|
+
expect(result).to include(p.id)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'returns empty array when no resonance' do
|
|
88
|
+
engine.create_pendulum(pole_pair: :analysis_intuition, period: 10.0)
|
|
89
|
+
expect(engine.check_resonance(99.0)).to be_empty
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'returns empty array for zero or negative frequency' do
|
|
93
|
+
engine.create_pendulum(pole_pair: :analysis_intuition, period: 10.0)
|
|
94
|
+
expect(engine.check_resonance(0.0)).to be_empty
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#dominant_pole' do
|
|
99
|
+
let!(:pendulum) { engine.create_pendulum(pole_pair: :certainty_doubt) }
|
|
100
|
+
|
|
101
|
+
it 'returns :neutral at center' do
|
|
102
|
+
expect(engine.dominant_pole(pendulum.id)).to eq(:neutral)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'returns the correct pole after swinging' do
|
|
106
|
+
engine.swing(pendulum.id, force: 0.9)
|
|
107
|
+
expect(engine.dominant_pole(pendulum.id)).to eq(:doubt)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'returns nil for unknown id' do
|
|
111
|
+
expect(engine.dominant_pole('missing-id')).to be_nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe '#most_active' do
|
|
116
|
+
before do
|
|
117
|
+
engine.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.9)
|
|
118
|
+
engine.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.3)
|
|
119
|
+
engine.create_pendulum(pole_pair: :analysis_intuition, amplitude: 0.6)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'returns pendulums sorted by amplitude descending' do
|
|
123
|
+
result = engine.most_active(limit: 3)
|
|
124
|
+
amplitudes = result.map(&:amplitude)
|
|
125
|
+
expect(amplitudes).to eq(amplitudes.sort.reverse)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'respects limit' do
|
|
129
|
+
expect(engine.most_active(limit: 2).size).to eq(2)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'returns all if limit exceeds count' do
|
|
133
|
+
expect(engine.most_active(limit: 10).size).to eq(3)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe '#most_damped' do
|
|
138
|
+
before do
|
|
139
|
+
engine.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.1)
|
|
140
|
+
engine.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.8)
|
|
141
|
+
engine.create_pendulum(pole_pair: :analysis_intuition, amplitude: 0.4)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'returns pendulums sorted by amplitude ascending' do
|
|
145
|
+
result = engine.most_damped(limit: 3)
|
|
146
|
+
amplitudes = result.map(&:amplitude)
|
|
147
|
+
expect(amplitudes).to eq(amplitudes.sort)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'respects limit' do
|
|
151
|
+
expect(engine.most_damped(limit: 1).size).to eq(1)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe '#pendulum_report' do
|
|
156
|
+
before do
|
|
157
|
+
engine.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.8)
|
|
158
|
+
engine.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.5)
|
|
159
|
+
engine.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.3)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'includes total count' do
|
|
163
|
+
expect(engine.pendulum_report[:total]).to eq(3)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'includes max constant' do
|
|
167
|
+
expect(engine.pendulum_report[:max]).to eq(Legion::Extensions::CognitivePendulum::Helpers::Constants::MAX_PENDULUMS)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'groups pendulums by pole_pair' do
|
|
171
|
+
report = engine.pendulum_report
|
|
172
|
+
expect(report[:pole_pairs][:certainty_doubt]).to eq(2)
|
|
173
|
+
expect(report[:pole_pairs][:focus_diffusion]).to eq(1)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'includes most_active as hashes' do
|
|
177
|
+
report = engine.pendulum_report
|
|
178
|
+
expect(report[:most_active]).to be_an(Array)
|
|
179
|
+
expect(report[:most_active].first).to be_a(Hash)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'includes most_damped as hashes' do
|
|
183
|
+
report = engine.pendulum_report
|
|
184
|
+
expect(report[:most_damped]).to be_an(Array)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe '#get' do
|
|
189
|
+
it 'returns the pendulum by id' do
|
|
190
|
+
p = engine.create_pendulum(pole_pair: :approach_avoidance)
|
|
191
|
+
expect(engine.get(p.id)).to eq(p)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'returns nil for unknown id' do
|
|
195
|
+
expect(engine.get('not-here')).to be_nil
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitivePendulum::Helpers::Pendulum do
|
|
4
|
+
subject(:pendulum) { described_class.new(pole_pair: :certainty_doubt) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a uuid id' do
|
|
8
|
+
expect(pendulum.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores the pole_pair' do
|
|
12
|
+
expect(pendulum.pole_pair).to eq(:certainty_doubt)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'defaults amplitude to 0.5' do
|
|
16
|
+
expect(pendulum.amplitude).to eq(0.5)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults period to 10.0' do
|
|
20
|
+
expect(pendulum.period).to eq(10.0)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'defaults damping to DAMPING_RATE' do
|
|
24
|
+
expect(pendulum.damping).to eq(Legion::Extensions::CognitivePendulum::Helpers::Constants::DAMPING_RATE)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'initializes current_position to 0.0' do
|
|
28
|
+
expect(pendulum.current_position).to eq(0.0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'initializes swings to 0' do
|
|
32
|
+
expect(pendulum.swings).to eq(0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'sets created_at' do
|
|
36
|
+
expect(pendulum.created_at).to be_a(Time)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'raises for invalid pole_pair' do
|
|
40
|
+
expect { described_class.new(pole_pair: :nonsense) }.to raise_error(ArgumentError)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'raises when amplitude out of range' do
|
|
44
|
+
expect { described_class.new(pole_pair: :certainty_doubt, amplitude: 1.5) }.to raise_error(ArgumentError)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'raises when period is zero or negative' do
|
|
48
|
+
expect { described_class.new(pole_pair: :certainty_doubt, period: 0.0) }.to raise_error(ArgumentError)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'raises when damping is negative' do
|
|
52
|
+
expect { described_class.new(pole_pair: :certainty_doubt, damping: -0.1) }.to raise_error(ArgumentError)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'accepts custom amplitude' do
|
|
56
|
+
p = described_class.new(pole_pair: :focus_diffusion, amplitude: 0.8)
|
|
57
|
+
expect(p.amplitude).to eq(0.8)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'accepts custom period' do
|
|
61
|
+
p = described_class.new(pole_pair: :focus_diffusion, period: 30.0)
|
|
62
|
+
expect(p.period).to eq(30.0)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'accepts zero damping' do
|
|
66
|
+
p = described_class.new(pole_pair: :analysis_intuition, damping: 0.0)
|
|
67
|
+
expect(p.damping).to eq(0.0)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#swing!' do
|
|
72
|
+
it 'moves position toward positive with positive force' do
|
|
73
|
+
pendulum.swing!(force: 0.5)
|
|
74
|
+
expect(pendulum.current_position).to be > 0.0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'moves position toward negative with negative force' do
|
|
78
|
+
pendulum.swing!(force: -0.5)
|
|
79
|
+
expect(pendulum.current_position).to be < 0.0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'clamps position to 1.0 maximum' do
|
|
83
|
+
5.times { pendulum.swing!(force: 1.0) }
|
|
84
|
+
expect(pendulum.current_position).to eq(1.0)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'clamps position to -1.0 minimum' do
|
|
88
|
+
5.times { pendulum.swing!(force: -1.0) }
|
|
89
|
+
expect(pendulum.current_position).to eq(-1.0)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'clamps force to valid range' do
|
|
93
|
+
pendulum.swing!(force: 99.0)
|
|
94
|
+
expect(pendulum.current_position).to be <= 1.0
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'increments swing count' do
|
|
98
|
+
pendulum.swing!(force: 0.3)
|
|
99
|
+
expect(pendulum.swings).to eq(1)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'returns the new position' do
|
|
103
|
+
result = pendulum.swing!(force: 0.4)
|
|
104
|
+
expect(result).to eq(pendulum.current_position)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe '#damp!' do
|
|
109
|
+
it 'reduces amplitude' do
|
|
110
|
+
original = pendulum.amplitude
|
|
111
|
+
pendulum.damp!
|
|
112
|
+
expect(pendulum.amplitude).to be < original
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'reduces current_position magnitude' do
|
|
116
|
+
pendulum.swing!(force: 0.8)
|
|
117
|
+
original = pendulum.current_position.abs
|
|
118
|
+
pendulum.damp!
|
|
119
|
+
expect(pendulum.current_position.abs).to be < original
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'returns the new amplitude' do
|
|
123
|
+
result = pendulum.damp!
|
|
124
|
+
expect(result).to eq(pendulum.amplitude)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'never goes below 0' do
|
|
128
|
+
100.times { pendulum.damp! }
|
|
129
|
+
expect(pendulum.amplitude).to be >= 0.0
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe '#position_at' do
|
|
134
|
+
let(:p) { described_class.new(pole_pair: :focus_diffusion, amplitude: 1.0, period: 10.0, damping: 0.0) }
|
|
135
|
+
|
|
136
|
+
it 'returns a float' do
|
|
137
|
+
expect(p.position_at(0.0)).to be_a(Float)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'returns amplitude at time 0 (cos(0) = 1)' do
|
|
141
|
+
expect(p.position_at(0.0)).to eq(1.0)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'returns a value within [-1.0, 1.0]' do
|
|
145
|
+
expect(p.position_at(5.0)).to be_between(-1.0, 1.0)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'decays with positive damping' do
|
|
149
|
+
damped = described_class.new(pole_pair: :focus_diffusion, amplitude: 1.0, period: 10.0, damping: 0.1)
|
|
150
|
+
expect(damped.position_at(10.0).abs).to be < p.position_at(10.0).abs
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe '#at_pole_a?' do
|
|
155
|
+
it 'is false at neutral position' do
|
|
156
|
+
expect(pendulum.at_pole_a?).to be false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'is true when position <= -0.5' do
|
|
160
|
+
3.times { pendulum.swing!(force: -1.0) }
|
|
161
|
+
expect(pendulum.at_pole_a?).to be true
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe '#at_pole_b?' do
|
|
166
|
+
it 'is false at neutral position' do
|
|
167
|
+
expect(pendulum.at_pole_b?).to be false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'is true when position >= 0.5' do
|
|
171
|
+
3.times { pendulum.swing!(force: 1.0) }
|
|
172
|
+
expect(pendulum.at_pole_b?).to be true
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe '#amplitude_label' do
|
|
177
|
+
it 'returns :moderate for default amplitude' do
|
|
178
|
+
expect(pendulum.amplitude_label).to eq(:moderate)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'returns :minimal for low amplitude' do
|
|
182
|
+
p = described_class.new(pole_pair: :certainty_doubt, amplitude: 0.1)
|
|
183
|
+
expect(p.amplitude_label).to eq(:minimal)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'returns :maximal for high amplitude' do
|
|
187
|
+
p = described_class.new(pole_pair: :certainty_doubt, amplitude: 0.9)
|
|
188
|
+
expect(p.amplitude_label).to eq(:maximal)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
describe '#resonant_with?' do
|
|
193
|
+
let(:p) { described_class.new(pole_pair: :analysis_intuition, period: 10.0) }
|
|
194
|
+
|
|
195
|
+
it 'returns true when frequency matches natural frequency' do
|
|
196
|
+
natural = 1.0 / 10.0
|
|
197
|
+
expect(p.resonant_with?(natural)).to be true
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'returns false when frequency is far off' do
|
|
201
|
+
expect(p.resonant_with?(0.5)).to be false
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'returns false for zero or negative frequency' do
|
|
205
|
+
expect(p.resonant_with?(0.0)).to be false
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'returns true within 5% tolerance' do
|
|
209
|
+
natural = 1.0 / 10.0
|
|
210
|
+
expect(p.resonant_with?(natural * 1.04)).to be true
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
describe '#dominant_pole' do
|
|
215
|
+
it 'returns :neutral near center' do
|
|
216
|
+
expect(pendulum.dominant_pole).to eq(:neutral)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'returns pole_a when position is negative' do
|
|
220
|
+
3.times { pendulum.swing!(force: -1.0) }
|
|
221
|
+
expect(pendulum.dominant_pole).to eq(:certainty)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'returns pole_b when position is positive' do
|
|
225
|
+
3.times { pendulum.swing!(force: 1.0) }
|
|
226
|
+
expect(pendulum.dominant_pole).to eq(:doubt)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe '#to_h' do
|
|
231
|
+
it 'includes all expected keys' do
|
|
232
|
+
h = pendulum.to_h
|
|
233
|
+
%i[id pole_pair pole_a pole_b amplitude amplitude_label period damping
|
|
234
|
+
current_position dominant_pole at_pole_a at_pole_b swings created_at].each do |key|
|
|
235
|
+
expect(h).to have_key(key)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'pole_a is :certainty for certainty_doubt pair' do
|
|
240
|
+
expect(pendulum.to_h[:pole_a]).to eq(:certainty)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'pole_b is :doubt for certainty_doubt pair' do
|
|
244
|
+
expect(pendulum.to_h[:pole_b]).to eq(:doubt)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_pendulum/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CognitivePendulum::Runners::CognitivePendulum do
|
|
6
|
+
let(:client) { Legion::Extensions::CognitivePendulum::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#create_pendulum' do
|
|
9
|
+
it 'returns success with valid pole_pair' do
|
|
10
|
+
result = client.create_pendulum(pole_pair: :certainty_doubt)
|
|
11
|
+
expect(result[:success]).to be true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'returns a pendulum_id uuid' do
|
|
15
|
+
result = client.create_pendulum(pole_pair: :focus_diffusion)
|
|
16
|
+
expect(result[:pendulum_id]).to match(/\A[0-9a-f-]{36}\z/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'echoes the pole_pair' do
|
|
20
|
+
result = client.create_pendulum(pole_pair: :analysis_intuition)
|
|
21
|
+
expect(result[:pole_pair]).to eq(:analysis_intuition)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns the amplitude' do
|
|
25
|
+
result = client.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.7)
|
|
26
|
+
expect(result[:amplitude]).to eq(0.7)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns error for invalid pole_pair' do
|
|
30
|
+
result = client.create_pendulum(pole_pair: :bogus)
|
|
31
|
+
expect(result[:success]).to be false
|
|
32
|
+
expect(result[:error]).to eq(:invalid_pole_pair)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'includes valid_pairs in error response' do
|
|
36
|
+
result = client.create_pendulum(pole_pair: :bogus)
|
|
37
|
+
expect(result[:valid_pairs]).to be_an(Array)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'returns argument_error for invalid amplitude' do
|
|
41
|
+
result = client.create_pendulum(pole_pair: :certainty_doubt, amplitude: 5.0)
|
|
42
|
+
expect(result[:success]).to be false
|
|
43
|
+
expect(result[:error]).to eq(:argument_error)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns argument_error for non-positive period' do
|
|
47
|
+
result = client.create_pendulum(pole_pair: :certainty_doubt, period: -1.0)
|
|
48
|
+
expect(result[:success]).to be false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'accepts all five pole pairs' do
|
|
52
|
+
%i[certainty_doubt focus_diffusion analysis_intuition approach_avoidance convergent_divergent].each do |pair|
|
|
53
|
+
result = client.create_pendulum(pole_pair: pair)
|
|
54
|
+
expect(result[:success]).to be true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#swing' do
|
|
60
|
+
let(:pendulum_id) { client.create_pendulum(pole_pair: :certainty_doubt)[:pendulum_id] }
|
|
61
|
+
|
|
62
|
+
it 'returns success for valid pendulum' do
|
|
63
|
+
result = client.swing(pendulum_id: pendulum_id, force: 0.5)
|
|
64
|
+
expect(result[:success]).to be true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'returns the current_position' do
|
|
68
|
+
result = client.swing(pendulum_id: pendulum_id, force: 0.5)
|
|
69
|
+
expect(result[:current_position]).to eq(0.5)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'returns the dominant_pole' do
|
|
73
|
+
result = client.swing(pendulum_id: pendulum_id, force: 0.8)
|
|
74
|
+
expect(result).to have_key(:dominant_pole)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'returns not_found for unknown id' do
|
|
78
|
+
result = client.swing(pendulum_id: 'no-such-id', force: 0.1)
|
|
79
|
+
expect(result[:success]).to be false
|
|
80
|
+
expect(result[:error]).to eq(:not_found)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'swings multiple times and accumulates position' do
|
|
84
|
+
client.swing(pendulum_id: pendulum_id, force: 0.3)
|
|
85
|
+
result = client.swing(pendulum_id: pendulum_id, force: 0.3)
|
|
86
|
+
expect(result[:current_position]).to be_within(0.001).of(0.6)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#damp_all' do
|
|
91
|
+
it 'returns success' do
|
|
92
|
+
result = client.damp_all
|
|
93
|
+
expect(result[:success]).to be true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'reports number of damped pendulums' do
|
|
97
|
+
client.create_pendulum(pole_pair: :focus_diffusion)
|
|
98
|
+
client.create_pendulum(pole_pair: :analysis_intuition)
|
|
99
|
+
result = client.damp_all
|
|
100
|
+
expect(result[:damped]).to eq(2)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'reports zero when no pendulums exist' do
|
|
104
|
+
result = client.damp_all
|
|
105
|
+
expect(result[:damped]).to eq(0)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#check_resonance' do
|
|
110
|
+
before { client.create_pendulum(pole_pair: :analysis_intuition, period: 10.0) }
|
|
111
|
+
|
|
112
|
+
it 'returns success' do
|
|
113
|
+
result = client.check_resonance(frequency: 0.1)
|
|
114
|
+
expect(result[:success]).to be true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'detects resonance at natural frequency' do
|
|
118
|
+
result = client.check_resonance(frequency: 0.1)
|
|
119
|
+
expect(result[:count]).to eq(1)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'returns empty when no resonance' do
|
|
123
|
+
result = client.check_resonance(frequency: 99.0)
|
|
124
|
+
expect(result[:count]).to eq(0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'returns error for non-positive frequency' do
|
|
128
|
+
result = client.check_resonance(frequency: 0.0)
|
|
129
|
+
expect(result[:success]).to be false
|
|
130
|
+
expect(result[:error]).to eq(:invalid_frequency)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'returns resonant_pendulum_ids array' do
|
|
134
|
+
result = client.check_resonance(frequency: 0.1)
|
|
135
|
+
expect(result[:resonant_pendulum_ids]).to be_an(Array)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'echoes the frequency' do
|
|
139
|
+
result = client.check_resonance(frequency: 0.1)
|
|
140
|
+
expect(result[:frequency]).to eq(0.1)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe '#get_dominant_pole' do
|
|
145
|
+
let(:pendulum_id) { client.create_pendulum(pole_pair: :certainty_doubt)[:pendulum_id] }
|
|
146
|
+
|
|
147
|
+
it 'returns success for known pendulum' do
|
|
148
|
+
result = client.get_dominant_pole(pendulum_id: pendulum_id)
|
|
149
|
+
expect(result[:success]).to be true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'returns :neutral for centered pendulum' do
|
|
153
|
+
result = client.get_dominant_pole(pendulum_id: pendulum_id)
|
|
154
|
+
expect(result[:dominant_pole]).to eq(:neutral)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'returns the active pole after swinging' do
|
|
158
|
+
client.swing(pendulum_id: pendulum_id, force: 0.8)
|
|
159
|
+
result = client.get_dominant_pole(pendulum_id: pendulum_id)
|
|
160
|
+
expect(result[:dominant_pole]).to eq(:doubt)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'returns not_found for unknown id' do
|
|
164
|
+
result = client.get_dominant_pole(pendulum_id: 'ghost')
|
|
165
|
+
expect(result[:success]).to be false
|
|
166
|
+
expect(result[:error]).to eq(:not_found)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe '#most_active' do
|
|
171
|
+
before do
|
|
172
|
+
client.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.9)
|
|
173
|
+
client.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.2)
|
|
174
|
+
client.create_pendulum(pole_pair: :analysis_intuition, amplitude: 0.6)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'returns success' do
|
|
178
|
+
expect(client.most_active[:success]).to be true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'returns pendulums sorted by descending amplitude' do
|
|
182
|
+
result = client.most_active(limit: 3)
|
|
183
|
+
amplitudes = result[:pendulums].map { |p| p[:amplitude] }
|
|
184
|
+
expect(amplitudes).to eq(amplitudes.sort.reverse)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'respects limit' do
|
|
188
|
+
result = client.most_active(limit: 2)
|
|
189
|
+
expect(result[:count]).to eq(2)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
describe '#most_damped' do
|
|
194
|
+
before do
|
|
195
|
+
client.create_pendulum(pole_pair: :certainty_doubt, amplitude: 0.1)
|
|
196
|
+
client.create_pendulum(pole_pair: :focus_diffusion, amplitude: 0.7)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'returns success' do
|
|
200
|
+
expect(client.most_damped[:success]).to be true
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'returns pendulums sorted by ascending amplitude' do
|
|
204
|
+
result = client.most_damped(limit: 2)
|
|
205
|
+
amplitudes = result[:pendulums].map { |p| p[:amplitude] }
|
|
206
|
+
expect(amplitudes).to eq(amplitudes.sort)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
describe '#pendulum_report' do
|
|
211
|
+
before do
|
|
212
|
+
client.create_pendulum(pole_pair: :certainty_doubt)
|
|
213
|
+
client.create_pendulum(pole_pair: :focus_diffusion)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'returns success' do
|
|
217
|
+
expect(client.pendulum_report[:success]).to be true
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'includes a report hash' do
|
|
221
|
+
result = client.pendulum_report
|
|
222
|
+
expect(result[:report]).to be_a(Hash)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'report has total of 2' do
|
|
226
|
+
expect(client.pendulum_report[:report][:total]).to eq(2)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
describe '#get_pendulum' do
|
|
231
|
+
let(:pendulum_id) { client.create_pendulum(pole_pair: :approach_avoidance)[:pendulum_id] }
|
|
232
|
+
|
|
233
|
+
it 'returns success with the pendulum hash' do
|
|
234
|
+
result = client.get_pendulum(pendulum_id: pendulum_id)
|
|
235
|
+
expect(result[:success]).to be true
|
|
236
|
+
expect(result[:pendulum]).to be_a(Hash)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'returns not_found for unknown id' do
|
|
240
|
+
result = client.get_pendulum(pendulum_id: 'fake-id')
|
|
241
|
+
expect(result[:success]).to be false
|
|
242
|
+
expect(result[:error]).to eq(:not_found)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'pendulum hash includes pole_pair' do
|
|
246
|
+
result = client.get_pendulum(pendulum_id: pendulum_id)
|
|
247
|
+
expect(result[:pendulum][:pole_pair]).to eq(:approach_avoidance)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|