lex-prediction 0.1.1
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-prediction.gemspec +29 -0
- data/lib/legion/extensions/prediction/actors/expire_predictions.rb +41 -0
- data/lib/legion/extensions/prediction/client.rb +23 -0
- data/lib/legion/extensions/prediction/helpers/modes.rb +24 -0
- data/lib/legion/extensions/prediction/helpers/prediction_store.rb +62 -0
- data/lib/legion/extensions/prediction/runners/prediction.rb +142 -0
- data/lib/legion/extensions/prediction/version.rb +9 -0
- data/lib/legion/extensions/prediction.rb +14 -0
- data/spec/legion/extensions/prediction/actors/expire_predictions_spec.rb +46 -0
- data/spec/legion/extensions/prediction/client_spec.rb +14 -0
- data/spec/legion/extensions/prediction/helpers/modes_spec.rb +118 -0
- data/spec/legion/extensions/prediction/helpers/prediction_store_spec.rb +262 -0
- data/spec/legion/extensions/prediction/runners/prediction_spec.rb +116 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 40883318ee6a41596e8b2693ba9c154d2191b8566ad39fea5d45c00a4a87d2de
|
|
4
|
+
data.tar.gz: f75fffc950c49b188aa8904d560e225c70965dd27c8f83874dbf6f7879e7ea0d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a49a26d11921d15af3613124205478263fc836ca7ce58adcf4506e4de69c3762fe748bfbb6a9a232c2a218bc114891fde42632a2ca623c0ddf3931bd2b864f64
|
|
7
|
+
data.tar.gz: 2fb9dfe8318de27742d9a67f17145abe63a2b069ef9eeef0be776069dfa672233848d76eddab624c750467bd8e99fb446c154821351fc4b0f6be78677b95079c
|
data/Gemfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/prediction/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-prediction'
|
|
7
|
+
spec.version = Legion::Extensions::Prediction::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Prediction'
|
|
12
|
+
spec.description = 'Forward-model prediction engine (4 reasoning modes) for brain-modeled agentic AI'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-prediction'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-prediction'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-prediction'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-prediction'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-prediction/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-prediction.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Prediction
|
|
8
|
+
module Actor
|
|
9
|
+
class ExpirePredictions < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::Prediction::Runners::Prediction
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'expire_stale_predictions'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
300
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run_now?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_runner?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_subtask?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_task?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/prediction/helpers/modes'
|
|
4
|
+
require 'legion/extensions/prediction/helpers/prediction_store'
|
|
5
|
+
require 'legion/extensions/prediction/runners/prediction'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Prediction
|
|
10
|
+
class Client
|
|
11
|
+
include Runners::Prediction
|
|
12
|
+
|
|
13
|
+
def initialize(**)
|
|
14
|
+
@prediction_store = Helpers::PredictionStore.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :prediction_store
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Prediction
|
|
6
|
+
module Helpers
|
|
7
|
+
module Modes
|
|
8
|
+
# Four reasoning modes (spec: prediction-engine-spec.md)
|
|
9
|
+
REASONING_MODES = %i[fault_localization functional_mapping boundary_testing counterfactual].freeze
|
|
10
|
+
|
|
11
|
+
PREDICTION_CONFIDENCE_MIN = 0.65
|
|
12
|
+
MAX_PREDICTIONS_PER_TICK = 5
|
|
13
|
+
PREDICTION_HORIZON = 3600 # 1 hour default lookahead
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def valid_mode?(mode)
|
|
18
|
+
REASONING_MODES.include?(mode)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Prediction
|
|
8
|
+
module Helpers
|
|
9
|
+
class PredictionStore
|
|
10
|
+
attr_reader :predictions, :outcomes
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@predictions = {}
|
|
14
|
+
@outcomes = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def store(prediction)
|
|
18
|
+
id = prediction[:prediction_id] || SecureRandom.uuid
|
|
19
|
+
prediction[:prediction_id] = id
|
|
20
|
+
prediction[:created_at] ||= Time.now.utc
|
|
21
|
+
prediction[:status] ||= :pending
|
|
22
|
+
@predictions[id] = prediction
|
|
23
|
+
id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get(prediction_id)
|
|
27
|
+
@predictions[prediction_id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def resolve(prediction_id, outcome:, actual: nil)
|
|
31
|
+
pred = @predictions[prediction_id]
|
|
32
|
+
return nil unless pred
|
|
33
|
+
|
|
34
|
+
pred[:status] = outcome # :correct, :incorrect, :partial, :expired
|
|
35
|
+
pred[:resolved_at] = Time.now.utc
|
|
36
|
+
pred[:actual] = actual
|
|
37
|
+
|
|
38
|
+
@outcomes << { prediction_id: prediction_id, outcome: outcome, at: Time.now.utc }
|
|
39
|
+
@outcomes.shift while @outcomes.size > 500
|
|
40
|
+
pred
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def pending
|
|
44
|
+
@predictions.values.select { |p| p[:status] == :pending }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def accuracy(window: 100)
|
|
48
|
+
recent = @outcomes.last(window)
|
|
49
|
+
return 0.0 if recent.empty?
|
|
50
|
+
|
|
51
|
+
correct = recent.count { |o| o[:outcome] == :correct }
|
|
52
|
+
correct.to_f / recent.size
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def count
|
|
56
|
+
@predictions.size
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Prediction
|
|
8
|
+
module Runners
|
|
9
|
+
module Prediction
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def predict(mode:, context: {}, confidence: nil, description: nil, **)
|
|
14
|
+
return { error: :invalid_mode, valid_modes: Helpers::Modes::REASONING_MODES } unless Helpers::Modes.valid_mode?(mode)
|
|
15
|
+
|
|
16
|
+
prediction = {
|
|
17
|
+
prediction_id: SecureRandom.uuid,
|
|
18
|
+
mode: mode,
|
|
19
|
+
context: context,
|
|
20
|
+
confidence: confidence || estimate_confidence(mode, context),
|
|
21
|
+
description: description,
|
|
22
|
+
status: :pending,
|
|
23
|
+
created_at: Time.now.utc,
|
|
24
|
+
horizon: Helpers::Modes::PREDICTION_HORIZON
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
prediction_store.store(prediction)
|
|
28
|
+
|
|
29
|
+
actionable = prediction[:confidence] >= Helpers::Modes::PREDICTION_CONFIDENCE_MIN
|
|
30
|
+
Legion::Logging.debug "[prediction] new: mode=#{mode} confidence=#{prediction[:confidence].round(2)} " \
|
|
31
|
+
"actionable=#{actionable} id=#{prediction[:prediction_id][0..7]}"
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
prediction_id: prediction[:prediction_id],
|
|
35
|
+
mode: mode,
|
|
36
|
+
confidence: prediction[:confidence],
|
|
37
|
+
actionable: actionable
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_prediction(prediction_id:, outcome:, actual: nil, **)
|
|
42
|
+
pred = prediction_store.resolve(prediction_id, outcome: outcome, actual: actual)
|
|
43
|
+
if pred
|
|
44
|
+
Legion::Logging.info "[prediction] resolved #{prediction_id[0..7]} outcome=#{outcome}"
|
|
45
|
+
record_outcome_trace(pred, outcome)
|
|
46
|
+
{ resolved: true, prediction_id: prediction_id, outcome: outcome }
|
|
47
|
+
else
|
|
48
|
+
Legion::Logging.debug "[prediction] resolve failed: #{prediction_id[0..7]} not found"
|
|
49
|
+
{ resolved: false, reason: :not_found }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pending_predictions(**)
|
|
54
|
+
preds = prediction_store.pending
|
|
55
|
+
Legion::Logging.debug "[prediction] pending count=#{preds.size}"
|
|
56
|
+
{ predictions: preds, count: preds.size }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def prediction_accuracy(window: 100, **)
|
|
60
|
+
acc = prediction_store.accuracy(window: window)
|
|
61
|
+
total = prediction_store.outcomes.size
|
|
62
|
+
Legion::Logging.debug "[prediction] accuracy=#{acc.round(2)} total_outcomes=#{total}"
|
|
63
|
+
{ accuracy: acc, total_outcomes: total }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def expire_stale_predictions(**)
|
|
67
|
+
expired_count = 0
|
|
68
|
+
|
|
69
|
+
prediction_store.pending.each do |pred|
|
|
70
|
+
age = Time.now.utc - pred[:created_at]
|
|
71
|
+
next unless age > pred[:horizon]
|
|
72
|
+
|
|
73
|
+
prediction_store.resolve(pred[:prediction_id], outcome: :expired, actual: nil)
|
|
74
|
+
expired_count += 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
remaining = prediction_store.pending.size
|
|
78
|
+
Legion::Logging.debug "[prediction] expire sweep: expired=#{expired_count} remaining=#{remaining}"
|
|
79
|
+
|
|
80
|
+
{ expired_count: expired_count, remaining_pending: remaining }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def get_prediction(prediction_id:, **)
|
|
84
|
+
pred = prediction_store.get(prediction_id)
|
|
85
|
+
pred ? { found: true, prediction: pred } : { found: false }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def prediction_store
|
|
91
|
+
@prediction_store ||= Helpers::PredictionStore.new
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def estimate_confidence(mode, context)
|
|
95
|
+
base = case mode
|
|
96
|
+
when :fault_localization then 0.7
|
|
97
|
+
when :functional_mapping then 0.6
|
|
98
|
+
when :counterfactual then 0.4
|
|
99
|
+
else 0.5
|
|
100
|
+
end
|
|
101
|
+
richness_bonus = [context.size * 0.02, 0.2].min
|
|
102
|
+
[base + richness_bonus, 1.0].min
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def record_outcome_trace(prediction, outcome)
|
|
106
|
+
return unless defined?(Legion::Extensions::Memory::Runners::Traces)
|
|
107
|
+
|
|
108
|
+
trace_params = case outcome
|
|
109
|
+
when :correct
|
|
110
|
+
{ type: :semantic, valence: 0.3, intensity: 0.3, unresolved: false }
|
|
111
|
+
when :incorrect
|
|
112
|
+
{ type: :episodic, valence: -0.5, intensity: 0.6, unresolved: true }
|
|
113
|
+
when :partial
|
|
114
|
+
{ type: :episodic, valence: -0.2, intensity: 0.4, unresolved: true }
|
|
115
|
+
else
|
|
116
|
+
return
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
runner = Object.new.extend(Legion::Extensions::Memory::Runners::Traces)
|
|
120
|
+
runner.store_trace(
|
|
121
|
+
type: trace_params[:type],
|
|
122
|
+
content_payload: "prediction #{outcome}: mode=#{prediction[:mode]} confidence=#{prediction[:confidence]}",
|
|
123
|
+
domain_tags: ['prediction', prediction[:mode].to_s],
|
|
124
|
+
origin: :direct_experience,
|
|
125
|
+
emotional_valence: trace_params[:valence],
|
|
126
|
+
emotional_intensity: trace_params[:intensity],
|
|
127
|
+
unresolved: trace_params[:unresolved],
|
|
128
|
+
confidence: prediction[:confidence]
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
store = runner.send(:default_store)
|
|
132
|
+
store.flush if store.respond_to?(:flush)
|
|
133
|
+
|
|
134
|
+
Legion::Logging.debug "[prediction] created #{trace_params[:type]} trace for #{outcome} prediction"
|
|
135
|
+
rescue StandardError => e
|
|
136
|
+
Legion::Logging.warn "[prediction] failed to create outcome trace: #{e.message}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/prediction/version'
|
|
4
|
+
require 'legion/extensions/prediction/helpers/modes'
|
|
5
|
+
require 'legion/extensions/prediction/helpers/prediction_store'
|
|
6
|
+
require 'legion/extensions/prediction/runners/prediction'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Prediction
|
|
11
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Stub the base class before loading the actor
|
|
4
|
+
module Legion
|
|
5
|
+
module Extensions
|
|
6
|
+
module Actors
|
|
7
|
+
class Every; end # rubocop:disable Lint/EmptyClass
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every'
|
|
13
|
+
|
|
14
|
+
require_relative '../../../../../lib/legion/extensions/prediction/actors/expire_predictions'
|
|
15
|
+
|
|
16
|
+
RSpec.describe Legion::Extensions::Prediction::Actor::ExpirePredictions do
|
|
17
|
+
subject(:actor) { described_class.new }
|
|
18
|
+
|
|
19
|
+
describe '#runner_class' do
|
|
20
|
+
it { expect(actor.runner_class).to eq Legion::Extensions::Prediction::Runners::Prediction }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#runner_function' do
|
|
24
|
+
it { expect(actor.runner_function).to eq 'expire_stale_predictions' }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#time' do
|
|
28
|
+
it { expect(actor.time).to eq 300 }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#run_now?' do
|
|
32
|
+
it { expect(actor.run_now?).to be false }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#use_runner?' do
|
|
36
|
+
it { expect(actor.use_runner?).to be false }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#check_subtask?' do
|
|
40
|
+
it { expect(actor.check_subtask?).to be false }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#generate_task?' do
|
|
44
|
+
it { expect(actor.generate_task?).to be false }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/prediction/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Prediction::Client do
|
|
6
|
+
it 'responds to prediction runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:predict)
|
|
9
|
+
expect(client).to respond_to(:resolve_prediction)
|
|
10
|
+
expect(client).to respond_to(:pending_predictions)
|
|
11
|
+
expect(client).to respond_to(:prediction_accuracy)
|
|
12
|
+
expect(client).to respond_to(:get_prediction)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Prediction::Helpers::Modes do
|
|
6
|
+
describe 'REASONING_MODES' do
|
|
7
|
+
it 'is a frozen array of symbols' do
|
|
8
|
+
expect(described_class::REASONING_MODES).to be_a(Array)
|
|
9
|
+
expect(described_class::REASONING_MODES).to be_frozen
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'contains exactly four modes' do
|
|
13
|
+
expect(described_class::REASONING_MODES.size).to eq(4)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'includes fault_localization' do
|
|
17
|
+
expect(described_class::REASONING_MODES).to include(:fault_localization)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'includes functional_mapping' do
|
|
21
|
+
expect(described_class::REASONING_MODES).to include(:functional_mapping)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'includes boundary_testing' do
|
|
25
|
+
expect(described_class::REASONING_MODES).to include(:boundary_testing)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'includes counterfactual' do
|
|
29
|
+
expect(described_class::REASONING_MODES).to include(:counterfactual)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'contains only symbols' do
|
|
33
|
+
expect(described_class::REASONING_MODES).to all(be_a(Symbol))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'PREDICTION_CONFIDENCE_MIN' do
|
|
38
|
+
it 'is 0.65' do
|
|
39
|
+
expect(described_class::PREDICTION_CONFIDENCE_MIN).to eq(0.65)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'is a float' do
|
|
43
|
+
expect(described_class::PREDICTION_CONFIDENCE_MIN).to be_a(Float)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'is in the valid 0.0-1.0 range' do
|
|
47
|
+
expect(described_class::PREDICTION_CONFIDENCE_MIN).to be_between(0.0, 1.0)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe 'MAX_PREDICTIONS_PER_TICK' do
|
|
52
|
+
it 'is 5' do
|
|
53
|
+
expect(described_class::MAX_PREDICTIONS_PER_TICK).to eq(5)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'is an integer' do
|
|
57
|
+
expect(described_class::MAX_PREDICTIONS_PER_TICK).to be_an(Integer)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'is positive' do
|
|
61
|
+
expect(described_class::MAX_PREDICTIONS_PER_TICK).to be > 0
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'PREDICTION_HORIZON' do
|
|
66
|
+
it 'is 3600' do
|
|
67
|
+
expect(described_class::PREDICTION_HORIZON).to eq(3600)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'is an integer' do
|
|
71
|
+
expect(described_class::PREDICTION_HORIZON).to be_an(Integer)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'represents one hour in seconds' do
|
|
75
|
+
expect(described_class::PREDICTION_HORIZON).to eq(60 * 60)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '.valid_mode?' do
|
|
80
|
+
it 'returns true for fault_localization' do
|
|
81
|
+
expect(described_class.valid_mode?(:fault_localization)).to be true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'returns true for functional_mapping' do
|
|
85
|
+
expect(described_class.valid_mode?(:functional_mapping)).to be true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'returns true for boundary_testing' do
|
|
89
|
+
expect(described_class.valid_mode?(:boundary_testing)).to be true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'returns true for counterfactual' do
|
|
93
|
+
expect(described_class.valid_mode?(:counterfactual)).to be true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns false for an unknown mode symbol' do
|
|
97
|
+
expect(described_class.valid_mode?(:neural_network)).to be false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'returns false for a string form of a valid mode' do
|
|
101
|
+
expect(described_class.valid_mode?('fault_localization')).to be false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'returns false for nil' do
|
|
105
|
+
expect(described_class.valid_mode?(nil)).to be false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'returns false for an integer' do
|
|
109
|
+
expect(described_class.valid_mode?(42)).to be false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'returns true for all REASONING_MODES members' do
|
|
113
|
+
described_class::REASONING_MODES.each do |mode|
|
|
114
|
+
expect(described_class.valid_mode?(mode)).to be true
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Prediction::Helpers::PredictionStore do
|
|
6
|
+
subject(:store) { described_class.new }
|
|
7
|
+
|
|
8
|
+
let(:basic_prediction) do
|
|
9
|
+
{
|
|
10
|
+
mode: :fault_localization,
|
|
11
|
+
confidence: 0.75,
|
|
12
|
+
description: 'disk latency spike incoming',
|
|
13
|
+
status: :pending
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#initialize' do
|
|
18
|
+
it 'starts with empty predictions hash' do
|
|
19
|
+
expect(store.predictions).to eq({})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'starts with empty outcomes array' do
|
|
23
|
+
expect(store.outcomes).to eq([])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#store' do
|
|
28
|
+
it 'returns a UUID string' do
|
|
29
|
+
id = store.store(basic_prediction.dup)
|
|
30
|
+
expect(id).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'stores the prediction under the returned id' do
|
|
34
|
+
id = store.store(basic_prediction.dup)
|
|
35
|
+
expect(store.predictions[id]).not_to be_nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'sets prediction_id on the prediction hash' do
|
|
39
|
+
prediction = basic_prediction.dup
|
|
40
|
+
id = store.store(prediction)
|
|
41
|
+
expect(prediction[:prediction_id]).to eq(id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'preserves a caller-supplied prediction_id' do
|
|
45
|
+
prediction = basic_prediction.merge(prediction_id: 'my-custom-id')
|
|
46
|
+
returned_id = store.store(prediction)
|
|
47
|
+
expect(returned_id).to eq('my-custom-id')
|
|
48
|
+
expect(store.predictions['my-custom-id']).not_to be_nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'sets created_at when not supplied' do
|
|
52
|
+
prediction = basic_prediction.dup
|
|
53
|
+
before = Time.now.utc
|
|
54
|
+
store.store(prediction)
|
|
55
|
+
expect(prediction[:created_at]).to be >= before
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'preserves a caller-supplied created_at' do
|
|
59
|
+
custom_time = Time.now.utc - 3600
|
|
60
|
+
prediction = basic_prediction.merge(created_at: custom_time)
|
|
61
|
+
store.store(prediction)
|
|
62
|
+
expect(prediction[:created_at]).to eq(custom_time)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'sets default status to :pending' do
|
|
66
|
+
prediction = { mode: :counterfactual, confidence: 0.5 }
|
|
67
|
+
store.store(prediction)
|
|
68
|
+
expect(prediction[:status]).to eq(:pending)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'preserves a caller-supplied status' do
|
|
72
|
+
prediction = basic_prediction.merge(status: :expired)
|
|
73
|
+
store.store(prediction)
|
|
74
|
+
expect(prediction[:status]).to eq(:expired)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'increments count with each stored prediction' do
|
|
78
|
+
3.times { store.store(basic_prediction.dup) }
|
|
79
|
+
expect(store.count).to eq(3)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe '#get' do
|
|
84
|
+
it 'retrieves a stored prediction by id' do
|
|
85
|
+
id = store.store(basic_prediction.dup)
|
|
86
|
+
result = store.get(id)
|
|
87
|
+
expect(result[:mode]).to eq(:fault_localization)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns nil for an unknown id' do
|
|
91
|
+
expect(store.get('no-such-id')).to be_nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#resolve' do
|
|
96
|
+
let!(:prediction_id) { store.store(basic_prediction.dup) }
|
|
97
|
+
|
|
98
|
+
it 'returns the updated prediction' do
|
|
99
|
+
result = store.resolve(prediction_id, outcome: :correct)
|
|
100
|
+
expect(result).to be_a(Hash)
|
|
101
|
+
expect(result[:prediction_id]).to eq(prediction_id)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'sets status to the given outcome' do
|
|
105
|
+
store.resolve(prediction_id, outcome: :incorrect)
|
|
106
|
+
expect(store.get(prediction_id)[:status]).to eq(:incorrect)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'sets resolved_at timestamp' do
|
|
110
|
+
before = Time.now.utc
|
|
111
|
+
store.resolve(prediction_id, outcome: :correct)
|
|
112
|
+
expect(store.get(prediction_id)[:resolved_at]).to be >= before
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'stores actual value when provided' do
|
|
116
|
+
store.resolve(prediction_id, outcome: :partial, actual: 'disk io at 80%')
|
|
117
|
+
expect(store.get(prediction_id)[:actual]).to eq('disk io at 80%')
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'stores nil actual when not provided' do
|
|
121
|
+
store.resolve(prediction_id, outcome: :correct)
|
|
122
|
+
expect(store.get(prediction_id)[:actual]).to be_nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'appends to outcomes array' do
|
|
126
|
+
store.resolve(prediction_id, outcome: :correct)
|
|
127
|
+
expect(store.outcomes.size).to eq(1)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'records prediction_id in the outcome entry' do
|
|
131
|
+
store.resolve(prediction_id, outcome: :correct)
|
|
132
|
+
expect(store.outcomes.last[:prediction_id]).to eq(prediction_id)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'records outcome in the outcome entry' do
|
|
136
|
+
store.resolve(prediction_id, outcome: :incorrect)
|
|
137
|
+
expect(store.outcomes.last[:outcome]).to eq(:incorrect)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'returns nil for a non-existent prediction_id' do
|
|
141
|
+
result = store.resolve('ghost-id', outcome: :correct)
|
|
142
|
+
expect(result).to be_nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'caps outcomes array at 500 entries' do
|
|
146
|
+
501.times do
|
|
147
|
+
id = store.store(basic_prediction.dup)
|
|
148
|
+
store.resolve(id, outcome: :correct)
|
|
149
|
+
end
|
|
150
|
+
expect(store.outcomes.size).to eq(500)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe '#pending' do
|
|
155
|
+
it 'returns only predictions with status :pending' do
|
|
156
|
+
id1 = store.store(basic_prediction.dup)
|
|
157
|
+
id2 = store.store(basic_prediction.dup)
|
|
158
|
+
store.resolve(id1, outcome: :correct)
|
|
159
|
+
|
|
160
|
+
result = store.pending
|
|
161
|
+
expect(result.size).to eq(1)
|
|
162
|
+
expect(result.first[:prediction_id]).to eq(id2)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'returns empty array when no pending predictions' do
|
|
166
|
+
id = store.store(basic_prediction.dup)
|
|
167
|
+
store.resolve(id, outcome: :correct)
|
|
168
|
+
expect(store.pending).to be_empty
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'returns all stored predictions when none are resolved' do
|
|
172
|
+
3.times { store.store(basic_prediction.dup) }
|
|
173
|
+
expect(store.pending.size).to eq(3)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe '#accuracy' do
|
|
178
|
+
it 'returns 0.0 when no outcomes exist' do
|
|
179
|
+
expect(store.accuracy).to eq(0.0)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'returns 1.0 when all outcomes are :correct' do
|
|
183
|
+
3.times do
|
|
184
|
+
id = store.store(basic_prediction.dup)
|
|
185
|
+
store.resolve(id, outcome: :correct)
|
|
186
|
+
end
|
|
187
|
+
expect(store.accuracy).to eq(1.0)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'returns 0.0 when all outcomes are :incorrect' do
|
|
191
|
+
3.times do
|
|
192
|
+
id = store.store(basic_prediction.dup)
|
|
193
|
+
store.resolve(id, outcome: :incorrect)
|
|
194
|
+
end
|
|
195
|
+
expect(store.accuracy).to eq(0.0)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'computes fractional accuracy correctly' do
|
|
199
|
+
3.times do
|
|
200
|
+
id = store.store(basic_prediction.dup)
|
|
201
|
+
store.resolve(id, outcome: :correct)
|
|
202
|
+
end
|
|
203
|
+
id = store.store(basic_prediction.dup)
|
|
204
|
+
store.resolve(id, outcome: :incorrect)
|
|
205
|
+
expect(store.accuracy).to eq(0.75)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'defaults window to 100' do
|
|
209
|
+
102.times do
|
|
210
|
+
id = store.store(basic_prediction.dup)
|
|
211
|
+
store.resolve(id, outcome: :incorrect)
|
|
212
|
+
end
|
|
213
|
+
2.times do
|
|
214
|
+
id = store.store(basic_prediction.dup)
|
|
215
|
+
store.resolve(id, outcome: :correct)
|
|
216
|
+
end
|
|
217
|
+
# window=100: last 100 contain 2 correct out of 100 = 0.02
|
|
218
|
+
expect(store.accuracy(window: 100)).to eq(0.02)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it 'accepts a custom window parameter' do
|
|
222
|
+
5.times do
|
|
223
|
+
id = store.store(basic_prediction.dup)
|
|
224
|
+
store.resolve(id, outcome: :incorrect)
|
|
225
|
+
end
|
|
226
|
+
5.times do
|
|
227
|
+
id = store.store(basic_prediction.dup)
|
|
228
|
+
store.resolve(id, outcome: :correct)
|
|
229
|
+
end
|
|
230
|
+
# window=5: last 5 are all :correct
|
|
231
|
+
expect(store.accuracy(window: 5)).to eq(1.0)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it 'ignores non-:correct outcomes in numerator' do
|
|
235
|
+
id = store.store(basic_prediction.dup)
|
|
236
|
+
store.resolve(id, outcome: :partial)
|
|
237
|
+
id = store.store(basic_prediction.dup)
|
|
238
|
+
store.resolve(id, outcome: :expired)
|
|
239
|
+
id = store.store(basic_prediction.dup)
|
|
240
|
+
store.resolve(id, outcome: :correct)
|
|
241
|
+
expect(store.accuracy).to be_within(0.001).of(1.0 / 3.0)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
describe '#count' do
|
|
246
|
+
it 'returns 0 for a new store' do
|
|
247
|
+
expect(store.count).to eq(0)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'returns the total number of stored predictions' do
|
|
251
|
+
5.times { store.store(basic_prediction.dup) }
|
|
252
|
+
expect(store.count).to eq(5)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'counts resolved predictions as well as pending ones' do
|
|
256
|
+
id = store.store(basic_prediction.dup)
|
|
257
|
+
store.resolve(id, outcome: :correct)
|
|
258
|
+
store.store(basic_prediction.dup)
|
|
259
|
+
expect(store.count).to eq(2)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/prediction/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Prediction::Runners::Prediction do
|
|
6
|
+
let(:client) { Legion::Extensions::Prediction::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#predict' do
|
|
9
|
+
it 'creates a prediction with valid mode' do
|
|
10
|
+
result = client.predict(mode: :fault_localization, description: 'test')
|
|
11
|
+
expect(result[:prediction_id]).to match(/\A[0-9a-f-]{36}\z/)
|
|
12
|
+
expect(result[:mode]).to eq(:fault_localization)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'rejects invalid mode' do
|
|
16
|
+
result = client.predict(mode: :invalid)
|
|
17
|
+
expect(result[:error]).to eq(:invalid_mode)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'marks actionable predictions above confidence threshold' do
|
|
21
|
+
result = client.predict(mode: :fault_localization, confidence: 0.9)
|
|
22
|
+
expect(result[:actionable]).to be true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'marks non-actionable predictions below threshold' do
|
|
26
|
+
result = client.predict(mode: :counterfactual, confidence: 0.3)
|
|
27
|
+
expect(result[:actionable]).to be false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'estimates confidence based on mode' do
|
|
31
|
+
fault = client.predict(mode: :fault_localization)
|
|
32
|
+
counterfactual = client.predict(mode: :counterfactual)
|
|
33
|
+
expect(fault[:confidence]).to be > counterfactual[:confidence]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#resolve_prediction' do
|
|
38
|
+
it 'resolves a pending prediction' do
|
|
39
|
+
pred = client.predict(mode: :functional_mapping)
|
|
40
|
+
result = client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :correct)
|
|
41
|
+
expect(result[:resolved]).to be true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns not_found for missing prediction' do
|
|
45
|
+
result = client.resolve_prediction(prediction_id: 'nonexistent', outcome: :correct)
|
|
46
|
+
expect(result[:resolved]).to be false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#pending_predictions' do
|
|
51
|
+
it 'lists pending predictions' do
|
|
52
|
+
client.predict(mode: :fault_localization)
|
|
53
|
+
client.predict(mode: :boundary_testing)
|
|
54
|
+
result = client.pending_predictions
|
|
55
|
+
expect(result[:count]).to eq(2)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#expire_stale_predictions' do
|
|
60
|
+
it 'returns zero expired when no pending predictions' do
|
|
61
|
+
result = client.expire_stale_predictions
|
|
62
|
+
expect(result[:expired_count]).to eq(0)
|
|
63
|
+
expect(result[:remaining_pending]).to eq(0)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'expires predictions older than their horizon' do
|
|
67
|
+
client.predict(mode: :fault_localization)
|
|
68
|
+
client.predict(mode: :boundary_testing)
|
|
69
|
+
|
|
70
|
+
# Simulate staleness by back-dating created_at beyond the horizon
|
|
71
|
+
store = client.send(:prediction_store)
|
|
72
|
+
store.predictions.each_value do |pred|
|
|
73
|
+
pred[:created_at] = Time.now.utc - pred[:horizon] - 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
result = client.expire_stale_predictions
|
|
77
|
+
expect(result[:expired_count]).to eq(2)
|
|
78
|
+
expect(result[:remaining_pending]).to eq(0)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'preserves predictions that have not exceeded their horizon' do
|
|
82
|
+
client.predict(mode: :fault_localization)
|
|
83
|
+
# created_at is just now, horizon is 3600s — not stale
|
|
84
|
+
result = client.expire_stale_predictions
|
|
85
|
+
expect(result[:expired_count]).to eq(0)
|
|
86
|
+
expect(result[:remaining_pending]).to eq(1)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'only expires stale predictions when mixed with fresh ones' do
|
|
90
|
+
client.predict(mode: :fault_localization)
|
|
91
|
+
stale = client.predict(mode: :boundary_testing)
|
|
92
|
+
|
|
93
|
+
store = client.send(:prediction_store)
|
|
94
|
+
stale_pred = store.predictions[stale[:prediction_id]]
|
|
95
|
+
stale_pred[:created_at] = Time.now.utc - stale_pred[:horizon] - 1
|
|
96
|
+
|
|
97
|
+
result = client.expire_stale_predictions
|
|
98
|
+
expect(result[:expired_count]).to eq(1)
|
|
99
|
+
expect(result[:remaining_pending]).to eq(1)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe '#prediction_accuracy' do
|
|
104
|
+
it 'computes accuracy' do
|
|
105
|
+
3.times do
|
|
106
|
+
pred = client.predict(mode: :fault_localization)
|
|
107
|
+
client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :correct)
|
|
108
|
+
end
|
|
109
|
+
pred = client.predict(mode: :fault_localization)
|
|
110
|
+
client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :incorrect)
|
|
111
|
+
|
|
112
|
+
result = client.prediction_accuracy
|
|
113
|
+
expect(result[:accuracy]).to eq(0.75)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
def self.debug(_msg); end
|
|
8
|
+
def self.info(_msg); end
|
|
9
|
+
def self.warn(_msg); end
|
|
10
|
+
def self.error(_msg); end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'legion/extensions/prediction'
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
18
|
+
config.disable_monkey_patching!
|
|
19
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-prediction
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
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: Forward-model prediction engine (4 reasoning modes) for brain-modeled
|
|
27
|
+
agentic AI
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- lex-prediction.gemspec
|
|
36
|
+
- lib/legion/extensions/prediction.rb
|
|
37
|
+
- lib/legion/extensions/prediction/actors/expire_predictions.rb
|
|
38
|
+
- lib/legion/extensions/prediction/client.rb
|
|
39
|
+
- lib/legion/extensions/prediction/helpers/modes.rb
|
|
40
|
+
- lib/legion/extensions/prediction/helpers/prediction_store.rb
|
|
41
|
+
- lib/legion/extensions/prediction/runners/prediction.rb
|
|
42
|
+
- lib/legion/extensions/prediction/version.rb
|
|
43
|
+
- spec/legion/extensions/prediction/actors/expire_predictions_spec.rb
|
|
44
|
+
- spec/legion/extensions/prediction/client_spec.rb
|
|
45
|
+
- spec/legion/extensions/prediction/helpers/modes_spec.rb
|
|
46
|
+
- spec/legion/extensions/prediction/helpers/prediction_store_spec.rb
|
|
47
|
+
- spec/legion/extensions/prediction/runners/prediction_spec.rb
|
|
48
|
+
- spec/spec_helper.rb
|
|
49
|
+
homepage: https://github.com/LegionIO/lex-prediction
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
homepage_uri: https://github.com/LegionIO/lex-prediction
|
|
54
|
+
source_code_uri: https://github.com/LegionIO/lex-prediction
|
|
55
|
+
documentation_uri: https://github.com/LegionIO/lex-prediction
|
|
56
|
+
changelog_uri: https://github.com/LegionIO/lex-prediction
|
|
57
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-prediction/issues
|
|
58
|
+
rubygems_mfa_required: 'true'
|
|
59
|
+
rdoc_options: []
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.4'
|
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubygems_version: 3.6.9
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: LEX Prediction
|
|
76
|
+
test_files: []
|