lex-causal-attribution 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-causal-attribution.gemspec +29 -0
- data/lib/legion/extensions/causal_attribution/client.rb +23 -0
- data/lib/legion/extensions/causal_attribution/helpers/attribution.rb +88 -0
- data/lib/legion/extensions/causal_attribution/helpers/attribution_engine.rb +155 -0
- data/lib/legion/extensions/causal_attribution/runners/causal_attribution.rb +107 -0
- data/lib/legion/extensions/causal_attribution/version.rb +9 -0
- data/lib/legion/extensions/causal_attribution.rb +14 -0
- data/spec/legion/extensions/causal_attribution/client_spec.rb +24 -0
- data/spec/legion/extensions/causal_attribution/helpers/attribution_engine_spec.rb +181 -0
- data/spec/legion/extensions/causal_attribution/helpers/attribution_spec.rb +108 -0
- data/spec/legion/extensions/causal_attribution/runners/causal_attribution_spec.rb +142 -0
- data/spec/spec_helper.rb +20 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 55c6a453c0a0102f9922c5fcd31988701ff6d3a683ecaa84512165532caeb10a
|
|
4
|
+
data.tar.gz: ed1593cdba918561fcbb2ac9c93a994d42386afe628c89f5079b3ebd790fef67
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c6bded9b8ef506d179343b15c256978246aa371c900f0e366030355bdd73c8d9462f5712114c49c96aab62c2c92342f03981b8f935bb86ec729cb8dfe07a4734
|
|
7
|
+
data.tar.gz: e01f123090aab3668fe2d80090d0417cc1a3889b10acde2e0802bc79aea2b295d38d75fa5b4a49a7e7febbfd41c0837a1ab7945a7aa4695a1ab5afbb2a36023b
|
data/Gemfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/causal_attribution/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-causal-attribution'
|
|
7
|
+
spec.version = Legion::Extensions::CausalAttribution::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Causal Attribution'
|
|
12
|
+
spec.description = "Weiner's attribution theory for brain-modeled agentic AI — locus, stability, controllability"
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-causal-attribution'
|
|
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-causal-attribution'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-causal-attribution'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-causal-attribution'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-causal-attribution/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-causal-attribution.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/causal_attribution/helpers/attribution'
|
|
4
|
+
require 'legion/extensions/causal_attribution/helpers/attribution_engine'
|
|
5
|
+
require 'legion/extensions/causal_attribution/runners/causal_attribution'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module CausalAttribution
|
|
10
|
+
class Client
|
|
11
|
+
include Runners::CausalAttribution
|
|
12
|
+
|
|
13
|
+
def initialize(**)
|
|
14
|
+
@engine = Helpers::AttributionEngine.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :engine
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CausalAttribution
|
|
8
|
+
module Helpers
|
|
9
|
+
class Attribution
|
|
10
|
+
MAX_ATTRIBUTIONS = 200
|
|
11
|
+
MAX_HISTORY = 300
|
|
12
|
+
|
|
13
|
+
LOCUS_VALUES = %i[internal external].freeze
|
|
14
|
+
STABILITY_VALUES = %i[stable unstable].freeze
|
|
15
|
+
CONTROLLABILITY_VALUES = %i[controllable uncontrollable].freeze
|
|
16
|
+
|
|
17
|
+
DEFAULT_CONFIDENCE = 0.5
|
|
18
|
+
CONFIDENCE_FLOOR = 0.0
|
|
19
|
+
CONFIDENCE_CEILING = 1.0
|
|
20
|
+
DECAY_RATE = 0.02
|
|
21
|
+
|
|
22
|
+
ATTRIBUTION_EMOTIONS = {
|
|
23
|
+
%i[internal stable controllable] => :guilt,
|
|
24
|
+
%i[internal stable uncontrollable] => :shame,
|
|
25
|
+
%i[internal unstable controllable] => :regret,
|
|
26
|
+
%i[internal unstable uncontrollable] => :surprise,
|
|
27
|
+
%i[external stable controllable] => :anger,
|
|
28
|
+
%i[external stable uncontrollable] => :helplessness,
|
|
29
|
+
%i[external unstable controllable] => :frustration,
|
|
30
|
+
%i[external unstable uncontrollable] => :relief
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
attr_reader :id, :event, :outcome, :domain, :locus, :stability, :controllability,
|
|
34
|
+
:confidence, :emotional_response, :created_at
|
|
35
|
+
|
|
36
|
+
def initialize(event:, outcome:, domain:, locus:, stability:, controllability:,
|
|
37
|
+
confidence: DEFAULT_CONFIDENCE)
|
|
38
|
+
@id = SecureRandom.uuid
|
|
39
|
+
@event = event
|
|
40
|
+
@outcome = outcome
|
|
41
|
+
@domain = domain
|
|
42
|
+
@locus = locus
|
|
43
|
+
@stability = stability
|
|
44
|
+
@controllability = controllability
|
|
45
|
+
@confidence = confidence.clamp(CONFIDENCE_FLOOR, CONFIDENCE_CEILING)
|
|
46
|
+
@emotional_response = ATTRIBUTION_EMOTIONS[pattern]
|
|
47
|
+
@created_at = Time.now.utc
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pattern
|
|
51
|
+
[locus, stability, controllability]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def internal?
|
|
55
|
+
locus == :internal
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def external?
|
|
59
|
+
locus == :external
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stable?
|
|
63
|
+
stability == :stable
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def controllable?
|
|
67
|
+
controllability == :controllable
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_h
|
|
71
|
+
{
|
|
72
|
+
id: id,
|
|
73
|
+
event: event,
|
|
74
|
+
outcome: outcome,
|
|
75
|
+
domain: domain,
|
|
76
|
+
locus: locus,
|
|
77
|
+
stability: stability,
|
|
78
|
+
controllability: controllability,
|
|
79
|
+
confidence: confidence,
|
|
80
|
+
emotional_response: emotional_response,
|
|
81
|
+
created_at: created_at
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CausalAttribution
|
|
6
|
+
module Helpers
|
|
7
|
+
class AttributionEngine
|
|
8
|
+
def initialize
|
|
9
|
+
@attributions = {}
|
|
10
|
+
@history = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_attribution(event:, outcome:, domain:, locus:, stability:, controllability:,
|
|
14
|
+
confidence: Attribution::DEFAULT_CONFIDENCE)
|
|
15
|
+
trim_if_needed
|
|
16
|
+
attribution = Attribution.new(
|
|
17
|
+
event: event,
|
|
18
|
+
outcome: outcome,
|
|
19
|
+
domain: domain,
|
|
20
|
+
locus: locus,
|
|
21
|
+
stability: stability,
|
|
22
|
+
controllability: controllability,
|
|
23
|
+
confidence: confidence
|
|
24
|
+
)
|
|
25
|
+
@attributions[attribution.id] = attribution
|
|
26
|
+
attribution
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reattribute(attribution_id:, locus: nil, stability: nil, controllability: nil)
|
|
30
|
+
attr = @attributions[attribution_id]
|
|
31
|
+
return { found: false, attribution_id: attribution_id } unless attr
|
|
32
|
+
|
|
33
|
+
new_locus = locus || attr.locus
|
|
34
|
+
new_stability = stability || attr.stability
|
|
35
|
+
new_controllability = controllability || attr.controllability
|
|
36
|
+
|
|
37
|
+
updated = Attribution.new(
|
|
38
|
+
event: attr.event,
|
|
39
|
+
outcome: attr.outcome,
|
|
40
|
+
domain: attr.domain,
|
|
41
|
+
locus: new_locus,
|
|
42
|
+
stability: new_stability,
|
|
43
|
+
controllability: new_controllability,
|
|
44
|
+
confidence: attr.confidence
|
|
45
|
+
)
|
|
46
|
+
@history << attr.to_h if @history.size < Attribution::MAX_HISTORY
|
|
47
|
+
@attributions[attribution_id] = updated
|
|
48
|
+
updated
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def by_pattern(locus: nil, stability: nil, controllability: nil)
|
|
52
|
+
@attributions.values.select do |a|
|
|
53
|
+
(locus.nil? || a.locus == locus) &&
|
|
54
|
+
(stability.nil? || a.stability == stability) &&
|
|
55
|
+
(controllability.nil? || a.controllability == controllability)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def by_domain(domain:)
|
|
60
|
+
@attributions.values.select { |a| a.domain == domain }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def by_outcome(outcome:)
|
|
64
|
+
@attributions.values.select { |a| a.outcome == outcome }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def attribution_bias
|
|
68
|
+
return { bias: :none, detail: 'no attributions' } if @attributions.empty?
|
|
69
|
+
|
|
70
|
+
all = @attributions.values
|
|
71
|
+
total = all.size.to_f
|
|
72
|
+
locus = bias_ratio(all, :locus, :external, total)
|
|
73
|
+
stab = bias_ratio(all, :stability, :stable, total)
|
|
74
|
+
control = bias_ratio(all, :controllability, :uncontrollable, total)
|
|
75
|
+
|
|
76
|
+
failures = all.select { |a| a.outcome == :failure }
|
|
77
|
+
external_failure_ratio = failures.empty? ? 0.0 : failures.count(&:external?).to_f / failures.size
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
external_locus_ratio: locus.round(3),
|
|
81
|
+
stable_ratio: stab.round(3),
|
|
82
|
+
uncontrollable_ratio: control.round(3),
|
|
83
|
+
external_failure_ratio: external_failure_ratio.round(3),
|
|
84
|
+
self_serving_bias_detected: external_failure_ratio > 0.6,
|
|
85
|
+
total_attributions: @attributions.size
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def emotional_profile
|
|
90
|
+
counts = Hash.new(0)
|
|
91
|
+
@attributions.each_value { |a| counts[a.emotional_response] += 1 if a.emotional_response }
|
|
92
|
+
total = counts.values.sum.to_f
|
|
93
|
+
profile = counts.transform_values { |v| (v / total).round(3) }
|
|
94
|
+
dominant = counts.max_by { |_, v| v }&.first
|
|
95
|
+
{ distribution: profile, dominant: dominant, total: counts.values.sum }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def most_common_pattern
|
|
99
|
+
return { pattern: nil, count: 0 } if @attributions.empty?
|
|
100
|
+
|
|
101
|
+
grouped = @attributions.values.group_by(&:pattern)
|
|
102
|
+
best = grouped.max_by { |_, attrs| attrs.size }
|
|
103
|
+
{ pattern: best[0], count: best[1].size }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def decay_all
|
|
107
|
+
decayed = 0
|
|
108
|
+
@attributions.each_value do |a|
|
|
109
|
+
new_conf = (a.confidence - Attribution::DECAY_RATE).clamp(
|
|
110
|
+
Attribution::CONFIDENCE_FLOOR, Attribution::CONFIDENCE_CEILING
|
|
111
|
+
)
|
|
112
|
+
a.instance_variable_set(:@confidence, new_conf)
|
|
113
|
+
decayed += 1
|
|
114
|
+
end
|
|
115
|
+
decayed
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def count
|
|
119
|
+
@attributions.size
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def to_h
|
|
123
|
+
{
|
|
124
|
+
total_attributions: @attributions.size,
|
|
125
|
+
history_size: @history.size,
|
|
126
|
+
outcome_counts: outcome_counts,
|
|
127
|
+
locus_counts: locus_counts
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def trim_if_needed
|
|
134
|
+
return unless @attributions.size >= Attribution::MAX_ATTRIBUTIONS
|
|
135
|
+
|
|
136
|
+
oldest_key = @attributions.min_by { |_, a| a.created_at }&.first
|
|
137
|
+
@attributions.delete(oldest_key)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def bias_ratio(all, dimension, value, total)
|
|
141
|
+
all.count { |a| a.public_send(dimension) == value } / total
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def outcome_counts
|
|
145
|
+
@attributions.values.group_by(&:outcome).transform_values(&:size)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def locus_counts
|
|
149
|
+
@attributions.values.group_by(&:locus).transform_values(&:size)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CausalAttribution
|
|
6
|
+
module Runners
|
|
7
|
+
module CausalAttribution
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def create_causal_attribution(event:, outcome:, domain:, locus:, stability:, controllability:,
|
|
12
|
+
confidence: nil, **)
|
|
13
|
+
conf = (confidence || Helpers::Attribution::DEFAULT_CONFIDENCE)
|
|
14
|
+
.clamp(Helpers::Attribution::CONFIDENCE_FLOOR, Helpers::Attribution::CONFIDENCE_CEILING)
|
|
15
|
+
attr = engine.create_attribution(
|
|
16
|
+
event: event,
|
|
17
|
+
outcome: outcome,
|
|
18
|
+
domain: domain,
|
|
19
|
+
locus: locus.to_sym,
|
|
20
|
+
stability: stability.to_sym,
|
|
21
|
+
controllability: controllability.to_sym,
|
|
22
|
+
confidence: conf
|
|
23
|
+
)
|
|
24
|
+
Legion::Logging.info "[causal_attribution] create id=#{attr.id} event=#{event} " \
|
|
25
|
+
"outcome=#{outcome} locus=#{locus} emotion=#{attr.emotional_response}"
|
|
26
|
+
{ success: true, attribution: attr.to_h }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reattribute_cause(attribution_id:, locus: nil, stability: nil, controllability: nil, **)
|
|
30
|
+
result = engine.reattribute(
|
|
31
|
+
attribution_id: attribution_id,
|
|
32
|
+
locus: locus&.to_sym,
|
|
33
|
+
stability: stability&.to_sym,
|
|
34
|
+
controllability: controllability&.to_sym
|
|
35
|
+
)
|
|
36
|
+
if result.is_a?(Hash) && result[:found] == false
|
|
37
|
+
Legion::Logging.warn "[causal_attribution] reattribute not_found id=#{attribution_id}"
|
|
38
|
+
return { success: false, attribution_id: attribution_id, found: false }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Legion::Logging.debug "[causal_attribution] reattribute id=#{attribution_id} " \
|
|
42
|
+
"locus=#{result.locus} emotion=#{result.emotional_response}"
|
|
43
|
+
{ success: true, attribution: result.to_h }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def attributions_by_pattern(locus: nil, stability: nil, controllability: nil, **)
|
|
47
|
+
results = engine.by_pattern(
|
|
48
|
+
locus: locus&.to_sym,
|
|
49
|
+
stability: stability&.to_sym,
|
|
50
|
+
controllability: controllability&.to_sym
|
|
51
|
+
)
|
|
52
|
+
Legion::Logging.debug "[causal_attribution] by_pattern count=#{results.size}"
|
|
53
|
+
{ success: true, attributions: results.map(&:to_h), count: results.size }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def domain_attributions(domain:, **)
|
|
57
|
+
results = engine.by_domain(domain: domain.to_sym)
|
|
58
|
+
Legion::Logging.debug "[causal_attribution] by_domain domain=#{domain} count=#{results.size}"
|
|
59
|
+
{ success: true, attributions: results.map(&:to_h), count: results.size }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def outcome_attributions(outcome:, **)
|
|
63
|
+
results = engine.by_outcome(outcome: outcome.to_sym)
|
|
64
|
+
Legion::Logging.debug "[causal_attribution] by_outcome outcome=#{outcome} count=#{results.size}"
|
|
65
|
+
{ success: true, attributions: results.map(&:to_h), count: results.size }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def attribution_bias_assessment(**)
|
|
69
|
+
bias = engine.attribution_bias
|
|
70
|
+
Legion::Logging.debug "[causal_attribution] bias_assessment self_serving=#{bias[:self_serving_bias_detected]}"
|
|
71
|
+
{ success: true, bias: bias }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def emotional_attribution_profile(**)
|
|
75
|
+
profile = engine.emotional_profile
|
|
76
|
+
Legion::Logging.debug "[causal_attribution] emotional_profile dominant=#{profile[:dominant]} total=#{profile[:total]}"
|
|
77
|
+
{ success: true, profile: profile }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def most_common_attribution(**)
|
|
81
|
+
result = engine.most_common_pattern
|
|
82
|
+
Legion::Logging.debug "[causal_attribution] most_common pattern=#{result[:pattern].inspect} count=#{result[:count]}"
|
|
83
|
+
{ success: true, pattern: result[:pattern], count: result[:count] }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_causal_attribution(**)
|
|
87
|
+
decayed = engine.decay_all
|
|
88
|
+
Legion::Logging.debug "[causal_attribution] decay cycle entries=#{decayed}"
|
|
89
|
+
{ success: true, decayed: decayed }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def causal_attribution_stats(**)
|
|
93
|
+
stats = engine.to_h
|
|
94
|
+
Legion::Logging.debug "[causal_attribution] stats total=#{stats[:total_attributions]}"
|
|
95
|
+
{ success: true, stats: stats }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def engine
|
|
101
|
+
@engine ||= Helpers::AttributionEngine.new
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/causal_attribution/version'
|
|
4
|
+
require 'legion/extensions/causal_attribution/helpers/attribution'
|
|
5
|
+
require 'legion/extensions/causal_attribution/helpers/attribution_engine'
|
|
6
|
+
require 'legion/extensions/causal_attribution/runners/causal_attribution'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module CausalAttribution
|
|
11
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/causal_attribution/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CausalAttribution::Client do
|
|
6
|
+
let(:client) { described_class.new }
|
|
7
|
+
|
|
8
|
+
it 'responds to all runner methods' do
|
|
9
|
+
%i[
|
|
10
|
+
create_causal_attribution
|
|
11
|
+
reattribute_cause
|
|
12
|
+
attributions_by_pattern
|
|
13
|
+
domain_attributions
|
|
14
|
+
outcome_attributions
|
|
15
|
+
attribution_bias_assessment
|
|
16
|
+
emotional_attribution_profile
|
|
17
|
+
most_common_attribution
|
|
18
|
+
update_causal_attribution
|
|
19
|
+
causal_attribution_stats
|
|
20
|
+
].each do |method|
|
|
21
|
+
expect(client).to respond_to(method)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CausalAttribution::Helpers::AttributionEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
let(:basic_args) do
|
|
7
|
+
{
|
|
8
|
+
event: 'task completed',
|
|
9
|
+
outcome: :success,
|
|
10
|
+
domain: :work,
|
|
11
|
+
locus: :internal,
|
|
12
|
+
stability: :stable,
|
|
13
|
+
controllability: :controllable
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#create_attribution' do
|
|
18
|
+
it 'returns an Attribution object' do
|
|
19
|
+
result = engine.create_attribution(**basic_args)
|
|
20
|
+
expect(result).to be_a(Legion::Extensions::CausalAttribution::Helpers::Attribution)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'stores the attribution' do
|
|
24
|
+
engine.create_attribution(**basic_args)
|
|
25
|
+
expect(engine.count).to eq(1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'accepts optional confidence' do
|
|
29
|
+
attr = engine.create_attribution(**basic_args, confidence: 0.9)
|
|
30
|
+
expect(attr.confidence).to eq(0.9)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '#reattribute' do
|
|
35
|
+
let!(:attr) { engine.create_attribution(**basic_args) }
|
|
36
|
+
|
|
37
|
+
it 'updates locus when provided' do
|
|
38
|
+
result = engine.reattribute(attribution_id: attr.id, locus: :external)
|
|
39
|
+
expect(result.locus).to eq(:external)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'preserves unchanged dimensions' do
|
|
43
|
+
result = engine.reattribute(attribution_id: attr.id, locus: :external)
|
|
44
|
+
expect(result.stability).to eq(:stable)
|
|
45
|
+
expect(result.controllability).to eq(:controllable)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'updates the emotional_response after reattribution' do
|
|
49
|
+
result = engine.reattribute(attribution_id: attr.id, locus: :external)
|
|
50
|
+
expect(result.emotional_response).to eq(:anger)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns not found hash for unknown id' do
|
|
54
|
+
result = engine.reattribute(attribution_id: 'nonexistent')
|
|
55
|
+
expect(result[:found]).to be false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#by_pattern' do
|
|
60
|
+
before do
|
|
61
|
+
engine.create_attribution(**basic_args)
|
|
62
|
+
engine.create_attribution(**basic_args, locus: :external)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'filters by locus' do
|
|
66
|
+
results = engine.by_pattern(locus: :internal)
|
|
67
|
+
expect(results.size).to eq(1)
|
|
68
|
+
expect(results.first.locus).to eq(:internal)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'returns all when no filter given' do
|
|
72
|
+
expect(engine.by_pattern.size).to eq(2)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe '#by_domain' do
|
|
77
|
+
before do
|
|
78
|
+
engine.create_attribution(**basic_args)
|
|
79
|
+
engine.create_attribution(**basic_args, domain: :personal)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'returns only matching domain' do
|
|
83
|
+
results = engine.by_domain(domain: :work)
|
|
84
|
+
expect(results.size).to eq(1)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '#by_outcome' do
|
|
89
|
+
before do
|
|
90
|
+
engine.create_attribution(**basic_args)
|
|
91
|
+
engine.create_attribution(**basic_args, outcome: :failure)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'returns only matching outcome' do
|
|
95
|
+
results = engine.by_outcome(outcome: :failure)
|
|
96
|
+
expect(results.size).to eq(1)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#attribution_bias' do
|
|
101
|
+
it 'returns bias hash with detection key' do
|
|
102
|
+
engine.create_attribution(**basic_args)
|
|
103
|
+
result = engine.attribution_bias
|
|
104
|
+
expect(result).to have_key(:self_serving_bias_detected)
|
|
105
|
+
expect(result).to have_key(:external_failure_ratio)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'detects self-serving bias when failures are mostly external' do
|
|
109
|
+
3.times do
|
|
110
|
+
engine.create_attribution(**basic_args, outcome: :failure, locus: :external, stability: :unstable, controllability: :controllable)
|
|
111
|
+
end
|
|
112
|
+
result = engine.attribution_bias
|
|
113
|
+
expect(result[:self_serving_bias_detected]).to be true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns no attributions message when empty' do
|
|
117
|
+
result = engine.attribution_bias
|
|
118
|
+
expect(result[:bias]).to eq(:none)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe '#emotional_profile' do
|
|
123
|
+
before do
|
|
124
|
+
engine.create_attribution(**basic_args)
|
|
125
|
+
engine.create_attribution(**basic_args, locus: :external, stability: :unstable)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'returns distribution and dominant' do
|
|
129
|
+
result = engine.emotional_profile
|
|
130
|
+
expect(result).to have_key(:distribution)
|
|
131
|
+
expect(result).to have_key(:dominant)
|
|
132
|
+
expect(result[:total]).to eq(2)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe '#most_common_pattern' do
|
|
137
|
+
before do
|
|
138
|
+
2.times { engine.create_attribution(**basic_args) }
|
|
139
|
+
engine.create_attribution(**basic_args, locus: :external)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'returns the most frequent pattern' do
|
|
143
|
+
result = engine.most_common_pattern
|
|
144
|
+
expect(result[:pattern]).to eq(%i[internal stable controllable])
|
|
145
|
+
expect(result[:count]).to eq(2)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'returns nil pattern when empty' do
|
|
149
|
+
expect(described_class.new.most_common_pattern[:pattern]).to be_nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
describe '#decay_all' do
|
|
154
|
+
it 'reduces confidence on all attributions' do
|
|
155
|
+
engine.create_attribution(**basic_args, confidence: 0.8)
|
|
156
|
+
engine.decay_all
|
|
157
|
+
expect(engine.by_pattern.first.confidence).to be < 0.8
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'returns count of decayed entries' do
|
|
161
|
+
2.times { engine.create_attribution(**basic_args) }
|
|
162
|
+
expect(engine.decay_all).to eq(2)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'floors at CONFIDENCE_FLOOR' do
|
|
166
|
+
attr = engine.create_attribution(**basic_args, confidence: 0.001)
|
|
167
|
+
engine.decay_all
|
|
168
|
+
expect(attr.confidence).to be >= Legion::Extensions::CausalAttribution::Helpers::Attribution::CONFIDENCE_FLOOR
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe '#to_h' do
|
|
173
|
+
it 'includes stats keys' do
|
|
174
|
+
engine.create_attribution(**basic_args)
|
|
175
|
+
h = engine.to_h
|
|
176
|
+
expect(h).to have_key(:total_attributions)
|
|
177
|
+
expect(h).to have_key(:outcome_counts)
|
|
178
|
+
expect(h).to have_key(:locus_counts)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CausalAttribution::Helpers::Attribution do
|
|
4
|
+
subject(:attr) do
|
|
5
|
+
described_class.new(
|
|
6
|
+
event: 'task failed',
|
|
7
|
+
outcome: :failure,
|
|
8
|
+
domain: :work,
|
|
9
|
+
locus: :internal,
|
|
10
|
+
stability: :stable,
|
|
11
|
+
controllability: :controllable
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#initialize' do
|
|
16
|
+
it 'assigns an id' do
|
|
17
|
+
expect(attr.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'stores all dimensions' do
|
|
21
|
+
expect(attr.locus).to eq(:internal)
|
|
22
|
+
expect(attr.stability).to eq(:stable)
|
|
23
|
+
expect(attr.controllability).to eq(:controllable)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'derives emotional_response from ATTRIBUTION_EMOTIONS' do
|
|
27
|
+
expect(attr.emotional_response).to eq(:guilt)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'uses DEFAULT_CONFIDENCE when none given' do
|
|
31
|
+
expect(attr.confidence).to eq(described_class::DEFAULT_CONFIDENCE)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'clamps confidence to ceiling' do
|
|
35
|
+
high = described_class.new(
|
|
36
|
+
event: 'x', outcome: :success, domain: :test,
|
|
37
|
+
locus: :external, stability: :unstable, controllability: :uncontrollable,
|
|
38
|
+
confidence: 5.0
|
|
39
|
+
)
|
|
40
|
+
expect(high.confidence).to eq(1.0)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'clamps confidence to floor' do
|
|
44
|
+
low = described_class.new(
|
|
45
|
+
event: 'x', outcome: :success, domain: :test,
|
|
46
|
+
locus: :external, stability: :unstable, controllability: :uncontrollable,
|
|
47
|
+
confidence: -1.0
|
|
48
|
+
)
|
|
49
|
+
expect(low.confidence).to eq(0.0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'records created_at' do
|
|
53
|
+
expect(attr.created_at).to be_a(Time)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#pattern' do
|
|
58
|
+
it 'returns [locus, stability, controllability] tuple' do
|
|
59
|
+
expect(attr.pattern).to eq(%i[internal stable controllable])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe 'predicates' do
|
|
64
|
+
it 'internal? returns true for internal locus' do
|
|
65
|
+
expect(attr.internal?).to be true
|
|
66
|
+
expect(attr.external?).to be false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'stable? returns true for stable stability' do
|
|
70
|
+
expect(attr.stable?).to be true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'controllable? returns true for controllable controllability' do
|
|
74
|
+
expect(attr.controllable?).to be true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe 'ATTRIBUTION_EMOTIONS mapping' do
|
|
79
|
+
{
|
|
80
|
+
%i[internal stable controllable] => :guilt,
|
|
81
|
+
%i[internal stable uncontrollable] => :shame,
|
|
82
|
+
%i[internal unstable controllable] => :regret,
|
|
83
|
+
%i[internal unstable uncontrollable] => :surprise,
|
|
84
|
+
%i[external stable controllable] => :anger,
|
|
85
|
+
%i[external stable uncontrollable] => :helplessness,
|
|
86
|
+
%i[external unstable controllable] => :frustration,
|
|
87
|
+
%i[external unstable uncontrollable] => :relief
|
|
88
|
+
}.each do |pattern, emotion|
|
|
89
|
+
it "maps #{pattern.inspect} to #{emotion}" do
|
|
90
|
+
a = described_class.new(
|
|
91
|
+
event: 'e', outcome: :neutral, domain: :test,
|
|
92
|
+
locus: pattern[0], stability: pattern[1], controllability: pattern[2]
|
|
93
|
+
)
|
|
94
|
+
expect(a.emotional_response).to eq(emotion)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#to_h' do
|
|
100
|
+
it 'includes all required keys' do
|
|
101
|
+
h = attr.to_h
|
|
102
|
+
%i[id event outcome domain locus stability controllability
|
|
103
|
+
confidence emotional_response created_at].each do |key|
|
|
104
|
+
expect(h).to have_key(key)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/causal_attribution/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CausalAttribution::Runners::CausalAttribution do
|
|
6
|
+
let(:client) { Legion::Extensions::CausalAttribution::Client.new }
|
|
7
|
+
|
|
8
|
+
let(:base_args) do
|
|
9
|
+
{
|
|
10
|
+
event: 'missed deadline',
|
|
11
|
+
outcome: :failure,
|
|
12
|
+
domain: :work,
|
|
13
|
+
locus: :internal,
|
|
14
|
+
stability: :stable,
|
|
15
|
+
controllability: :controllable
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#create_causal_attribution' do
|
|
20
|
+
it 'returns success true' do
|
|
21
|
+
result = client.create_causal_attribution(**base_args)
|
|
22
|
+
expect(result[:success]).to be true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'includes attribution hash' do
|
|
26
|
+
result = client.create_causal_attribution(**base_args)
|
|
27
|
+
expect(result[:attribution]).to have_key(:id)
|
|
28
|
+
expect(result[:attribution][:emotional_response]).to eq(:guilt)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'accepts string locus and coerces to symbol' do
|
|
32
|
+
result = client.create_causal_attribution(**base_args, locus: 'external')
|
|
33
|
+
expect(result[:attribution][:locus]).to eq(:external)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#reattribute_cause' do
|
|
38
|
+
let!(:id) { client.create_causal_attribution(**base_args)[:attribution][:id] }
|
|
39
|
+
|
|
40
|
+
it 'updates the attribution' do
|
|
41
|
+
result = client.reattribute_cause(attribution_id: id, locus: :external)
|
|
42
|
+
expect(result[:success]).to be true
|
|
43
|
+
expect(result[:attribution][:locus]).to eq(:external)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns success false for unknown id' do
|
|
47
|
+
result = client.reattribute_cause(attribution_id: 'bad-id')
|
|
48
|
+
expect(result[:success]).to be false
|
|
49
|
+
expect(result[:found]).to be false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe '#attributions_by_pattern' do
|
|
54
|
+
before do
|
|
55
|
+
client.create_causal_attribution(**base_args)
|
|
56
|
+
client.create_causal_attribution(**base_args, locus: :external)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'filters by locus' do
|
|
60
|
+
result = client.attributions_by_pattern(locus: :internal)
|
|
61
|
+
expect(result[:count]).to eq(1)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'returns all when no filter given' do
|
|
65
|
+
result = client.attributions_by_pattern
|
|
66
|
+
expect(result[:count]).to eq(2)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '#domain_attributions' do
|
|
71
|
+
before { client.create_causal_attribution(**base_args) }
|
|
72
|
+
|
|
73
|
+
it 'returns attributions for domain' do
|
|
74
|
+
result = client.domain_attributions(domain: :work)
|
|
75
|
+
expect(result[:count]).to be >= 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '#outcome_attributions' do
|
|
80
|
+
before do
|
|
81
|
+
client.create_causal_attribution(**base_args)
|
|
82
|
+
client.create_causal_attribution(**base_args, outcome: :success)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'returns attributions by outcome' do
|
|
86
|
+
result = client.outcome_attributions(outcome: :failure)
|
|
87
|
+
expect(result[:count]).to eq(1)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#attribution_bias_assessment' do
|
|
92
|
+
before { client.create_causal_attribution(**base_args) }
|
|
93
|
+
|
|
94
|
+
it 'returns bias structure' do
|
|
95
|
+
result = client.attribution_bias_assessment
|
|
96
|
+
expect(result[:success]).to be true
|
|
97
|
+
expect(result[:bias]).to have_key(:self_serving_bias_detected)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '#emotional_attribution_profile' do
|
|
102
|
+
before { client.create_causal_attribution(**base_args) }
|
|
103
|
+
|
|
104
|
+
it 'returns profile with dominant emotion' do
|
|
105
|
+
result = client.emotional_attribution_profile
|
|
106
|
+
expect(result[:success]).to be true
|
|
107
|
+
expect(result[:profile]).to have_key(:dominant)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe '#most_common_attribution' do
|
|
112
|
+
before do
|
|
113
|
+
2.times { client.create_causal_attribution(**base_args) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns the most frequent pattern' do
|
|
117
|
+
result = client.most_common_attribution
|
|
118
|
+
expect(result[:success]).to be true
|
|
119
|
+
expect(result[:count]).to eq(2)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe '#update_causal_attribution' do
|
|
124
|
+
before { client.create_causal_attribution(**base_args, confidence: 0.8) }
|
|
125
|
+
|
|
126
|
+
it 'decays confidence and reports count' do
|
|
127
|
+
result = client.update_causal_attribution
|
|
128
|
+
expect(result[:success]).to be true
|
|
129
|
+
expect(result[:decayed]).to eq(1)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe '#causal_attribution_stats' do
|
|
134
|
+
before { client.create_causal_attribution(**base_args) }
|
|
135
|
+
|
|
136
|
+
it 'returns stats hash' do
|
|
137
|
+
result = client.causal_attribution_stats
|
|
138
|
+
expect(result[:success]).to be true
|
|
139
|
+
expect(result[:stats][:total_attributions]).to eq(1)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
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/causal_attribution'
|
|
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,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-causal-attribution
|
|
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: Weiner's attribution theory for brain-modeled agentic AI — locus, stability,
|
|
27
|
+
controllability
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- lex-causal-attribution.gemspec
|
|
36
|
+
- lib/legion/extensions/causal_attribution.rb
|
|
37
|
+
- lib/legion/extensions/causal_attribution/client.rb
|
|
38
|
+
- lib/legion/extensions/causal_attribution/helpers/attribution.rb
|
|
39
|
+
- lib/legion/extensions/causal_attribution/helpers/attribution_engine.rb
|
|
40
|
+
- lib/legion/extensions/causal_attribution/runners/causal_attribution.rb
|
|
41
|
+
- lib/legion/extensions/causal_attribution/version.rb
|
|
42
|
+
- spec/legion/extensions/causal_attribution/client_spec.rb
|
|
43
|
+
- spec/legion/extensions/causal_attribution/helpers/attribution_engine_spec.rb
|
|
44
|
+
- spec/legion/extensions/causal_attribution/helpers/attribution_spec.rb
|
|
45
|
+
- spec/legion/extensions/causal_attribution/runners/causal_attribution_spec.rb
|
|
46
|
+
- spec/spec_helper.rb
|
|
47
|
+
homepage: https://github.com/LegionIO/lex-causal-attribution
|
|
48
|
+
licenses:
|
|
49
|
+
- MIT
|
|
50
|
+
metadata:
|
|
51
|
+
homepage_uri: https://github.com/LegionIO/lex-causal-attribution
|
|
52
|
+
source_code_uri: https://github.com/LegionIO/lex-causal-attribution
|
|
53
|
+
documentation_uri: https://github.com/LegionIO/lex-causal-attribution
|
|
54
|
+
changelog_uri: https://github.com/LegionIO/lex-causal-attribution
|
|
55
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-causal-attribution/issues
|
|
56
|
+
rubygems_mfa_required: 'true'
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '3.4'
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 3.6.9
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: LEX Causal Attribution
|
|
74
|
+
test_files: []
|