lex-agentic-affect 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/CHANGELOG.md +12 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-affect.gemspec +30 -0
- data/lib/legion/extensions/agentic/affect/appraisal/client.rb +20 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal.rb +112 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine.rb +129 -0
- data/lib/legion/extensions/agentic/affect/appraisal/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/affect/appraisal/runners/appraisal.rb +105 -0
- data/lib/legion/extensions/agentic/affect/appraisal/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/appraisal.rb +19 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/client.rb +19 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/constants.rb +37 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine.rb +151 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective.rb +92 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy.rb +93 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/cognitive_empathy.rb +20 -0
- data/lib/legion/extensions/agentic/affect/contagion/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/constants.rb +34 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/contagion_engine.rb +184 -0
- data/lib/legion/extensions/agentic/affect/contagion/helpers/meme.rb +97 -0
- data/lib/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion.rb +125 -0
- data/lib/legion/extensions/agentic/affect/contagion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/contagion.rb +19 -0
- data/lib/legion/extensions/agentic/affect/defusion/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/constants.rb +64 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/defusion_engine.rb +167 -0
- data/lib/legion/extensions/agentic/affect/defusion/helpers/thought.rb +92 -0
- data/lib/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion.rb +127 -0
- data/lib/legion/extensions/agentic/affect/defusion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/defusion.rb +19 -0
- data/lib/legion/extensions/agentic/affect/emotion/actors/momentum_decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/emotion/client.rb +36 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/baseline.rb +52 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/momentum.rb +52 -0
- data/lib/legion/extensions/agentic/affect/emotion/helpers/valence.rb +92 -0
- data/lib/legion/extensions/agentic/affect/emotion/runners/gut.rb +102 -0
- data/lib/legion/extensions/agentic/affect/emotion/runners/valence.rb +120 -0
- data/lib/legion/extensions/agentic/affect/emotion/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/emotion.rb +20 -0
- data/lib/legion/extensions/agentic/affect/empathy/client.rb +21 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/constants.rb +54 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/mental_model.rb +185 -0
- data/lib/legion/extensions/agentic/affect/empathy/helpers/model_store.rb +88 -0
- data/lib/legion/extensions/agentic/affect/empathy/runners/empathy.rb +173 -0
- data/lib/legion/extensions/agentic/affect/empathy/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/empathy.rb +20 -0
- data/lib/legion/extensions/agentic/affect/fatigue/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/constants.rb +54 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/energy_model.rb +181 -0
- data/lib/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store.rb +146 -0
- data/lib/legion/extensions/agentic/affect/fatigue/runners/fatigue.rb +89 -0
- data/lib/legion/extensions/agentic/affect/fatigue/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/fatigue.rb +19 -0
- data/lib/legion/extensions/agentic/affect/flow/client.rb +25 -0
- data/lib/legion/extensions/agentic/affect/flow/helpers/constants.rb +84 -0
- data/lib/legion/extensions/agentic/affect/flow/helpers/flow_detector.rb +166 -0
- data/lib/legion/extensions/agentic/affect/flow/runners/flow.rb +129 -0
- data/lib/legion/extensions/agentic/affect/flow/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/flow.rb +18 -0
- data/lib/legion/extensions/agentic/affect/interoception/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/interoception/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb +152 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/constants.rb +68 -0
- data/lib/legion/extensions/agentic/affect/interoception/helpers/somatic_marker.rb +75 -0
- data/lib/legion/extensions/agentic/affect/interoception/runners/interoception.rb +101 -0
- data/lib/legion/extensions/agentic/affect/interoception/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/interoception.rb +20 -0
- data/lib/legion/extensions/agentic/affect/mood/client.rb +21 -0
- data/lib/legion/extensions/agentic/affect/mood/helpers/constants.rb +78 -0
- data/lib/legion/extensions/agentic/affect/mood/helpers/mood_state.rb +154 -0
- data/lib/legion/extensions/agentic/affect/mood/runners/mood.rb +122 -0
- data/lib/legion/extensions/agentic/affect/mood/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/mood.rb +18 -0
- data/lib/legion/extensions/agentic/affect/motivation/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/constants.rb +48 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/drive_state.rb +98 -0
- data/lib/legion/extensions/agentic/affect/motivation/helpers/motivation_store.rb +106 -0
- data/lib/legion/extensions/agentic/affect/motivation/runners/motivation.rb +165 -0
- data/lib/legion/extensions/agentic/affect/motivation/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/motivation.rb +19 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate.rb +45 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/client.rb +28 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/constants.rb +82 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event.rb +98 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer.rb +88 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +153 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +164 -0
- data/lib/legion/extensions/agentic/affect/reappraisal/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/reappraisal.rb +20 -0
- data/lib/legion/extensions/agentic/affect/regulation/client.rb +25 -0
- data/lib/legion/extensions/agentic/affect/regulation/helpers/constants.rb +71 -0
- data/lib/legion/extensions/agentic/affect/regulation/helpers/regulation_model.rb +175 -0
- data/lib/legion/extensions/agentic/affect/regulation/runners/emotional_regulation.rb +127 -0
- data/lib/legion/extensions/agentic/affect/regulation/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/regulation.rb +18 -0
- data/lib/legion/extensions/agentic/affect/resilience/client.rb +27 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker.rb +130 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/constants.rb +79 -0
- data/lib/legion/extensions/agentic/affect/resilience/helpers/resilience_model.rb +165 -0
- data/lib/legion/extensions/agentic/affect/resilience/runners/resilience.rb +150 -0
- data/lib/legion/extensions/agentic/affect/resilience/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/resilience.rb +19 -0
- data/lib/legion/extensions/agentic/affect/resonance/client.rb +24 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/category.rb +75 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/constants.rb +47 -0
- data/lib/legion/extensions/agentic/affect/resonance/helpers/resonance_engine.rb +115 -0
- data/lib/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance.rb +94 -0
- data/lib/legion/extensions/agentic/affect/resonance/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/resonance.rb +19 -0
- data/lib/legion/extensions/agentic/affect/reward/client.rb +26 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/constants.rb +67 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/reward_signal.rb +178 -0
- data/lib/legion/extensions/agentic/affect/reward/helpers/reward_store.rb +142 -0
- data/lib/legion/extensions/agentic/affect/reward/runners/reward.rb +92 -0
- data/lib/legion/extensions/agentic/affect/reward/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/reward.rb +19 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/client.rb +29 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/body_state.rb +69 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store.rb +160 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker.rb +74 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker.rb +132 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker/version.rb +13 -0
- data/lib/legion/extensions/agentic/affect/somatic_marker.rb +20 -0
- data/lib/legion/extensions/agentic/affect/version.rb +11 -0
- data/lib/legion/extensions/agentic/affect.rb +34 -0
- data/spec/legion/extensions/agentic/affect/appraisal/client_spec.rb +52 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine_spec.rb +161 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_spec.rb +175 -0
- data/spec/legion/extensions/agentic/affect/appraisal/helpers/constants_spec.rb +49 -0
- data/spec/legion/extensions/agentic/affect/appraisal/runners/appraisal_spec.rb +116 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/client_spec.rb +62 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine_spec.rb +316 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective_spec.rb +132 -0
- data/spec/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy_spec.rb +200 -0
- data/spec/legion/extensions/agentic/affect/contagion/client_spec.rb +63 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/constants_spec.rb +86 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/contagion_engine_spec.rb +241 -0
- data/spec/legion/extensions/agentic/affect/contagion/helpers/meme_spec.rb +160 -0
- data/spec/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion_spec.rb +211 -0
- data/spec/legion/extensions/agentic/affect/defusion/client_spec.rb +80 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/constants_spec.rb +84 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/defusion_engine_spec.rb +250 -0
- data/spec/legion/extensions/agentic/affect/defusion/helpers/thought_spec.rb +178 -0
- data/spec/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion_spec.rb +185 -0
- data/spec/legion/extensions/agentic/affect/emotion/actors/momentum_decay_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/emotion/client_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/baseline_spec.rb +48 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/momentum_spec.rb +45 -0
- data/spec/legion/extensions/agentic/affect/emotion/helpers/valence_spec.rb +91 -0
- data/spec/legion/extensions/agentic/affect/emotion/runners/gut_spec.rb +73 -0
- data/spec/legion/extensions/agentic/affect/emotion/runners/valence_spec.rb +67 -0
- data/spec/legion/extensions/agentic/affect/empathy/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/constants_spec.rb +23 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/mental_model_spec.rb +150 -0
- data/spec/legion/extensions/agentic/affect/empathy/helpers/model_store_spec.rb +94 -0
- data/spec/legion/extensions/agentic/affect/empathy/runners/empathy_spec.rb +127 -0
- data/spec/legion/extensions/agentic/affect/fatigue/client_spec.rb +66 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/constants_spec.rb +130 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/energy_model_spec.rb +281 -0
- data/spec/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store_spec.rb +157 -0
- data/spec/legion/extensions/agentic/affect/fatigue/runners/fatigue_spec.rb +127 -0
- data/spec/legion/extensions/agentic/affect/flow/client_spec.rb +58 -0
- data/spec/legion/extensions/agentic/affect/flow/helpers/constants_spec.rb +112 -0
- data/spec/legion/extensions/agentic/affect/flow/helpers/flow_detector_spec.rb +268 -0
- data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +222 -0
- data/spec/legion/extensions/agentic/affect/interoception/client_spec.rb +52 -0
- data/spec/legion/extensions/agentic/affect/interoception/helpers/body_budget_spec.rb +178 -0
- data/spec/legion/extensions/agentic/affect/interoception/helpers/somatic_marker_spec.rb +120 -0
- data/spec/legion/extensions/agentic/affect/interoception/runners/interoception_spec.rb +108 -0
- data/spec/legion/extensions/agentic/affect/mood/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/affect/mood/helpers/constants_spec.rb +29 -0
- data/spec/legion/extensions/agentic/affect/mood/helpers/mood_state_spec.rb +94 -0
- data/spec/legion/extensions/agentic/affect/mood/runners/mood_spec.rb +71 -0
- data/spec/legion/extensions/agentic/affect/motivation/client_spec.rb +35 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/constants_spec.rb +111 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/drive_state_spec.rb +183 -0
- data/spec/legion/extensions/agentic/affect/motivation/helpers/motivation_store_spec.rb +185 -0
- data/spec/legion/extensions/agentic/affect/motivation/runners/motivation_spec.rb +248 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate_spec.rb +46 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/client_spec.rb +64 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/constants_spec.rb +102 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event_spec.rb +177 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer_spec.rb +161 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +211 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +312 -0
- data/spec/legion/extensions/agentic/affect/regulation/client_spec.rb +61 -0
- data/spec/legion/extensions/agentic/affect/regulation/helpers/constants_spec.rb +108 -0
- data/spec/legion/extensions/agentic/affect/regulation/helpers/regulation_model_spec.rb +200 -0
- data/spec/legion/extensions/agentic/affect/regulation/runners/emotional_regulation_spec.rb +190 -0
- data/spec/legion/extensions/agentic/affect/resilience/client_spec.rb +36 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker_spec.rb +164 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/constants_spec.rb +78 -0
- data/spec/legion/extensions/agentic/affect/resilience/helpers/resilience_model_spec.rb +133 -0
- data/spec/legion/extensions/agentic/affect/resilience/runners/resilience_spec.rb +150 -0
- data/spec/legion/extensions/agentic/affect/resonance/client_spec.rb +66 -0
- data/spec/legion/extensions/agentic/affect/resonance/cognitive_resonance_spec.rb +27 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/category_spec.rb +146 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/constants_spec.rb +104 -0
- data/spec/legion/extensions/agentic/affect/resonance/helpers/resonance_engine_spec.rb +189 -0
- data/spec/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance_spec.rb +197 -0
- data/spec/legion/extensions/agentic/affect/reward/client_spec.rb +42 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/constants_spec.rb +91 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/reward_signal_spec.rb +296 -0
- data/spec/legion/extensions/agentic/affect/reward/helpers/reward_store_spec.rb +167 -0
- data/spec/legion/extensions/agentic/affect/reward/runners/reward_spec.rb +149 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/client_spec.rb +83 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/body_state_spec.rb +155 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store_spec.rb +233 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker_spec.rb +172 -0
- data/spec/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker_spec.rb +181 -0
- data/spec/spec_helper.rb +46 -0
- metadata +302 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Affect
|
|
7
|
+
module CognitiveEmpathy
|
|
8
|
+
module Helpers
|
|
9
|
+
class EmpathyEngine
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :perspectives, :contagion_level, :history
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@perspectives = {}
|
|
16
|
+
@contagion_level = 0.0
|
|
17
|
+
@counter = 0
|
|
18
|
+
@history = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def take_perspective(agent_id:, perspective_type:, predicted_state:, confidence:)
|
|
22
|
+
return nil if @perspectives.size >= MAX_PERSPECTIVES
|
|
23
|
+
return nil unless PERSPECTIVE_TYPES.include?(perspective_type)
|
|
24
|
+
|
|
25
|
+
@counter += 1
|
|
26
|
+
id = :"perspective_#{@counter}"
|
|
27
|
+
perspective = Perspective.new(
|
|
28
|
+
id: id,
|
|
29
|
+
agent_id: agent_id,
|
|
30
|
+
perspective_type: perspective_type,
|
|
31
|
+
predicted_state: predicted_state,
|
|
32
|
+
confidence: confidence
|
|
33
|
+
)
|
|
34
|
+
@perspectives[id] = perspective
|
|
35
|
+
record_event(:perspective_taken, id: id, agent_id: agent_id)
|
|
36
|
+
perspective
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def record_outcome(perspective_id:, actual_state:)
|
|
40
|
+
perspective = @perspectives[perspective_id]
|
|
41
|
+
return nil unless perspective
|
|
42
|
+
|
|
43
|
+
perspective.record_actual(actual_state: actual_state)
|
|
44
|
+
record_event(:outcome_recorded, id: perspective_id, accuracy: perspective.accuracy)
|
|
45
|
+
perspective
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def empathic_accuracy(agent_id:)
|
|
49
|
+
agent_perspectives = perspectives_for(agent_id: agent_id)
|
|
50
|
+
resolved = agent_perspectives.select(&:resolved?)
|
|
51
|
+
return DEFAULT_ACCURACY if resolved.empty?
|
|
52
|
+
|
|
53
|
+
resolved.sum(&:accuracy) / resolved.size
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def overall_accuracy
|
|
57
|
+
resolved = @perspectives.values.select(&:resolved?)
|
|
58
|
+
return DEFAULT_ACCURACY if resolved.empty?
|
|
59
|
+
|
|
60
|
+
resolved.sum(&:accuracy) / resolved.size
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def emotional_contagion(emotion_valence:, intensity:)
|
|
64
|
+
intensity_f = intensity.to_f.clamp(0.0, 1.0)
|
|
65
|
+
absorption = CONTAGION_RATE * intensity_f
|
|
66
|
+
@contagion_level = (@contagion_level + absorption).clamp(0.0, 1.0)
|
|
67
|
+
record_event(:contagion, valence: emotion_valence, intensity: intensity_f,
|
|
68
|
+
level: @contagion_level)
|
|
69
|
+
@contagion_level
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def contagion_decay
|
|
73
|
+
@contagion_level = [@contagion_level - CONTAGION_DECAY, 0.0].max
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def empathic_state
|
|
77
|
+
return :immersed if @contagion_level >= 0.75
|
|
78
|
+
return :resonating if @contagion_level >= 0.45
|
|
79
|
+
return :observing if @contagion_level >= 0.15
|
|
80
|
+
|
|
81
|
+
:detached
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def perspectives_for(agent_id:)
|
|
85
|
+
@perspectives.values.select { |p| p.agent_id == agent_id }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def most_accurate_agent
|
|
89
|
+
agent_accuracies = build_agent_accuracies
|
|
90
|
+
return nil if agent_accuracies.empty?
|
|
91
|
+
|
|
92
|
+
agent_accuracies.max_by { |_, acc| acc }&.first
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def least_accurate_agent
|
|
96
|
+
agent_accuracies = build_agent_accuracies
|
|
97
|
+
return nil if agent_accuracies.empty?
|
|
98
|
+
|
|
99
|
+
agent_accuracies.min_by { |_, acc| acc }&.first
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def tick
|
|
103
|
+
contagion_decay
|
|
104
|
+
prune_old_perspectives
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def to_h
|
|
109
|
+
{
|
|
110
|
+
perspective_count: @perspectives.size,
|
|
111
|
+
resolved_count: @perspectives.values.count(&:resolved?),
|
|
112
|
+
overall_accuracy: overall_accuracy.round(4),
|
|
113
|
+
contagion_level: @contagion_level.round(4),
|
|
114
|
+
empathic_state: empathic_state,
|
|
115
|
+
history_size: @history.size
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def build_agent_accuracies
|
|
122
|
+
agent_ids = @perspectives.values.map(&:agent_id).uniq
|
|
123
|
+
accuracies = {}
|
|
124
|
+
agent_ids.each do |aid|
|
|
125
|
+
resolved = perspectives_for(agent_id: aid).select(&:resolved?)
|
|
126
|
+
next if resolved.empty?
|
|
127
|
+
|
|
128
|
+
accuracies[aid] = resolved.sum(&:accuracy) / resolved.size
|
|
129
|
+
end
|
|
130
|
+
accuracies
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def prune_old_perspectives
|
|
134
|
+
resolved = @perspectives.select { |_, p| p.resolved? }
|
|
135
|
+
return unless resolved.size > MAX_PERSPECTIVES / 2
|
|
136
|
+
|
|
137
|
+
oldest_keys = resolved.keys.first(resolved.size - (MAX_PERSPECTIVES / 4))
|
|
138
|
+
oldest_keys.each { |k| @perspectives.delete(k) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def record_event(type, **details)
|
|
142
|
+
@history << { type: type, at: Time.now.utc }.merge(details)
|
|
143
|
+
@history.shift while @history.size > MAX_HISTORY
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Affect
|
|
7
|
+
module CognitiveEmpathy
|
|
8
|
+
module Helpers
|
|
9
|
+
class Perspective
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :agent_id, :perspective_type, :predicted_state, :actual_state,
|
|
13
|
+
:confidence, :accuracy
|
|
14
|
+
|
|
15
|
+
def initialize(id:, agent_id:, perspective_type: :cognitive, predicted_state: {}, confidence: 0.5)
|
|
16
|
+
@id = id
|
|
17
|
+
@agent_id = agent_id
|
|
18
|
+
@perspective_type = perspective_type
|
|
19
|
+
@predicted_state = predicted_state
|
|
20
|
+
@confidence = confidence.to_f.clamp(0.0, 1.0)
|
|
21
|
+
@actual_state = nil
|
|
22
|
+
@accuracy = DEFAULT_ACCURACY
|
|
23
|
+
@created_at = Time.now.utc
|
|
24
|
+
@resolved_at = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def record_actual(actual_state:)
|
|
28
|
+
@actual_state = actual_state
|
|
29
|
+
@resolved_at = Time.now.utc
|
|
30
|
+
error = compute_error(predicted_state, actual_state)
|
|
31
|
+
observed_accuracy = (1.0 - error).clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
|
|
32
|
+
@accuracy = ((1.0 - ACCURACY_ALPHA) * @accuracy) + (ACCURACY_ALPHA * observed_accuracy)
|
|
33
|
+
@accuracy = @accuracy.clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def accurate?
|
|
38
|
+
@accuracy > 0.6
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolved?
|
|
42
|
+
!@actual_state.nil?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
{
|
|
47
|
+
id: @id,
|
|
48
|
+
agent_id: @agent_id,
|
|
49
|
+
perspective_type: @perspective_type,
|
|
50
|
+
predicted_state: @predicted_state,
|
|
51
|
+
actual_state: @actual_state,
|
|
52
|
+
confidence: @confidence.round(4),
|
|
53
|
+
accuracy: @accuracy.round(4),
|
|
54
|
+
accurate: accurate?,
|
|
55
|
+
resolved: resolved?,
|
|
56
|
+
created_at: @created_at,
|
|
57
|
+
resolved_at: @resolved_at
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def compute_error(predicted, actual)
|
|
64
|
+
return 0.0 if predicted.empty? && actual.empty?
|
|
65
|
+
return 1.0 if predicted.empty? || actual.empty?
|
|
66
|
+
|
|
67
|
+
keys = (predicted.keys | actual.keys)
|
|
68
|
+
return 1.0 if keys.empty?
|
|
69
|
+
|
|
70
|
+
total_error = keys.sum do |k|
|
|
71
|
+
p_val = numeric_value(predicted[k])
|
|
72
|
+
a_val = numeric_value(actual[k])
|
|
73
|
+
(p_val - a_val).abs
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
(total_error / keys.size).clamp(0.0, 1.0)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def numeric_value(val)
|
|
80
|
+
return val.to_f if val.is_a?(Numeric)
|
|
81
|
+
return 1.0 if val == true
|
|
82
|
+
return 0.0 if val == false || val.nil?
|
|
83
|
+
|
|
84
|
+
0.5
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Affect
|
|
7
|
+
module CognitiveEmpathy
|
|
8
|
+
module Runners
|
|
9
|
+
module CognitiveEmpathy
|
|
10
|
+
include Helpers::Constants
|
|
11
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
12
|
+
|
|
13
|
+
def take_empathic_perspective(agent_id:, perspective_type:, predicted_state:, confidence: 0.5, **)
|
|
14
|
+
perspective = engine.take_perspective(
|
|
15
|
+
agent_id: agent_id,
|
|
16
|
+
perspective_type: perspective_type,
|
|
17
|
+
predicted_state: predicted_state,
|
|
18
|
+
confidence: confidence
|
|
19
|
+
)
|
|
20
|
+
return { success: false, reason: :limit_or_invalid_type } unless perspective
|
|
21
|
+
|
|
22
|
+
{ success: true, perspective_id: perspective.id, agent_id: agent_id,
|
|
23
|
+
perspective_type: perspective_type }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def record_empathic_outcome(perspective_id:, actual_state:, **)
|
|
27
|
+
perspective = engine.record_outcome(perspective_id: perspective_id, actual_state: actual_state)
|
|
28
|
+
return { success: false, reason: :not_found } unless perspective
|
|
29
|
+
|
|
30
|
+
{ success: true, perspective_id: perspective_id,
|
|
31
|
+
accuracy: perspective.accuracy.round(4), accurate: perspective.accurate? }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def empathic_accuracy_for(agent_id:, **)
|
|
35
|
+
accuracy = engine.empathic_accuracy(agent_id: agent_id)
|
|
36
|
+
label = accuracy_label(accuracy)
|
|
37
|
+
{ success: true, agent_id: agent_id, accuracy: accuracy.round(4), label: label }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def overall_empathic_accuracy(**)
|
|
41
|
+
accuracy = engine.overall_accuracy
|
|
42
|
+
label = accuracy_label(accuracy)
|
|
43
|
+
{ success: true, accuracy: accuracy.round(4), label: label }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def apply_emotional_contagion(emotion_valence:, intensity:, **)
|
|
47
|
+
level = engine.emotional_contagion(emotion_valence: emotion_valence, intensity: intensity)
|
|
48
|
+
{ success: true, contagion_level: level.round(4), empathic_state: engine.empathic_state }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def current_empathic_state(**)
|
|
52
|
+
{ success: true, empathic_state: engine.empathic_state,
|
|
53
|
+
contagion_level: engine.contagion_level.round(4) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def perspectives_for_agent(agent_id:, **)
|
|
57
|
+
list = engine.perspectives_for(agent_id: agent_id).map(&:to_h)
|
|
58
|
+
{ success: true, agent_id: agent_id, perspectives: list, count: list.size }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def empathic_blind_spots(**)
|
|
62
|
+
least = engine.least_accurate_agent
|
|
63
|
+
most = engine.most_accurate_agent
|
|
64
|
+
{ success: true, least_accurate_agent: least, most_accurate_agent: most,
|
|
65
|
+
overall_accuracy: engine.overall_accuracy.round(4) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update_cognitive_empathy(**)
|
|
69
|
+
engine.tick
|
|
70
|
+
{ success: true }.merge(engine.to_h)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def cognitive_empathy_stats(**)
|
|
74
|
+
{ success: true }.merge(engine.to_h)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def engine
|
|
80
|
+
@engine ||= Helpers::EmpathyEngine.new
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def accuracy_label(accuracy)
|
|
84
|
+
ACCURACY_LABELS.each { |range, lbl| return lbl if range.cover?(accuracy) }
|
|
85
|
+
:blind
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'cognitive_empathy/version'
|
|
4
|
+
require_relative 'cognitive_empathy/helpers/constants'
|
|
5
|
+
require_relative 'cognitive_empathy/helpers/empathy_engine'
|
|
6
|
+
require_relative 'cognitive_empathy/helpers/perspective'
|
|
7
|
+
require_relative 'cognitive_empathy/runners/cognitive_empathy'
|
|
8
|
+
require_relative 'cognitive_empathy/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Agentic
|
|
13
|
+
module Affect
|
|
14
|
+
module CognitiveEmpathy
|
|
15
|
+
# Sub-module for cognitive empathy: affective and cognitive empathy modeling
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/affect/contagion/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/affect/contagion/helpers/meme'
|
|
5
|
+
require 'legion/extensions/agentic/affect/contagion/helpers/contagion_engine'
|
|
6
|
+
require 'legion/extensions/agentic/affect/contagion/runners/cognitive_contagion'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Affect
|
|
12
|
+
module Contagion
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::CognitiveContagion
|
|
15
|
+
|
|
16
|
+
def initialize(**)
|
|
17
|
+
@engine = Helpers::ContagionEngine.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :engine
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Affect
|
|
7
|
+
module Contagion
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
MAX_AGENTS = 200
|
|
11
|
+
MAX_MEMES = 500
|
|
12
|
+
DEFAULT_VIRULENCE = 0.3
|
|
13
|
+
DEFAULT_RESISTANCE = 0.5
|
|
14
|
+
TRANSMISSION_RATE = 0.15
|
|
15
|
+
RECOVERY_RATE = 0.05
|
|
16
|
+
IMMUNITY_BOOST = 0.1
|
|
17
|
+
|
|
18
|
+
VIRULENCE_LABELS = {
|
|
19
|
+
(0.8..) => :pandemic,
|
|
20
|
+
(0.6...0.8) => :epidemic,
|
|
21
|
+
(0.4...0.6) => :endemic,
|
|
22
|
+
(0.2...0.4) => :sporadic,
|
|
23
|
+
(..0.2) => :contained
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
STATUS_LABELS = %i[susceptible exposed infected recovered immune].freeze
|
|
27
|
+
CONTAGION_TYPES = %i[emotional belief behavioral cognitive].freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Affect
|
|
7
|
+
module Contagion
|
|
8
|
+
module Helpers
|
|
9
|
+
class ContagionEngine
|
|
10
|
+
attr_reader :memes, :agent_resistance
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@memes = {}
|
|
14
|
+
@agent_resistance = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_meme(label:, contagion_type: :cognitive, virulence: Constants::DEFAULT_VIRULENCE)
|
|
18
|
+
return { error: :too_many_memes } if @memes.size >= Constants::MAX_MEMES
|
|
19
|
+
|
|
20
|
+
meme = Meme.new(label: label, contagion_type: contagion_type, virulence: virulence)
|
|
21
|
+
@memes[meme.id] = meme
|
|
22
|
+
meme
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def register_agent(agent_id:, resistance: Constants::DEFAULT_RESISTANCE)
|
|
26
|
+
return { error: :too_many_agents } if @agent_resistance.size >= Constants::MAX_AGENTS
|
|
27
|
+
|
|
28
|
+
@agent_resistance[agent_id] = resistance.clamp(0.0, 1.0).round(10)
|
|
29
|
+
{ agent_id: agent_id, resistance: @agent_resistance[agent_id] }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def attempt_transmission(meme_id:, source_agent_id:, target_agent_id:)
|
|
33
|
+
meme = @memes.fetch(meme_id, nil)
|
|
34
|
+
return { transmitted: false, reason: :meme_not_found } unless meme
|
|
35
|
+
return { transmitted: false, reason: :source_not_carrying } unless meme.carrying?(agent_id: source_agent_id)
|
|
36
|
+
|
|
37
|
+
resistance = @agent_resistance.fetch(target_agent_id, Constants::DEFAULT_RESISTANCE)
|
|
38
|
+
probability = (meme.virulence * Constants::TRANSMISSION_RATE * (1.0 - resistance)).round(10)
|
|
39
|
+
|
|
40
|
+
if rand <= probability
|
|
41
|
+
result = meme.infect!(agent_id: target_agent_id)
|
|
42
|
+
{ transmitted: result == :infected, reason: result, meme_id: meme_id,
|
|
43
|
+
source: source_agent_id, target: target_agent_id, probability: probability }
|
|
44
|
+
else
|
|
45
|
+
{ transmitted: false, reason: :blocked_by_resistance, meme_id: meme_id,
|
|
46
|
+
source: source_agent_id, target: target_agent_id, probability: probability }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def recover_agent(meme_id:, agent_id:)
|
|
51
|
+
meme = @memes.fetch(meme_id, nil)
|
|
52
|
+
return { recovered: false, reason: :meme_not_found } unless meme
|
|
53
|
+
|
|
54
|
+
result = meme.recover!(agent_id: agent_id)
|
|
55
|
+
boost_resistance(agent_id)
|
|
56
|
+
{ recovered: result == :recovered, agent_id: agent_id, meme_id: meme_id, result: result }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def immunize_agent(meme_id:, agent_id:)
|
|
60
|
+
meme = @memes.fetch(meme_id, nil)
|
|
61
|
+
return { immunized: false, reason: :meme_not_found } unless meme
|
|
62
|
+
|
|
63
|
+
result = meme.immunize!(agent_id: agent_id)
|
|
64
|
+
{ immunized: true, agent_id: agent_id, meme_id: meme_id, result: result }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def spread_step(meme_id:)
|
|
68
|
+
meme = @memes.fetch(meme_id, nil)
|
|
69
|
+
return { transmissions: 0, reason: :meme_not_found } unless meme
|
|
70
|
+
|
|
71
|
+
susceptible = susceptible_agents(meme_id: meme_id)
|
|
72
|
+
carriers = meme.carriers.to_a
|
|
73
|
+
transmissions = 0
|
|
74
|
+
|
|
75
|
+
carriers.each do |carrier_id|
|
|
76
|
+
susceptible.each do |target_id|
|
|
77
|
+
result = attempt_transmission(meme_id: meme_id, source_agent_id: carrier_id,
|
|
78
|
+
target_agent_id: target_id)
|
|
79
|
+
transmissions += 1 if result[:transmitted]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
recoveries = apply_natural_recovery(meme_id: meme_id)
|
|
84
|
+
{ meme_id: meme_id, transmissions: transmissions, recoveries: recoveries,
|
|
85
|
+
carrier_count: meme.carrier_count }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def epidemic_report(meme_id:)
|
|
89
|
+
meme = @memes.fetch(meme_id, nil)
|
|
90
|
+
return { error: :meme_not_found } unless meme
|
|
91
|
+
|
|
92
|
+
total = @agent_resistance.size
|
|
93
|
+
infected = meme.carriers.size
|
|
94
|
+
recovered = meme.recovered.size
|
|
95
|
+
immune = meme.immune.size
|
|
96
|
+
susceptible_count = [total - infected - recovered - immune, 0].max
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
meme_id: meme_id,
|
|
100
|
+
label: meme.label,
|
|
101
|
+
contagion_type: meme.contagion_type,
|
|
102
|
+
virulence: meme.virulence,
|
|
103
|
+
virulence_label: meme.virulence_label,
|
|
104
|
+
susceptible: susceptible_count,
|
|
105
|
+
infected: infected,
|
|
106
|
+
recovered: recovered,
|
|
107
|
+
immune: immune,
|
|
108
|
+
total_agents: total,
|
|
109
|
+
total_transmissions: meme.total_transmissions,
|
|
110
|
+
transmission_rate: meme.transmission_rate
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def most_viral(limit: 5)
|
|
115
|
+
@memes.values
|
|
116
|
+
.sort_by { |m| -m.virulence }
|
|
117
|
+
.first(limit)
|
|
118
|
+
.map(&:to_h)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def agent_status(agent_id:, meme_id:)
|
|
122
|
+
meme = @memes.fetch(meme_id, nil)
|
|
123
|
+
return { status: :unknown, reason: :meme_not_found } unless meme
|
|
124
|
+
|
|
125
|
+
status = if meme.immune.include?(agent_id)
|
|
126
|
+
:immune
|
|
127
|
+
elsif meme.carrying?(agent_id: agent_id)
|
|
128
|
+
:infected
|
|
129
|
+
elsif meme.recovered.include?(agent_id)
|
|
130
|
+
:recovered
|
|
131
|
+
else
|
|
132
|
+
:susceptible
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
{ agent_id: agent_id, meme_id: meme_id, status: status,
|
|
136
|
+
resistance: @agent_resistance.fetch(agent_id, Constants::DEFAULT_RESISTANCE) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def susceptible_agents(meme_id:)
|
|
140
|
+
meme = @memes.fetch(meme_id, nil)
|
|
141
|
+
return [] unless meme
|
|
142
|
+
|
|
143
|
+
@agent_resistance.keys.reject do |id|
|
|
144
|
+
meme.carrying?(agent_id: id) || meme.immune.include?(id)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def to_h
|
|
149
|
+
{
|
|
150
|
+
meme_count: @memes.size,
|
|
151
|
+
agent_count: @agent_resistance.size,
|
|
152
|
+
memes: @memes.values.map(&:to_h),
|
|
153
|
+
agent_resistance: @agent_resistance
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def boost_resistance(agent_id)
|
|
160
|
+
current = @agent_resistance.fetch(agent_id, Constants::DEFAULT_RESISTANCE)
|
|
161
|
+
@agent_resistance[agent_id] = (current + Constants::IMMUNITY_BOOST).clamp(0.0, 1.0).round(10)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def apply_natural_recovery(meme_id:)
|
|
165
|
+
meme = @memes.fetch(meme_id, nil)
|
|
166
|
+
return 0 unless meme
|
|
167
|
+
|
|
168
|
+
recoveries = 0
|
|
169
|
+
meme.carriers.to_a.each do |carrier_id|
|
|
170
|
+
next unless rand <= Constants::RECOVERY_RATE
|
|
171
|
+
|
|
172
|
+
meme.recover!(agent_id: carrier_id)
|
|
173
|
+
boost_resistance(carrier_id)
|
|
174
|
+
recoveries += 1
|
|
175
|
+
end
|
|
176
|
+
recoveries
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|