lex-cognitive-offloading 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-offloading.gemspec +29 -0
- data/lib/legion/extensions/cognitive_offloading/client.rb +25 -0
- data/lib/legion/extensions/cognitive_offloading/helpers/constants.rb +51 -0
- data/lib/legion/extensions/cognitive_offloading/helpers/external_store.rb +78 -0
- data/lib/legion/extensions/cognitive_offloading/helpers/offloaded_item.rb +61 -0
- data/lib/legion/extensions/cognitive_offloading/helpers/offloading_engine.rb +134 -0
- data/lib/legion/extensions/cognitive_offloading/runners/cognitive_offloading.rb +98 -0
- data/lib/legion/extensions/cognitive_offloading/version.rb +9 -0
- data/lib/legion/extensions/cognitive_offloading.rb +16 -0
- data/spec/legion/extensions/cognitive_offloading/client_spec.rb +30 -0
- data/spec/legion/extensions/cognitive_offloading/helpers/constants_spec.rb +71 -0
- data/spec/legion/extensions/cognitive_offloading/helpers/external_store_spec.rb +158 -0
- data/spec/legion/extensions/cognitive_offloading/helpers/offloaded_item_spec.rb +123 -0
- data/spec/legion/extensions/cognitive_offloading/helpers/offloading_engine_spec.rb +250 -0
- data/spec/legion/extensions/cognitive_offloading/runners/cognitive_offloading_spec.rb +194 -0
- data/spec/spec_helper.rb +20 -0
- metadata +78 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::ExternalStore do
|
|
4
|
+
let(:store) { described_class.new(name: 'notes_db', store_type: :database) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a UUID id' do
|
|
8
|
+
expect(store.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores name' do
|
|
12
|
+
expect(store.name).to eq('notes_db')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'stores store_type' do
|
|
16
|
+
expect(store.store_type).to eq(:database)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults trust to DEFAULT_STORE_TRUST (0.7)' do
|
|
20
|
+
expect(store.trust).to eq(0.7)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'initializes items_stored to 0' do
|
|
24
|
+
expect(store.items_stored).to eq(0)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'initializes successful_retrievals to 0' do
|
|
28
|
+
expect(store.successful_retrievals).to eq(0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'initializes failed_retrievals to 0' do
|
|
32
|
+
expect(store.failed_retrievals).to eq(0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'records created_at as a Time' do
|
|
36
|
+
expect(store.created_at).to be_a(Time)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe '#increment_items!' do
|
|
41
|
+
it 'increments items_stored' do
|
|
42
|
+
store.increment_items!
|
|
43
|
+
expect(store.items_stored).to eq(1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns self for chaining' do
|
|
47
|
+
expect(store.increment_items!).to eq(store)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe '#record_success!' do
|
|
52
|
+
it 'increments successful_retrievals' do
|
|
53
|
+
store.record_success!
|
|
54
|
+
expect(store.successful_retrievals).to eq(1)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'boosts trust by TRUST_BOOST' do
|
|
58
|
+
trust_before = store.trust
|
|
59
|
+
store.record_success!
|
|
60
|
+
expect(store.trust).to be > trust_before
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'does not exceed trust of 1.0' do
|
|
64
|
+
10.times { store.record_success! }
|
|
65
|
+
expect(store.trust).to be <= 1.0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'returns self for chaining' do
|
|
69
|
+
expect(store.record_success!).to eq(store)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#record_failure!' do
|
|
74
|
+
it 'increments failed_retrievals' do
|
|
75
|
+
store.record_failure!
|
|
76
|
+
expect(store.failed_retrievals).to eq(1)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'decays trust by TRUST_DECAY' do
|
|
80
|
+
trust_before = store.trust
|
|
81
|
+
store.record_failure!
|
|
82
|
+
expect(store.trust).to be < trust_before
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'does not drop trust below 0.0' do
|
|
86
|
+
20.times { store.record_failure! }
|
|
87
|
+
expect(store.trust).to be >= 0.0
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns self for chaining' do
|
|
91
|
+
expect(store.record_failure!).to eq(store)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#retrieval_rate' do
|
|
96
|
+
it 'returns 0.0 with no retrievals' do
|
|
97
|
+
expect(store.retrieval_rate).to eq(0.0)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns 1.0 when all retrievals succeed' do
|
|
101
|
+
3.times { store.record_success! }
|
|
102
|
+
expect(store.retrieval_rate).to eq(1.0)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'returns 0.5 for equal success/failure' do
|
|
106
|
+
store.record_success!
|
|
107
|
+
store.record_failure!
|
|
108
|
+
expect(store.retrieval_rate).to eq(0.5)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'rounds to 10 decimal places' do
|
|
112
|
+
2.times { store.record_success! }
|
|
113
|
+
store.record_failure!
|
|
114
|
+
expect(store.retrieval_rate).to eq((2.0 / 3).round(10))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '#reliable?' do
|
|
119
|
+
it 'returns true when trust >= 0.7' do
|
|
120
|
+
expect(store.reliable?).to be true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'returns false when trust drops below 0.7' do
|
|
124
|
+
4.times { store.record_failure! }
|
|
125
|
+
expect(store.reliable?).to be false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe '#trust_label' do
|
|
130
|
+
it 'returns :trusted for default trust of 0.7' do
|
|
131
|
+
expect(store.trust_label).to eq(:trusted)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'returns :highly_trusted after many successes' do
|
|
135
|
+
5.times { store.record_success! }
|
|
136
|
+
expect(store.trust_label).to eq(:highly_trusted)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'returns :unreliable after many failures' do
|
|
140
|
+
30.times { store.record_failure! }
|
|
141
|
+
expect(store.trust_label).to eq(:unreliable)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe '#to_h' do
|
|
146
|
+
it 'returns a hash with expected keys' do
|
|
147
|
+
h = store.to_h
|
|
148
|
+
expect(h).to include(:id, :name, :store_type, :trust, :trust_label, :items_stored,
|
|
149
|
+
:successful_retrievals, :failed_retrievals, :retrieval_rate,
|
|
150
|
+
:reliable, :created_at)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'reflects current trust value' do
|
|
154
|
+
store.record_failure!
|
|
155
|
+
expect(store.to_h[:trust]).to eq(store.trust)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::OffloadedItem do
|
|
4
|
+
let(:store_id) { SecureRandom.uuid }
|
|
5
|
+
let(:item) do
|
|
6
|
+
described_class.new(
|
|
7
|
+
content: 'The capital of France is Paris',
|
|
8
|
+
item_type: :fact,
|
|
9
|
+
importance: 0.8,
|
|
10
|
+
store_id: store_id
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#initialize' do
|
|
15
|
+
it 'assigns a UUID id' do
|
|
16
|
+
expect(item.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'stores content' do
|
|
20
|
+
expect(item.content).to eq('The capital of France is Paris')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'stores item_type' do
|
|
24
|
+
expect(item.item_type).to eq(:fact)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'stores importance' do
|
|
28
|
+
expect(item.importance).to eq(0.8)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'stores store_id' do
|
|
32
|
+
expect(item.store_id).to eq(store_id)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'clamps importance above 1.0 to 1.0' do
|
|
36
|
+
overimportant = described_class.new(content: 'x', item_type: :fact, importance: 1.5, store_id: store_id)
|
|
37
|
+
expect(overimportant.importance).to eq(1.0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'clamps importance below 0.0 to 0.0' do
|
|
41
|
+
negative = described_class.new(content: 'x', item_type: :fact, importance: -0.5, store_id: store_id)
|
|
42
|
+
expect(negative.importance).to eq(0.0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'initializes retrieved_count to 0' do
|
|
46
|
+
expect(item.retrieved_count).to eq(0)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'initializes last_retrieved_at to nil' do
|
|
50
|
+
expect(item.last_retrieved_at).to be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'records offloaded_at as a Time' do
|
|
54
|
+
expect(item.offloaded_at).to be_a(Time)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#retrieve!' do
|
|
59
|
+
it 'increments retrieved_count' do
|
|
60
|
+
item.retrieve!
|
|
61
|
+
expect(item.retrieved_count).to eq(1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'updates last_retrieved_at' do
|
|
65
|
+
item.retrieve!
|
|
66
|
+
expect(item.last_retrieved_at).to be_a(Time)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'returns self for chaining' do
|
|
70
|
+
expect(item.retrieve!).to eq(item)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'accumulates multiple retrievals' do
|
|
74
|
+
3.times { item.retrieve! }
|
|
75
|
+
expect(item.retrieved_count).to eq(3)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '#stale?' do
|
|
80
|
+
it 'returns false for newly offloaded item with no retrievals' do
|
|
81
|
+
expect(item.stale?(threshold_seconds: 3600)).to be false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'returns true when last retrieved time exceeds threshold' do
|
|
85
|
+
item.retrieve!
|
|
86
|
+
item.instance_variable_set(:@last_retrieved_at, Time.now.utc - 7200)
|
|
87
|
+
expect(item.stale?(threshold_seconds: 3600)).to be true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns false when within threshold' do
|
|
91
|
+
item.retrieve!
|
|
92
|
+
expect(item.stale?(threshold_seconds: 3600)).to be false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe '#importance_label' do
|
|
97
|
+
it 'returns :critical for high importance' do
|
|
98
|
+
expect(item.importance_label).to eq(:critical)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'returns :trivial for very low importance' do
|
|
102
|
+
trivial = described_class.new(content: 'x', item_type: :fact, importance: 0.1, store_id: store_id)
|
|
103
|
+
expect(trivial.importance_label).to eq(:trivial)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'returns :moderate for mid-range importance' do
|
|
107
|
+
moderate = described_class.new(content: 'x', item_type: :fact, importance: 0.5, store_id: store_id)
|
|
108
|
+
expect(moderate.importance_label).to eq(:moderate)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe '#to_h' do
|
|
113
|
+
it 'returns a hash with expected keys' do
|
|
114
|
+
h = item.to_h
|
|
115
|
+
expect(h).to include(:id, :content, :item_type, :importance, :importance_label,
|
|
116
|
+
:store_id, :offloaded_at, :retrieved_count, :last_retrieved_at)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'rounds importance to 10 decimal places' do
|
|
120
|
+
expect(item.to_h[:importance]).to eq(item.importance.round(10))
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::OffloadingEngine do
|
|
4
|
+
let(:engine) { described_class.new }
|
|
5
|
+
let(:store) { engine.register_store(name: 'my_store', store_type: :database) }
|
|
6
|
+
|
|
7
|
+
describe '#register_store' do
|
|
8
|
+
it 'creates and returns a store' do
|
|
9
|
+
expect(store).to be_a(Legion::Extensions::CognitiveOffloading::Helpers::ExternalStore)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'adds the store to @stores' do
|
|
13
|
+
store
|
|
14
|
+
expect(engine.stores.size).to eq(1)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'returns nil when at MAX_STORES limit' do
|
|
18
|
+
stub_const('Legion::Extensions::CognitiveOffloading::Helpers::Constants::MAX_STORES', 1)
|
|
19
|
+
engine.register_store(name: 'first', store_type: :file)
|
|
20
|
+
second = engine.register_store(name: 'second', store_type: :file)
|
|
21
|
+
expect(second).to be_nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#offload' do
|
|
26
|
+
it 'creates and returns an offloaded item' do
|
|
27
|
+
item = engine.offload(content: 'remember this', item_type: :fact, importance: 0.6, store_id: store.id)
|
|
28
|
+
expect(item).to be_a(Legion::Extensions::CognitiveOffloading::Helpers::OffloadedItem)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'adds item to @items' do
|
|
32
|
+
engine.offload(content: 'remember this', item_type: :fact, importance: 0.6, store_id: store.id)
|
|
33
|
+
expect(engine.items.size).to eq(1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'increments items_stored on the store' do
|
|
37
|
+
engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
38
|
+
expect(store.items_stored).to eq(1)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'returns nil for unknown store_id' do
|
|
42
|
+
item = engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: 'bad-id')
|
|
43
|
+
expect(item).to be_nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns nil when at MAX_ITEMS limit' do
|
|
47
|
+
stub_const('Legion::Extensions::CognitiveOffloading::Helpers::Constants::MAX_ITEMS', 1)
|
|
48
|
+
engine.offload(content: 'first', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
49
|
+
second = engine.offload(content: 'second', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
50
|
+
expect(second).to be_nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#retrieve' do
|
|
55
|
+
let!(:item) { engine.offload(content: 'test', item_type: :reminder, importance: 0.4, store_id: store.id) }
|
|
56
|
+
|
|
57
|
+
it 'returns the item' do
|
|
58
|
+
retrieved = engine.retrieve(item_id: item.id)
|
|
59
|
+
expect(retrieved).to eq(item)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'increments item retrieved_count' do
|
|
63
|
+
engine.retrieve(item_id: item.id)
|
|
64
|
+
expect(item.retrieved_count).to eq(1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'records success on the store' do
|
|
68
|
+
engine.retrieve(item_id: item.id)
|
|
69
|
+
expect(store.successful_retrievals).to eq(1)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'returns nil for unknown item_id' do
|
|
73
|
+
expect(engine.retrieve(item_id: 'no-such-item')).to be_nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#retrieve_failed' do
|
|
78
|
+
let!(:item) { engine.offload(content: 'test', item_type: :fact, importance: 0.5, store_id: store.id) }
|
|
79
|
+
|
|
80
|
+
it 'returns the item' do
|
|
81
|
+
expect(engine.retrieve_failed(item_id: item.id)).to eq(item)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'records failure on the store' do
|
|
85
|
+
engine.retrieve_failed(item_id: item.id)
|
|
86
|
+
expect(store.failed_retrievals).to eq(1)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'decays store trust' do
|
|
90
|
+
trust_before = store.trust
|
|
91
|
+
engine.retrieve_failed(item_id: item.id)
|
|
92
|
+
expect(store.trust).to be < trust_before
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns nil for unknown item_id' do
|
|
96
|
+
expect(engine.retrieve_failed(item_id: 'ghost')).to be_nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#items_in_store' do
|
|
101
|
+
it 'returns items belonging to the given store' do
|
|
102
|
+
engine.offload(content: 'a', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
103
|
+
engine.offload(content: 'b', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
104
|
+
other_store = engine.register_store(name: 'other', store_type: :file)
|
|
105
|
+
engine.offload(content: 'c', item_type: :fact, importance: 0.5, store_id: other_store.id)
|
|
106
|
+
|
|
107
|
+
items = engine.items_in_store(store_id: store.id)
|
|
108
|
+
expect(items.size).to eq(2)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'returns empty array for store with no items' do
|
|
112
|
+
expect(engine.items_in_store(store_id: store.id)).to eq([])
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe '#items_by_type' do
|
|
117
|
+
before do
|
|
118
|
+
engine.offload(content: 'a', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
119
|
+
engine.offload(content: 'b', item_type: :fact, importance: 0.6, store_id: store.id)
|
|
120
|
+
engine.offload(content: 'c', item_type: :plan, importance: 0.7, store_id: store.id)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'returns items of the given type' do
|
|
124
|
+
expect(engine.items_by_type(item_type: :fact).size).to eq(2)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'returns empty array for unused type' do
|
|
128
|
+
expect(engine.items_by_type(item_type: :calculation)).to eq([])
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe '#most_important_offloaded' do
|
|
133
|
+
before do
|
|
134
|
+
engine.offload(content: 'low', item_type: :fact, importance: 0.2, store_id: store.id)
|
|
135
|
+
engine.offload(content: 'high', item_type: :fact, importance: 0.9, store_id: store.id)
|
|
136
|
+
engine.offload(content: 'mid', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'returns items sorted by importance descending' do
|
|
140
|
+
items = engine.most_important_offloaded(limit: 3)
|
|
141
|
+
importances = items.map(&:importance)
|
|
142
|
+
expect(importances).to eq(importances.sort.reverse)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'respects limit' do
|
|
146
|
+
items = engine.most_important_offloaded(limit: 2)
|
|
147
|
+
expect(items.size).to eq(2)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'returns the highest importance item first' do
|
|
151
|
+
items = engine.most_important_offloaded(limit: 1)
|
|
152
|
+
expect(items.first.importance).to eq(0.9)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe '#offloading_ratio' do
|
|
157
|
+
it 'returns 0.0 with no items' do
|
|
158
|
+
expect(engine.offloading_ratio).to eq(0.0)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'increases as items are added' do
|
|
162
|
+
engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
163
|
+
expect(engine.offloading_ratio).to be > 0.0
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe '#overall_store_trust' do
|
|
168
|
+
it 'returns 0.0 with no stores' do
|
|
169
|
+
expect(described_class.new.overall_store_trust).to eq(0.0)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'returns average trust across stores' do
|
|
173
|
+
s1 = engine.register_store(name: 's1', store_type: :file)
|
|
174
|
+
s2 = engine.register_store(name: 's2', store_type: :notes)
|
|
175
|
+
s1.record_failure!
|
|
176
|
+
expected = ((s1.trust + s2.trust + store.trust) / 3.0).round(10)
|
|
177
|
+
expect(engine.overall_store_trust).to eq(expected)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#most_trusted_store' do
|
|
182
|
+
it 'returns nil with no stores' do
|
|
183
|
+
expect(described_class.new.most_trusted_store).to be_nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'returns the store with highest trust' do
|
|
187
|
+
s2 = engine.register_store(name: 's2', store_type: :file)
|
|
188
|
+
s2.record_success!
|
|
189
|
+
expect(engine.most_trusted_store.trust).to be >= store.trust
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
describe '#least_trusted_store' do
|
|
194
|
+
it 'returns nil with no stores' do
|
|
195
|
+
expect(described_class.new.least_trusted_store).to be_nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'returns the store with lowest trust' do
|
|
199
|
+
s2 = engine.register_store(name: 's2', store_type: :file)
|
|
200
|
+
3.times { s2.record_failure! }
|
|
201
|
+
expect(engine.least_trusted_store.trust).to be <= store.trust
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe '#offloading_report' do
|
|
206
|
+
before do
|
|
207
|
+
engine.offload(content: 'fact one', item_type: :fact, importance: 0.8, store_id: store.id)
|
|
208
|
+
engine.offload(content: 'plan one', item_type: :plan, importance: 0.6, store_id: store.id)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'includes total_items count' do
|
|
212
|
+
expect(engine.offloading_report[:total_items]).to eq(2)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'includes total_stores count' do
|
|
216
|
+
expect(engine.offloading_report[:total_stores]).to eq(1)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'includes offloading_ratio' do
|
|
220
|
+
expect(engine.offloading_report[:offloading_ratio]).to be > 0.0
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it 'includes offloading_label' do
|
|
224
|
+
expect(engine.offloading_report[:offloading_label]).to be_a(Symbol)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'includes overall_store_trust' do
|
|
228
|
+
expect(engine.offloading_report[:overall_store_trust]).to be_a(Float)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'includes stores_summary array' do
|
|
232
|
+
expect(engine.offloading_report[:stores_summary]).to be_an(Array)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'includes items_by_type breakdown' do
|
|
236
|
+
breakdown = engine.offloading_report[:items_by_type]
|
|
237
|
+
expect(breakdown[:fact]).to eq(1)
|
|
238
|
+
expect(breakdown[:plan]).to eq(1)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe '#to_h' do
|
|
243
|
+
it 'returns items and stores as hashes' do
|
|
244
|
+
engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
|
|
245
|
+
h = engine.to_h
|
|
246
|
+
expect(h).to have_key(:items)
|
|
247
|
+
expect(h).to have_key(:stores)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|