lex-cognitive-catalyst 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0831b554c585e8335d544e63f5a141d04c795876c99f9123e5d9117bf65a8cd2'
4
+ data.tar.gz: bf6d4293e8d91d75a90ca92f01f273d9d651f2970077562416a13079f8da7ddf
5
+ SHA512:
6
+ metadata.gz: 1de51909f167df0bab5a81c5719e01ec1394b318fdc12bee119e55854a0d204896696d340090a4e0c37e55b8a8f584042cdd2536f1b7380035f7c9c443f7ccd2
7
+ data.tar.gz: 6f7c198df94bdbffb1a389d3e444d52f5f8d13083a8db4c07890b7d88c36e88c8881a7fb3b5cae7201f99adf87c8abface4558efa9d31c69175cc72ab97606ca
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc/
9
+ coverage/
10
+ doc/
11
+ lib/bundler/man
12
+ pkg/
13
+ rdoc/
14
+ spec/reports/
15
+ test/
16
+ tmp/
17
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
4
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+ Style/Documentation:
5
+ Enabled: false
6
+ Naming/PredicateMethod:
7
+ Enabled: false
8
+ Naming/PredicatePrefix:
9
+ Enabled: false
10
+ Metrics/ClassLength:
11
+ Max: 150
12
+ Metrics/MethodLength:
13
+ Max: 25
14
+ Metrics/AbcSize:
15
+ Max: 25
16
+ Metrics/ParameterLists:
17
+ Max: 8
18
+ MaxOptionalParameters: 8
19
+ Layout/HashAlignment:
20
+ EnforcedColonStyle: table
21
+ EnforcedHashRocketStyle: table
22
+ Metrics/BlockLength:
23
+ Exclude:
24
+ - 'spec/**/*'
25
+ Style/FrozenStringLiteralComment:
26
+ Enabled: true
27
+ Style/OneClassPerFile:
28
+ Exclude:
29
+ - 'spec/spec_helper.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,78 @@
1
+ # lex-cognitive-catalyst
2
+
3
+ **Level 3 Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Models how certain thoughts and experiences act as chemical catalysts — accelerating cognitive reactions without being consumed. Catalysts lower the activation energy required for synthesis, decomposition, exchange, neutralization, and precipitation reactions between ideas. A potent catalyst enables reactions that would otherwise stall from insufficient energy input.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-cognitive-catalyst`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::CognitiveCatalyst`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/cognitive_catalyst/
23
+ cognitive_catalyst.rb
24
+ version.rb
25
+ client.rb
26
+ helpers/
27
+ constants.rb
28
+ catalyst.rb
29
+ catalyst_engine.rb
30
+ reaction.rb
31
+ runners/
32
+ cognitive_catalyst.rb
33
+ ```
34
+
35
+ ## Key Constants
36
+
37
+ From `helpers/constants.rb`:
38
+
39
+ - `CATALYST_TYPES` — `%i[experience insight analogy pattern emotion]`
40
+ - `REACTION_TYPES` — `%i[synthesis decomposition exchange neutralization precipitation]`
41
+ - `MAX_CATALYSTS` = `500`, `MAX_REACTIONS` = `200`
42
+ - `ACTIVATION_ENERGY` = `0.6` (minimum energy to complete a reaction without a catalyst)
43
+ - `CATALYST_REDUCTION` = `0.3` (how much a catalyst lowers the activation energy threshold)
44
+ - `POTENCY_DECAY` = `0.02` (environmental wear — not from use)
45
+ - `SPECIFICITY_BONUS` = `0.15` (bonus when catalyst domain matches reaction domain)
46
+ - `POTENCY_LABELS` — `0.8+` = `:powerful`, `0.6` = `:strong`, `0.4` = `:moderate`, `0.2` = `:weak`, below = `:inert`
47
+ - `YIELD_LABELS` — `0.8+` = `:excellent` through below `0.2` = `:negligible`
48
+
49
+ ## Runners
50
+
51
+ All methods in `Runners::CognitiveCatalyst`:
52
+
53
+ - `create_catalyst(catalyst_type:, domain:, potency: nil, specificity: nil)` — registers a new catalyst; validates against `CATALYST_TYPES`
54
+ - `create_reaction(reaction_type:, reactants:, activation_energy: nil)` — creates a pending reaction; validates against `REACTION_TYPES`
55
+ - `apply_catalyst(catalyst_id:, reaction_id:)` — associates a catalyst with a reaction, lowering effective activation energy
56
+ - `attempt_reaction(reaction_id:, energy_input:)` — attempts to complete a reaction; succeeds if `energy_input >= effective_threshold`; returns yield score
57
+ - `recharge(catalyst_id:, amount:)` — restores potency to a catalyst
58
+ - `list_catalysts` — all catalysts with current potency
59
+ - `catalyst_status` — aggregate report: totals, catalyzed rate, most potent
60
+
61
+ ## Helpers
62
+
63
+ - `CatalystEngine` — manages catalysts and reactions. `apply_catalyst` attaches a catalyst to a reaction; `attempt_reaction` evaluates whether energy input meets the (optionally reduced) threshold.
64
+ - `Catalyst` — has `catalyst_type`, `domain`, `potency`, `specificity`. `potency` decays over time (environmental wear). Not consumed on use.
65
+ - `Reaction` — has `reaction_type`, `reactants`, `activation_energy`. Stores applied catalyst IDs. Effective threshold = `activation_energy - (CATALYST_REDUCTION * catalyst_potency) + SPECIFICITY_BONUS` if domains match.
66
+
67
+ ## Integration Points
68
+
69
+ - `lex-cognitive-coherence` can provide the constraint network that reactions operate within — catalysts accelerate coherence maximization by lowering the cost of belief integration.
70
+ - `lex-memory` trace retrieval during `lex-dream` is a natural catalyst source: high-strength traces become experience catalysts for synthesis reactions during dream phases.
71
+ - `recharge` enables explicit catalyst maintenance — callers can recharge important catalysts (e.g., a core analogy) to keep them potent.
72
+
73
+ ## Development Notes
74
+
75
+ - Catalyst potency decays from environmental wear (not from use) — catalysts are not consumed. `POTENCY_DECAY = 0.02` per decay cycle.
76
+ - `SPECIFICITY_BONUS` is added when catalyst domain matches reaction domain — rewarding domain-specific knowledge application.
77
+ - Invalid `catalyst_type` or `reaction_type` raises `ArgumentError` (from constants validation in the runner).
78
+ - Runners log all operations via `Legion::Logging.debug`/`warn`.
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75'
9
+ gem 'rubocop-rspec'
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # lex-cognitive-catalyst
2
+
3
+ Cognitive catalysis acceleration for LegionIO. Models how certain thoughts and experiences act as chemical catalysts — accelerating cognitive reactions without being consumed.
4
+
5
+ ## What It Does
6
+
7
+ Catalysts lower the activation energy required for cognitive reactions. An insight, analogy, or recurring pattern can make otherwise-stalled synthesis reactions complete with much less energy. Unlike consumed reagents, catalysts remain available for repeated use (though their potency decays from environmental wear).
8
+
9
+ Five reaction types are supported: synthesis (combining ideas), decomposition (breaking down assumptions), exchange (swapping belief components), neutralization (resolving opposing forces), and precipitation (crystallizing vague concepts into concrete ones). Applying the right catalyst — especially one with matching domain specificity — dramatically increases reaction yield.
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ client = Legion::Extensions::CognitiveCatalyst::Client.new
15
+
16
+ catalyst = client.create_catalyst(
17
+ catalyst_type: :analogy,
18
+ domain: :problem_solving,
19
+ potency: 0.9,
20
+ specificity: 0.8
21
+ )
22
+
23
+ reaction = client.create_reaction(
24
+ reaction_type: :synthesis,
25
+ reactants: ['concept_a', 'concept_b'],
26
+ activation_energy: 0.7
27
+ )
28
+
29
+ client.apply_catalyst(catalyst_id: catalyst[:catalyst][:id],
30
+ reaction_id: reaction[:reaction][:id])
31
+ client.attempt_reaction(reaction_id: reaction[:reaction][:id], energy_input: 0.5)
32
+ ```
33
+
34
+ ## Development
35
+
36
+ ```bash
37
+ bundle install
38
+ bundle exec rspec
39
+ bundle exec rubocop
40
+ ```
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_catalyst/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-catalyst'
7
+ spec.version = Legion::Extensions::CognitiveCatalyst::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Cognitive catalysis acceleration for LegionIO agentic architecture'
12
+ spec.description = 'Models how certain thoughts and experiences act as chemical catalysts — ' \
13
+ 'accelerating cognitive reactions without being consumed. Catalysts lower ' \
14
+ 'the activation energy for synthesis, decomposition, exchange, neutralization, ' \
15
+ 'and precipitation reactions between ideas.'
16
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-catalyst'
17
+ spec.license = 'MIT'
18
+
19
+ spec.required_ruby_version = '>= 3.4'
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-catalyst'
23
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-catalyst'
24
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-catalyst'
25
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-catalyst/issues'
26
+ spec.metadata['rubygems_mfa_required'] = 'true'
27
+
28
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
29
+ spec.require_paths = ['lib']
30
+ spec.add_development_dependency 'legion-gaia'
31
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveCatalyst
6
+ class Client
7
+ include Runners::CognitiveCatalyst
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveCatalyst
8
+ module Helpers
9
+ class Catalyst
10
+ include Constants
11
+
12
+ attr_reader :id, :catalyst_type, :domain, :potency, :specificity,
13
+ :uses_count, :created_at
14
+
15
+ def initialize(catalyst_type:, domain:, potency: 0.5, specificity: 0.5)
16
+ @id = SecureRandom.uuid
17
+ @catalyst_type = catalyst_type
18
+ @domain = domain
19
+ @potency = potency.clamp(0.0, 1.0)
20
+ @specificity = specificity.clamp(0.0, 1.0)
21
+ @uses_count = 0
22
+ @created_at = Time.now.utc
23
+ end
24
+
25
+ # Apply this catalyst to a reaction — increments uses_count but does NOT reduce potency
26
+ # Catalysts are not consumed by use; they are reusable accelerators
27
+ # Returns activation_reduction = potency * specificity
28
+ def catalyze!(_reaction_type)
29
+ @uses_count += 1
30
+ (@potency * @specificity).round(10)
31
+ end
32
+
33
+ # Degrade from environmental wear (not from use)
34
+ def degrade!
35
+ @potency = (@potency - POTENCY_DECAY).clamp(0.0, 1.0).round(10)
36
+ end
37
+
38
+ # Recharge by increasing potency
39
+ def recharge!(amount)
40
+ @potency = (@potency + amount).clamp(0.0, 1.0).round(10)
41
+ end
42
+
43
+ def powerful?
44
+ @potency >= 0.8
45
+ end
46
+
47
+ def inert?
48
+ @potency < 0.2
49
+ end
50
+
51
+ def specific?
52
+ @specificity >= 0.7
53
+ end
54
+
55
+ def broad?
56
+ @specificity < 0.3
57
+ end
58
+
59
+ def potency_label
60
+ POTENCY_LABELS.find { |range, _| range.cover?(@potency) }&.last || :inert
61
+ end
62
+
63
+ def to_h
64
+ {
65
+ id: @id,
66
+ catalyst_type: @catalyst_type,
67
+ domain: @domain,
68
+ potency: @potency,
69
+ specificity: @specificity,
70
+ uses_count: @uses_count,
71
+ potency_label: potency_label,
72
+ powerful: powerful?,
73
+ inert: inert?,
74
+ specific: specific?,
75
+ broad: broad?,
76
+ created_at: @created_at
77
+ }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveCatalyst
6
+ module Helpers
7
+ class CatalystEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @catalysts = {}
12
+ @reactions = {}
13
+ end
14
+
15
+ def create_catalyst(catalyst_type:, domain:, potency: 0.5, specificity: 0.5, **)
16
+ unless CATALYST_TYPES.include?(catalyst_type.to_sym)
17
+ raise ArgumentError,
18
+ "invalid catalyst_type: #{catalyst_type}"
19
+ end
20
+
21
+ evict_oldest_catalyst if @catalysts.size >= MAX_CATALYSTS
22
+ catalyst = Catalyst.new(
23
+ catalyst_type: catalyst_type.to_sym,
24
+ domain: domain,
25
+ potency: potency,
26
+ specificity: specificity
27
+ )
28
+ @catalysts[catalyst.id] = catalyst
29
+ catalyst
30
+ end
31
+
32
+ def create_reaction(reaction_type:, reactants:, activation_energy: ACTIVATION_ENERGY, **)
33
+ unless REACTION_TYPES.include?(reaction_type.to_sym)
34
+ raise ArgumentError,
35
+ "invalid reaction_type: #{reaction_type}"
36
+ end
37
+
38
+ evict_oldest_reaction if @reactions.size >= MAX_REACTIONS
39
+ reaction = Reaction.new(
40
+ reaction_type: reaction_type.to_sym,
41
+ reactants: reactants,
42
+ activation_energy: activation_energy
43
+ )
44
+ @reactions[reaction.id] = reaction
45
+ reaction
46
+ end
47
+
48
+ def apply_catalyst(catalyst_id:, reaction_id:)
49
+ catalyst = @catalysts[catalyst_id]
50
+ reaction = @reactions[reaction_id]
51
+
52
+ return { success: false, reason: :catalyst_not_found } unless catalyst
53
+ return { success: false, reason: :reaction_not_found } unless reaction
54
+ return { success: false, reason: :already_completed } if reaction.complete?
55
+
56
+ reaction.apply_catalyst!(catalyst)
57
+ {
58
+ success: true,
59
+ activation_energy: reaction.activation_energy,
60
+ catalyst_id: catalyst_id,
61
+ reaction_id: reaction_id
62
+ }
63
+ end
64
+
65
+ def attempt_reaction(reaction_id:, energy_input:)
66
+ reaction = @reactions[reaction_id]
67
+ return { success: false, reason: :not_found } unless reaction
68
+ return { success: false, reason: :already_completed } if reaction.complete?
69
+
70
+ completed = reaction.attempt!(energy_input)
71
+ {
72
+ success: true,
73
+ completed: completed,
74
+ yield_value: reaction.yield_value,
75
+ yield_label: reaction.yield_label,
76
+ catalyzed: reaction.catalyzed?,
77
+ activation_energy: reaction.activation_energy
78
+ }
79
+ end
80
+
81
+ def degrade_all!
82
+ @catalysts.each_value(&:degrade!)
83
+ end
84
+
85
+ def recharge_catalyst(catalyst_id:, amount:)
86
+ catalyst = @catalysts[catalyst_id]
87
+ return { success: false, reason: :not_found } unless catalyst
88
+
89
+ catalyst.recharge!(amount)
90
+ { success: true, potency: catalyst.potency, potency_label: catalyst.potency_label }
91
+ end
92
+
93
+ def all_catalysts
94
+ @catalysts.values
95
+ end
96
+
97
+ def all_reactions
98
+ @reactions.values
99
+ end
100
+
101
+ def completed_reactions
102
+ @reactions.values.select(&:complete?)
103
+ end
104
+
105
+ def catalyzed_rate
106
+ completed = completed_reactions
107
+ return 0.0 if completed.empty?
108
+
109
+ catalyzed_count = completed.count(&:catalyzed?)
110
+ (catalyzed_count.to_f / completed.size).round(10)
111
+ end
112
+
113
+ def catalyst_report
114
+ completed = completed_reactions
115
+ catalyzed_cnt = completed.count(&:catalyzed?)
116
+ avg_potency = if @catalysts.empty?
117
+ 0.0
118
+ else
119
+ (@catalysts.values.sum(&:potency) / @catalysts.size).round(10)
120
+ end
121
+
122
+ {
123
+ total_catalysts: @catalysts.size,
124
+ total_reactions: @reactions.size,
125
+ completed: completed.size,
126
+ catalyzed_count: catalyzed_cnt,
127
+ catalyzed_rate: catalyzed_rate,
128
+ avg_potency: avg_potency,
129
+ powerful_count: @catalysts.values.count(&:powerful?),
130
+ inert_count: @catalysts.values.count(&:inert?)
131
+ }
132
+ end
133
+
134
+ private
135
+
136
+ def evict_oldest_catalyst
137
+ oldest_id = @catalysts.min_by { |_id, c| c.created_at }&.first
138
+ @catalysts.delete(oldest_id) if oldest_id
139
+ end
140
+
141
+ def evict_oldest_reaction
142
+ oldest_id = @reactions.min_by { |_id, r| r.created_at }&.first
143
+ @reactions.delete(oldest_id) if oldest_id
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveCatalyst
6
+ module Helpers
7
+ module Constants
8
+ # Catalyst types — categories of experiences/insights that accelerate reactions
9
+ CATALYST_TYPES = %i[experience insight analogy pattern emotion].freeze
10
+
11
+ # Reaction types — categories of cognitive transformations
12
+ REACTION_TYPES = %i[synthesis decomposition exchange neutralization precipitation].freeze
13
+
14
+ # Storage limits
15
+ MAX_CATALYSTS = 500
16
+ MAX_REACTIONS = 200
17
+
18
+ # Activation energy — minimum energy to complete a reaction without a catalyst
19
+ ACTIVATION_ENERGY = 0.6
20
+
21
+ # How much a catalyst lowers the activation energy threshold
22
+ CATALYST_REDUCTION = 0.3
23
+
24
+ # Potency degradation rate from environmental wear (not from use)
25
+ POTENCY_DECAY = 0.02
26
+
27
+ # Bonus added when catalyst specificity matches reaction domain
28
+ SPECIFICITY_BONUS = 0.15
29
+
30
+ # Potency labels — range-based classification of catalyst strength
31
+ POTENCY_LABELS = {
32
+ (0.8..) => :powerful,
33
+ (0.6...0.8) => :strong,
34
+ (0.4...0.6) => :moderate,
35
+ (0.2...0.4) => :weak,
36
+ (..0.2) => :inert
37
+ }.freeze
38
+
39
+ # Yield labels — range-based classification of reaction output quality
40
+ YIELD_LABELS = {
41
+ (0.8..) => :excellent,
42
+ (0.6...0.8) => :good,
43
+ (0.4...0.6) => :fair,
44
+ (0.2...0.4) => :poor,
45
+ (..0.2) => :negligible
46
+ }.freeze
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveCatalyst
8
+ module Helpers
9
+ class Reaction
10
+ include Constants
11
+
12
+ attr_reader :id, :reaction_type, :reactants, :activation_energy,
13
+ :yield_value, :catalyzed, :catalyst_id, :completed, :created_at
14
+
15
+ def initialize(reaction_type:, reactants:, activation_energy: ACTIVATION_ENERGY)
16
+ @id = SecureRandom.uuid
17
+ @reaction_type = reaction_type
18
+ @reactants = Array(reactants)
19
+ @activation_energy = activation_energy.clamp(0.0, 1.0)
20
+ @yield_value = 0.0
21
+ @catalyzed = false
22
+ @catalyst_id = nil
23
+ @completed = false
24
+ @created_at = Time.now.utc
25
+ end
26
+
27
+ # Apply a catalyst to lower the activation energy threshold
28
+ def apply_catalyst!(catalyst)
29
+ reduction = catalyst.catalyze!(@reaction_type)
30
+ @activation_energy = (@activation_energy - reduction).clamp(0.0, 1.0).round(10)
31
+ @catalyzed = true
32
+ @catalyst_id = catalyst.id
33
+ end
34
+
35
+ # Attempt to complete the reaction with given energy input
36
+ # Completes if energy_input >= activation_energy; yield based on energy surplus
37
+ def attempt!(energy_input)
38
+ return false if @completed
39
+ return false if energy_input < @activation_energy
40
+
41
+ @completed = true
42
+ surplus = (energy_input - @activation_energy).clamp(0.0, 1.0)
43
+ @yield_value = (0.5 + (surplus * 0.5)).clamp(0.0, 1.0).round(10)
44
+ true
45
+ end
46
+
47
+ def complete?
48
+ @completed
49
+ end
50
+
51
+ def catalyzed?
52
+ @catalyzed
53
+ end
54
+
55
+ # Completed spontaneously — without a catalyst
56
+ def spontaneous?
57
+ @completed && !@catalyzed
58
+ end
59
+
60
+ def yield_label
61
+ YIELD_LABELS.find { |range, _| range.cover?(@yield_value) }&.last || :negligible
62
+ end
63
+
64
+ def to_h
65
+ {
66
+ id: @id,
67
+ reaction_type: @reaction_type,
68
+ reactants: @reactants,
69
+ activation_energy: @activation_energy,
70
+ yield_value: @yield_value,
71
+ yield_label: yield_label,
72
+ catalyzed: @catalyzed,
73
+ catalyst_id: @catalyst_id,
74
+ completed: @completed,
75
+ spontaneous: spontaneous?,
76
+ created_at: @created_at
77
+ }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end