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.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-affect.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/affect/appraisal/client.rb +20 -0
  8. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal.rb +112 -0
  9. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine.rb +129 -0
  10. data/lib/legion/extensions/agentic/affect/appraisal/helpers/constants.rb +43 -0
  11. data/lib/legion/extensions/agentic/affect/appraisal/runners/appraisal.rb +105 -0
  12. data/lib/legion/extensions/agentic/affect/appraisal/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/affect/appraisal.rb +19 -0
  14. data/lib/legion/extensions/agentic/affect/cognitive_empathy/client.rb +19 -0
  15. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/constants.rb +37 -0
  16. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine.rb +151 -0
  17. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective.rb +92 -0
  18. data/lib/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy.rb +93 -0
  19. data/lib/legion/extensions/agentic/affect/cognitive_empathy/version.rb +13 -0
  20. data/lib/legion/extensions/agentic/affect/cognitive_empathy.rb +20 -0
  21. data/lib/legion/extensions/agentic/affect/contagion/client.rb +28 -0
  22. data/lib/legion/extensions/agentic/affect/contagion/helpers/constants.rb +34 -0
  23. data/lib/legion/extensions/agentic/affect/contagion/helpers/contagion_engine.rb +184 -0
  24. data/lib/legion/extensions/agentic/affect/contagion/helpers/meme.rb +97 -0
  25. data/lib/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion.rb +125 -0
  26. data/lib/legion/extensions/agentic/affect/contagion/version.rb +13 -0
  27. data/lib/legion/extensions/agentic/affect/contagion.rb +19 -0
  28. data/lib/legion/extensions/agentic/affect/defusion/client.rb +28 -0
  29. data/lib/legion/extensions/agentic/affect/defusion/helpers/constants.rb +64 -0
  30. data/lib/legion/extensions/agentic/affect/defusion/helpers/defusion_engine.rb +167 -0
  31. data/lib/legion/extensions/agentic/affect/defusion/helpers/thought.rb +92 -0
  32. data/lib/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion.rb +127 -0
  33. data/lib/legion/extensions/agentic/affect/defusion/version.rb +13 -0
  34. data/lib/legion/extensions/agentic/affect/defusion.rb +19 -0
  35. data/lib/legion/extensions/agentic/affect/emotion/actors/momentum_decay.rb +45 -0
  36. data/lib/legion/extensions/agentic/affect/emotion/client.rb +36 -0
  37. data/lib/legion/extensions/agentic/affect/emotion/helpers/baseline.rb +52 -0
  38. data/lib/legion/extensions/agentic/affect/emotion/helpers/momentum.rb +52 -0
  39. data/lib/legion/extensions/agentic/affect/emotion/helpers/valence.rb +92 -0
  40. data/lib/legion/extensions/agentic/affect/emotion/runners/gut.rb +102 -0
  41. data/lib/legion/extensions/agentic/affect/emotion/runners/valence.rb +120 -0
  42. data/lib/legion/extensions/agentic/affect/emotion/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/affect/emotion.rb +20 -0
  44. data/lib/legion/extensions/agentic/affect/empathy/client.rb +21 -0
  45. data/lib/legion/extensions/agentic/affect/empathy/helpers/constants.rb +54 -0
  46. data/lib/legion/extensions/agentic/affect/empathy/helpers/mental_model.rb +185 -0
  47. data/lib/legion/extensions/agentic/affect/empathy/helpers/model_store.rb +88 -0
  48. data/lib/legion/extensions/agentic/affect/empathy/runners/empathy.rb +173 -0
  49. data/lib/legion/extensions/agentic/affect/empathy/version.rb +13 -0
  50. data/lib/legion/extensions/agentic/affect/empathy.rb +20 -0
  51. data/lib/legion/extensions/agentic/affect/fatigue/client.rb +26 -0
  52. data/lib/legion/extensions/agentic/affect/fatigue/helpers/constants.rb +54 -0
  53. data/lib/legion/extensions/agentic/affect/fatigue/helpers/energy_model.rb +181 -0
  54. data/lib/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store.rb +146 -0
  55. data/lib/legion/extensions/agentic/affect/fatigue/runners/fatigue.rb +89 -0
  56. data/lib/legion/extensions/agentic/affect/fatigue/version.rb +13 -0
  57. data/lib/legion/extensions/agentic/affect/fatigue.rb +19 -0
  58. data/lib/legion/extensions/agentic/affect/flow/client.rb +25 -0
  59. data/lib/legion/extensions/agentic/affect/flow/helpers/constants.rb +84 -0
  60. data/lib/legion/extensions/agentic/affect/flow/helpers/flow_detector.rb +166 -0
  61. data/lib/legion/extensions/agentic/affect/flow/runners/flow.rb +129 -0
  62. data/lib/legion/extensions/agentic/affect/flow/version.rb +13 -0
  63. data/lib/legion/extensions/agentic/affect/flow.rb +18 -0
  64. data/lib/legion/extensions/agentic/affect/interoception/actors/decay.rb +45 -0
  65. data/lib/legion/extensions/agentic/affect/interoception/client.rb +28 -0
  66. data/lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb +152 -0
  67. data/lib/legion/extensions/agentic/affect/interoception/helpers/constants.rb +68 -0
  68. data/lib/legion/extensions/agentic/affect/interoception/helpers/somatic_marker.rb +75 -0
  69. data/lib/legion/extensions/agentic/affect/interoception/runners/interoception.rb +101 -0
  70. data/lib/legion/extensions/agentic/affect/interoception/version.rb +13 -0
  71. data/lib/legion/extensions/agentic/affect/interoception.rb +20 -0
  72. data/lib/legion/extensions/agentic/affect/mood/client.rb +21 -0
  73. data/lib/legion/extensions/agentic/affect/mood/helpers/constants.rb +78 -0
  74. data/lib/legion/extensions/agentic/affect/mood/helpers/mood_state.rb +154 -0
  75. data/lib/legion/extensions/agentic/affect/mood/runners/mood.rb +122 -0
  76. data/lib/legion/extensions/agentic/affect/mood/version.rb +13 -0
  77. data/lib/legion/extensions/agentic/affect/mood.rb +18 -0
  78. data/lib/legion/extensions/agentic/affect/motivation/client.rb +26 -0
  79. data/lib/legion/extensions/agentic/affect/motivation/helpers/constants.rb +48 -0
  80. data/lib/legion/extensions/agentic/affect/motivation/helpers/drive_state.rb +98 -0
  81. data/lib/legion/extensions/agentic/affect/motivation/helpers/motivation_store.rb +106 -0
  82. data/lib/legion/extensions/agentic/affect/motivation/runners/motivation.rb +165 -0
  83. data/lib/legion/extensions/agentic/affect/motivation/version.rb +13 -0
  84. data/lib/legion/extensions/agentic/affect/motivation.rb +19 -0
  85. data/lib/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate.rb +45 -0
  86. data/lib/legion/extensions/agentic/affect/reappraisal/client.rb +28 -0
  87. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/constants.rb +82 -0
  88. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event.rb +98 -0
  89. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer.rb +88 -0
  90. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +153 -0
  91. data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +164 -0
  92. data/lib/legion/extensions/agentic/affect/reappraisal/version.rb +13 -0
  93. data/lib/legion/extensions/agentic/affect/reappraisal.rb +20 -0
  94. data/lib/legion/extensions/agentic/affect/regulation/client.rb +25 -0
  95. data/lib/legion/extensions/agentic/affect/regulation/helpers/constants.rb +71 -0
  96. data/lib/legion/extensions/agentic/affect/regulation/helpers/regulation_model.rb +175 -0
  97. data/lib/legion/extensions/agentic/affect/regulation/runners/emotional_regulation.rb +127 -0
  98. data/lib/legion/extensions/agentic/affect/regulation/version.rb +13 -0
  99. data/lib/legion/extensions/agentic/affect/regulation.rb +18 -0
  100. data/lib/legion/extensions/agentic/affect/resilience/client.rb +27 -0
  101. data/lib/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker.rb +130 -0
  102. data/lib/legion/extensions/agentic/affect/resilience/helpers/constants.rb +79 -0
  103. data/lib/legion/extensions/agentic/affect/resilience/helpers/resilience_model.rb +165 -0
  104. data/lib/legion/extensions/agentic/affect/resilience/runners/resilience.rb +150 -0
  105. data/lib/legion/extensions/agentic/affect/resilience/version.rb +13 -0
  106. data/lib/legion/extensions/agentic/affect/resilience.rb +19 -0
  107. data/lib/legion/extensions/agentic/affect/resonance/client.rb +24 -0
  108. data/lib/legion/extensions/agentic/affect/resonance/helpers/category.rb +75 -0
  109. data/lib/legion/extensions/agentic/affect/resonance/helpers/constants.rb +47 -0
  110. data/lib/legion/extensions/agentic/affect/resonance/helpers/resonance_engine.rb +115 -0
  111. data/lib/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance.rb +94 -0
  112. data/lib/legion/extensions/agentic/affect/resonance/version.rb +13 -0
  113. data/lib/legion/extensions/agentic/affect/resonance.rb +19 -0
  114. data/lib/legion/extensions/agentic/affect/reward/client.rb +26 -0
  115. data/lib/legion/extensions/agentic/affect/reward/helpers/constants.rb +67 -0
  116. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_signal.rb +178 -0
  117. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_store.rb +142 -0
  118. data/lib/legion/extensions/agentic/affect/reward/runners/reward.rb +92 -0
  119. data/lib/legion/extensions/agentic/affect/reward/version.rb +13 -0
  120. data/lib/legion/extensions/agentic/affect/reward.rb +19 -0
  121. data/lib/legion/extensions/agentic/affect/somatic_marker/actors/decay.rb +45 -0
  122. data/lib/legion/extensions/agentic/affect/somatic_marker/client.rb +29 -0
  123. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/body_state.rb +69 -0
  124. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/constants.rb +43 -0
  125. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store.rb +160 -0
  126. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker.rb +74 -0
  127. data/lib/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker.rb +132 -0
  128. data/lib/legion/extensions/agentic/affect/somatic_marker/version.rb +13 -0
  129. data/lib/legion/extensions/agentic/affect/somatic_marker.rb +20 -0
  130. data/lib/legion/extensions/agentic/affect/version.rb +11 -0
  131. data/lib/legion/extensions/agentic/affect.rb +34 -0
  132. data/spec/legion/extensions/agentic/affect/appraisal/client_spec.rb +52 -0
  133. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine_spec.rb +161 -0
  134. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_spec.rb +175 -0
  135. data/spec/legion/extensions/agentic/affect/appraisal/helpers/constants_spec.rb +49 -0
  136. data/spec/legion/extensions/agentic/affect/appraisal/runners/appraisal_spec.rb +116 -0
  137. data/spec/legion/extensions/agentic/affect/cognitive_empathy/client_spec.rb +62 -0
  138. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine_spec.rb +316 -0
  139. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective_spec.rb +132 -0
  140. data/spec/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy_spec.rb +200 -0
  141. data/spec/legion/extensions/agentic/affect/contagion/client_spec.rb +63 -0
  142. data/spec/legion/extensions/agentic/affect/contagion/helpers/constants_spec.rb +86 -0
  143. data/spec/legion/extensions/agentic/affect/contagion/helpers/contagion_engine_spec.rb +241 -0
  144. data/spec/legion/extensions/agentic/affect/contagion/helpers/meme_spec.rb +160 -0
  145. data/spec/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion_spec.rb +211 -0
  146. data/spec/legion/extensions/agentic/affect/defusion/client_spec.rb +80 -0
  147. data/spec/legion/extensions/agentic/affect/defusion/helpers/constants_spec.rb +84 -0
  148. data/spec/legion/extensions/agentic/affect/defusion/helpers/defusion_engine_spec.rb +250 -0
  149. data/spec/legion/extensions/agentic/affect/defusion/helpers/thought_spec.rb +178 -0
  150. data/spec/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion_spec.rb +185 -0
  151. data/spec/legion/extensions/agentic/affect/emotion/actors/momentum_decay_spec.rb +46 -0
  152. data/spec/legion/extensions/agentic/affect/emotion/client_spec.rb +46 -0
  153. data/spec/legion/extensions/agentic/affect/emotion/helpers/baseline_spec.rb +48 -0
  154. data/spec/legion/extensions/agentic/affect/emotion/helpers/momentum_spec.rb +45 -0
  155. data/spec/legion/extensions/agentic/affect/emotion/helpers/valence_spec.rb +91 -0
  156. data/spec/legion/extensions/agentic/affect/emotion/runners/gut_spec.rb +73 -0
  157. data/spec/legion/extensions/agentic/affect/emotion/runners/valence_spec.rb +67 -0
  158. data/spec/legion/extensions/agentic/affect/empathy/client_spec.rb +20 -0
  159. data/spec/legion/extensions/agentic/affect/empathy/helpers/constants_spec.rb +23 -0
  160. data/spec/legion/extensions/agentic/affect/empathy/helpers/mental_model_spec.rb +150 -0
  161. data/spec/legion/extensions/agentic/affect/empathy/helpers/model_store_spec.rb +94 -0
  162. data/spec/legion/extensions/agentic/affect/empathy/runners/empathy_spec.rb +127 -0
  163. data/spec/legion/extensions/agentic/affect/fatigue/client_spec.rb +66 -0
  164. data/spec/legion/extensions/agentic/affect/fatigue/helpers/constants_spec.rb +130 -0
  165. data/spec/legion/extensions/agentic/affect/fatigue/helpers/energy_model_spec.rb +281 -0
  166. data/spec/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store_spec.rb +157 -0
  167. data/spec/legion/extensions/agentic/affect/fatigue/runners/fatigue_spec.rb +127 -0
  168. data/spec/legion/extensions/agentic/affect/flow/client_spec.rb +58 -0
  169. data/spec/legion/extensions/agentic/affect/flow/helpers/constants_spec.rb +112 -0
  170. data/spec/legion/extensions/agentic/affect/flow/helpers/flow_detector_spec.rb +268 -0
  171. data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +222 -0
  172. data/spec/legion/extensions/agentic/affect/interoception/client_spec.rb +52 -0
  173. data/spec/legion/extensions/agentic/affect/interoception/helpers/body_budget_spec.rb +178 -0
  174. data/spec/legion/extensions/agentic/affect/interoception/helpers/somatic_marker_spec.rb +120 -0
  175. data/spec/legion/extensions/agentic/affect/interoception/runners/interoception_spec.rb +108 -0
  176. data/spec/legion/extensions/agentic/affect/mood/client_spec.rb +20 -0
  177. data/spec/legion/extensions/agentic/affect/mood/helpers/constants_spec.rb +29 -0
  178. data/spec/legion/extensions/agentic/affect/mood/helpers/mood_state_spec.rb +94 -0
  179. data/spec/legion/extensions/agentic/affect/mood/runners/mood_spec.rb +71 -0
  180. data/spec/legion/extensions/agentic/affect/motivation/client_spec.rb +35 -0
  181. data/spec/legion/extensions/agentic/affect/motivation/helpers/constants_spec.rb +111 -0
  182. data/spec/legion/extensions/agentic/affect/motivation/helpers/drive_state_spec.rb +183 -0
  183. data/spec/legion/extensions/agentic/affect/motivation/helpers/motivation_store_spec.rb +185 -0
  184. data/spec/legion/extensions/agentic/affect/motivation/runners/motivation_spec.rb +248 -0
  185. data/spec/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate_spec.rb +46 -0
  186. data/spec/legion/extensions/agentic/affect/reappraisal/client_spec.rb +64 -0
  187. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/constants_spec.rb +102 -0
  188. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event_spec.rb +177 -0
  189. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer_spec.rb +161 -0
  190. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +211 -0
  191. data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +312 -0
  192. data/spec/legion/extensions/agentic/affect/regulation/client_spec.rb +61 -0
  193. data/spec/legion/extensions/agentic/affect/regulation/helpers/constants_spec.rb +108 -0
  194. data/spec/legion/extensions/agentic/affect/regulation/helpers/regulation_model_spec.rb +200 -0
  195. data/spec/legion/extensions/agentic/affect/regulation/runners/emotional_regulation_spec.rb +190 -0
  196. data/spec/legion/extensions/agentic/affect/resilience/client_spec.rb +36 -0
  197. data/spec/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker_spec.rb +164 -0
  198. data/spec/legion/extensions/agentic/affect/resilience/helpers/constants_spec.rb +78 -0
  199. data/spec/legion/extensions/agentic/affect/resilience/helpers/resilience_model_spec.rb +133 -0
  200. data/spec/legion/extensions/agentic/affect/resilience/runners/resilience_spec.rb +150 -0
  201. data/spec/legion/extensions/agentic/affect/resonance/client_spec.rb +66 -0
  202. data/spec/legion/extensions/agentic/affect/resonance/cognitive_resonance_spec.rb +27 -0
  203. data/spec/legion/extensions/agentic/affect/resonance/helpers/category_spec.rb +146 -0
  204. data/spec/legion/extensions/agentic/affect/resonance/helpers/constants_spec.rb +104 -0
  205. data/spec/legion/extensions/agentic/affect/resonance/helpers/resonance_engine_spec.rb +189 -0
  206. data/spec/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance_spec.rb +197 -0
  207. data/spec/legion/extensions/agentic/affect/reward/client_spec.rb +42 -0
  208. data/spec/legion/extensions/agentic/affect/reward/helpers/constants_spec.rb +91 -0
  209. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_signal_spec.rb +296 -0
  210. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_store_spec.rb +167 -0
  211. data/spec/legion/extensions/agentic/affect/reward/runners/reward_spec.rb +149 -0
  212. data/spec/legion/extensions/agentic/affect/somatic_marker/client_spec.rb +83 -0
  213. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/body_state_spec.rb +155 -0
  214. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store_spec.rb +233 -0
  215. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker_spec.rb +172 -0
  216. data/spec/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker_spec.rb +181 -0
  217. data/spec/spec_helper.rb +46 -0
  218. metadata +302 -0
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::EmpathyEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#initialize' do
7
+ it 'starts with empty perspectives' do
8
+ expect(engine.perspectives).to be_empty
9
+ end
10
+
11
+ it 'starts with zero contagion level' do
12
+ expect(engine.contagion_level).to eq(0.0)
13
+ end
14
+
15
+ it 'starts with empty history' do
16
+ expect(engine.history).to be_empty
17
+ end
18
+ end
19
+
20
+ describe '#take_perspective' do
21
+ it 'creates and stores a perspective' do
22
+ p = engine.take_perspective(
23
+ agent_id: :alice,
24
+ perspective_type: :cognitive,
25
+ predicted_state: { valence: 0.6 },
26
+ confidence: 0.7
27
+ )
28
+ expect(p).to be_a(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Perspective)
29
+ expect(engine.perspectives.size).to eq(1)
30
+ end
31
+
32
+ it 'returns nil for invalid perspective_type' do
33
+ result = engine.take_perspective(
34
+ agent_id: :alice,
35
+ perspective_type: :bogus,
36
+ predicted_state: {},
37
+ confidence: 0.5
38
+ )
39
+ expect(result).to be_nil
40
+ end
41
+
42
+ it 'enforces MAX_PERSPECTIVES limit' do
43
+ 50.times do |i|
44
+ engine.take_perspective(
45
+ agent_id: :"agent_#{i}",
46
+ perspective_type: :cognitive,
47
+ predicted_state: {},
48
+ confidence: 0.5
49
+ )
50
+ end
51
+ result = engine.take_perspective(
52
+ agent_id: :overflow,
53
+ perspective_type: :cognitive,
54
+ predicted_state: {},
55
+ confidence: 0.5
56
+ )
57
+ expect(result).to be_nil
58
+ end
59
+
60
+ it 'records event in history' do
61
+ engine.take_perspective(
62
+ agent_id: :bob,
63
+ perspective_type: :affective,
64
+ predicted_state: {},
65
+ confidence: 0.5
66
+ )
67
+ expect(engine.history.size).to eq(1)
68
+ end
69
+
70
+ it 'assigns unique ids' do
71
+ p1 = engine.take_perspective(
72
+ agent_id: :a,
73
+ perspective_type: :cognitive,
74
+ predicted_state: {},
75
+ confidence: 0.5
76
+ )
77
+ p2 = engine.take_perspective(
78
+ agent_id: :b,
79
+ perspective_type: :cognitive,
80
+ predicted_state: {},
81
+ confidence: 0.5
82
+ )
83
+ expect(p1.id).not_to eq(p2.id)
84
+ end
85
+ end
86
+
87
+ describe '#record_outcome' do
88
+ it 'updates the perspective with actual state' do
89
+ p = engine.take_perspective(
90
+ agent_id: :carol,
91
+ perspective_type: :cognitive,
92
+ predicted_state: { valence: 0.7 },
93
+ confidence: 0.8
94
+ )
95
+ perspective_id = p.id
96
+ result = engine.record_outcome(perspective_id: perspective_id, actual_state: { valence: 0.7 })
97
+ expect(result).to be_a(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Perspective)
98
+ expect(result.resolved?).to be true
99
+ end
100
+
101
+ it 'returns nil for unknown perspective_id' do
102
+ result = engine.record_outcome(perspective_id: :bogus_id, actual_state: {})
103
+ expect(result).to be_nil
104
+ end
105
+
106
+ it 'records event in history' do
107
+ p = engine.take_perspective(
108
+ agent_id: :dave,
109
+ perspective_type: :motivational,
110
+ predicted_state: {},
111
+ confidence: 0.5
112
+ )
113
+ perspective_id = p.id
114
+ engine.record_outcome(perspective_id: perspective_id, actual_state: {})
115
+ expect(engine.history.size).to eq(2)
116
+ end
117
+ end
118
+
119
+ describe '#empathic_accuracy' do
120
+ it 'returns default accuracy when no resolved perspectives exist' do
121
+ engine.take_perspective(
122
+ agent_id: :eve,
123
+ perspective_type: :cognitive,
124
+ predicted_state: {},
125
+ confidence: 0.5
126
+ )
127
+ acc = engine.empathic_accuracy(agent_id: :eve)
128
+ expect(acc).to eq(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::DEFAULT_ACCURACY)
129
+ end
130
+
131
+ it 'returns average accuracy for resolved perspectives' do
132
+ p = engine.take_perspective(
133
+ agent_id: :frank,
134
+ perspective_type: :cognitive,
135
+ predicted_state: { v: 0.5 },
136
+ confidence: 0.8
137
+ )
138
+ perspective_id = p.id
139
+ engine.record_outcome(perspective_id: perspective_id, actual_state: { v: 0.5 })
140
+ acc = engine.empathic_accuracy(agent_id: :frank)
141
+ expect(acc).to be_a(Float)
142
+ expect(acc).to be > 0.0
143
+ end
144
+
145
+ it 'returns default for unknown agent' do
146
+ acc = engine.empathic_accuracy(agent_id: :nobody)
147
+ expect(acc).to eq(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::DEFAULT_ACCURACY)
148
+ end
149
+ end
150
+
151
+ describe '#overall_accuracy' do
152
+ it 'returns default when no resolved perspectives' do
153
+ expect(engine.overall_accuracy).to eq(
154
+ Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::DEFAULT_ACCURACY
155
+ )
156
+ end
157
+
158
+ it 'returns average across all agents after outcomes recorded' do
159
+ p1 = engine.take_perspective(
160
+ agent_id: :g, perspective_type: :cognitive, predicted_state: { v: 0.5 }, confidence: 0.8
161
+ )
162
+ p2 = engine.take_perspective(
163
+ agent_id: :h, perspective_type: :affective, predicted_state: { v: 0.5 }, confidence: 0.8
164
+ )
165
+ engine.record_outcome(perspective_id: p1.id, actual_state: { v: 0.5 })
166
+ engine.record_outcome(perspective_id: p2.id, actual_state: { v: 0.5 })
167
+ expect(engine.overall_accuracy).to be > 0.5
168
+ end
169
+ end
170
+
171
+ describe '#emotional_contagion' do
172
+ it 'increases contagion_level' do
173
+ engine.emotional_contagion(emotion_valence: 0.8, intensity: 1.0)
174
+ expect(engine.contagion_level).to be > 0.0
175
+ end
176
+
177
+ it 'clamps intensity to 0..1' do
178
+ engine.emotional_contagion(emotion_valence: 0.5, intensity: 5.0)
179
+ expect(engine.contagion_level).to be <= 1.0
180
+ end
181
+
182
+ it 'records event in history' do
183
+ engine.emotional_contagion(emotion_valence: 0.5, intensity: 0.5)
184
+ expect(engine.history).not_to be_empty
185
+ end
186
+
187
+ it 'returns the new contagion level' do
188
+ level = engine.emotional_contagion(emotion_valence: 0.5, intensity: 0.5)
189
+ expect(level).to be_a(Float)
190
+ expect(level).to be > 0.0
191
+ end
192
+ end
193
+
194
+ describe '#contagion_decay' do
195
+ it 'reduces contagion_level' do
196
+ engine.emotional_contagion(emotion_valence: 0.8, intensity: 1.0)
197
+ before = engine.contagion_level
198
+ engine.contagion_decay
199
+ expect(engine.contagion_level).to be < before
200
+ end
201
+
202
+ it 'does not go below 0' do
203
+ engine.contagion_decay
204
+ expect(engine.contagion_level).to eq(0.0)
205
+ end
206
+ end
207
+
208
+ describe '#empathic_state' do
209
+ it 'returns :detached when contagion is near zero' do
210
+ expect(engine.empathic_state).to eq(:detached)
211
+ end
212
+
213
+ it 'returns :observing when contagion is moderate' do
214
+ 10.times { engine.emotional_contagion(emotion_valence: 0.5, intensity: 0.3) }
215
+ state = engine.empathic_state
216
+ expect(%i[observing resonating immersed detached]).to include(state)
217
+ end
218
+
219
+ it 'returns :immersed when contagion is very high' do
220
+ 100.times { engine.emotional_contagion(emotion_valence: 0.9, intensity: 1.0) }
221
+ expect(engine.empathic_state).to eq(:immersed)
222
+ end
223
+
224
+ it 'returns :resonating at mid-range contagion' do
225
+ # Manually set by absorbing enough
226
+ 50.times { engine.emotional_contagion(emotion_valence: 0.5, intensity: 0.7) }
227
+ state = engine.empathic_state
228
+ expect(%i[resonating immersed]).to include(state)
229
+ end
230
+ end
231
+
232
+ describe '#perspectives_for' do
233
+ it 'returns perspectives matching agent_id' do
234
+ engine.take_perspective(
235
+ agent_id: :ivan,
236
+ perspective_type: :cognitive,
237
+ predicted_state: {},
238
+ confidence: 0.5
239
+ )
240
+ engine.take_perspective(
241
+ agent_id: :jane,
242
+ perspective_type: :affective,
243
+ predicted_state: {},
244
+ confidence: 0.5
245
+ )
246
+ results = engine.perspectives_for(agent_id: :ivan)
247
+ expect(results.size).to eq(1)
248
+ expect(results.first.agent_id).to eq(:ivan)
249
+ end
250
+
251
+ it 'returns empty array for unknown agent' do
252
+ expect(engine.perspectives_for(agent_id: :nobody)).to be_empty
253
+ end
254
+ end
255
+
256
+ describe '#most_accurate_agent and #least_accurate_agent' do
257
+ it 'returns nil when no resolved perspectives exist' do
258
+ expect(engine.most_accurate_agent).to be_nil
259
+ expect(engine.least_accurate_agent).to be_nil
260
+ end
261
+
262
+ context 'with resolved perspectives for two agents' do
263
+ before do
264
+ # Agent A: perfect prediction
265
+ pa = engine.take_perspective(
266
+ agent_id: :agent_a, perspective_type: :cognitive,
267
+ predicted_state: { v: 0.5 }, confidence: 0.9
268
+ )
269
+ engine.record_outcome(perspective_id: pa.id, actual_state: { v: 0.5 })
270
+
271
+ # Agent B: terrible prediction
272
+ pb = engine.take_perspective(
273
+ agent_id: :agent_b, perspective_type: :cognitive,
274
+ predicted_state: { v: 1.0 }, confidence: 0.9
275
+ )
276
+ engine.record_outcome(perspective_id: pb.id, actual_state: { v: 0.0 })
277
+ end
278
+
279
+ it 'identifies most accurate agent' do
280
+ expect(engine.most_accurate_agent).to eq(:agent_a)
281
+ end
282
+
283
+ it 'identifies least accurate agent' do
284
+ expect(engine.least_accurate_agent).to eq(:agent_b)
285
+ end
286
+ end
287
+ end
288
+
289
+ describe '#tick' do
290
+ it 'decays contagion level' do
291
+ engine.emotional_contagion(emotion_valence: 0.5, intensity: 1.0)
292
+ before = engine.contagion_level
293
+ engine.tick
294
+ expect(engine.contagion_level).to be < before
295
+ end
296
+
297
+ it 'returns self' do
298
+ expect(engine.tick).to be(engine)
299
+ end
300
+ end
301
+
302
+ describe '#to_h' do
303
+ it 'returns expected keys' do
304
+ h = engine.to_h
305
+ expect(h).to include(
306
+ :perspective_count, :resolved_count, :overall_accuracy,
307
+ :contagion_level, :empathic_state, :history_size
308
+ )
309
+ end
310
+
311
+ it 'rounds overall_accuracy to 4 places' do
312
+ h = engine.to_h
313
+ expect(h[:overall_accuracy]).to be_a(Float)
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Perspective do
4
+ subject(:perspective) do
5
+ described_class.new(
6
+ id: :persp_one,
7
+ agent_id: :agent_a,
8
+ perspective_type: :cognitive,
9
+ predicted_state: { valence: 0.7, arousal: 0.5 },
10
+ confidence: 0.8
11
+ )
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'sets id' do
16
+ expect(perspective.id).to eq(:persp_one)
17
+ end
18
+
19
+ it 'sets agent_id' do
20
+ expect(perspective.agent_id).to eq(:agent_a)
21
+ end
22
+
23
+ it 'sets perspective_type' do
24
+ expect(perspective.perspective_type).to eq(:cognitive)
25
+ end
26
+
27
+ it 'sets predicted_state' do
28
+ expect(perspective.predicted_state).to eq({ valence: 0.7, arousal: 0.5 })
29
+ end
30
+
31
+ it 'sets confidence' do
32
+ expect(perspective.confidence).to eq(0.8)
33
+ end
34
+
35
+ it 'clamps confidence to 0..1' do
36
+ p = described_class.new(id: :x, agent_id: :a, confidence: 1.5)
37
+ expect(p.confidence).to eq(1.0)
38
+ end
39
+
40
+ it 'starts with nil actual_state' do
41
+ expect(perspective.actual_state).to be_nil
42
+ end
43
+
44
+ it 'starts with default accuracy' do
45
+ expect(perspective.accuracy).to eq(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::DEFAULT_ACCURACY)
46
+ end
47
+
48
+ it 'is not resolved initially' do
49
+ expect(perspective.resolved?).to be false
50
+ end
51
+ end
52
+
53
+ describe '#record_actual' do
54
+ context 'when prediction matches well' do
55
+ it 'increases accuracy above default' do
56
+ perspective.record_actual(actual_state: { valence: 0.7, arousal: 0.5 })
57
+ expect(perspective.accuracy).to be > 0.5
58
+ end
59
+ end
60
+
61
+ context 'when prediction is off' do
62
+ it 'lowers accuracy below default' do
63
+ perspective.record_actual(actual_state: { valence: 0.0, arousal: 0.0 })
64
+ expect(perspective.accuracy).to be < 0.5
65
+ end
66
+ end
67
+
68
+ it 'sets actual_state' do
69
+ actual = { valence: 0.6, arousal: 0.4 }
70
+ perspective.record_actual(actual_state: actual)
71
+ expect(perspective.actual_state).to eq(actual)
72
+ end
73
+
74
+ it 'marks as resolved' do
75
+ perspective.record_actual(actual_state: { valence: 0.6 })
76
+ expect(perspective.resolved?).to be true
77
+ end
78
+
79
+ it 'returns self for chaining' do
80
+ result = perspective.record_actual(actual_state: {})
81
+ expect(result).to be(perspective)
82
+ end
83
+
84
+ it 'clamps accuracy to floor' do
85
+ p = described_class.new(id: :x, agent_id: :a, predicted_state: { v: 1.0 })
86
+ 100.times { p.record_actual(actual_state: { v: 0.0 }) }
87
+ expect(p.accuracy).to be >= Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::ACCURACY_FLOOR
88
+ end
89
+
90
+ it 'clamps accuracy to ceiling' do
91
+ p = described_class.new(id: :x, agent_id: :a, predicted_state: { v: 0.5 })
92
+ 100.times { p.record_actual(actual_state: { v: 0.5 }) }
93
+ expect(p.accuracy).to be <= Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::ACCURACY_CEILING
94
+ end
95
+ end
96
+
97
+ describe '#accurate?' do
98
+ it 'returns false at default accuracy (0.5)' do
99
+ expect(perspective.accurate?).to be false
100
+ end
101
+
102
+ it 'returns true when accuracy is above 0.6 after repeated correct predictions' do
103
+ p = described_class.new(id: :y, agent_id: :b, predicted_state: { v: 0.5 }, confidence: 0.9)
104
+ 10.times { p.record_actual(actual_state: { v: 0.5 }) }
105
+ expect(p.accurate?).to be true
106
+ end
107
+ end
108
+
109
+ describe '#to_h' do
110
+ it 'returns a hash with expected keys' do
111
+ h = perspective.to_h
112
+ expect(h).to include(
113
+ :id, :agent_id, :perspective_type, :predicted_state, :actual_state,
114
+ :confidence, :accuracy, :accurate, :resolved, :created_at, :resolved_at
115
+ )
116
+ end
117
+
118
+ it 'rounds accuracy to 4 decimal places' do
119
+ h = perspective.to_h
120
+ expect(h[:accuracy].to_s).to match(/\A\d+\.\d{1,4}\z/)
121
+ end
122
+
123
+ it 'resolved_at is nil before recording actual' do
124
+ expect(perspective.to_h[:resolved_at]).to be_nil
125
+ end
126
+
127
+ it 'resolved_at is set after recording actual' do
128
+ perspective.record_actual(actual_state: {})
129
+ expect(perspective.to_h[:resolved_at]).not_to be_nil
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Runners::CognitiveEmpathy do
4
+ let(:runner) do
5
+ obj = Object.new
6
+ obj.extend(described_class)
7
+ obj
8
+ end
9
+
10
+ describe '#take_empathic_perspective' do
11
+ it 'creates a perspective and returns success' do
12
+ result = runner.take_empathic_perspective(
13
+ agent_id: :alice,
14
+ perspective_type: :cognitive,
15
+ predicted_state: { valence: 0.7 },
16
+ confidence: 0.8
17
+ )
18
+ expect(result[:success]).to be true
19
+ expect(result[:perspective_id]).to be_a(Symbol)
20
+ expect(result[:agent_id]).to eq(:alice)
21
+ end
22
+
23
+ it 'returns failure for invalid perspective_type' do
24
+ result = runner.take_empathic_perspective(
25
+ agent_id: :alice,
26
+ perspective_type: :invalid_type,
27
+ predicted_state: {},
28
+ confidence: 0.5
29
+ )
30
+ expect(result[:success]).to be false
31
+ expect(result[:reason]).to eq(:limit_or_invalid_type)
32
+ end
33
+
34
+ it 'uses default confidence when not provided' do
35
+ result = runner.take_empathic_perspective(
36
+ agent_id: :bob,
37
+ perspective_type: :affective,
38
+ predicted_state: {}
39
+ )
40
+ expect(result[:success]).to be true
41
+ end
42
+ end
43
+
44
+ describe '#record_empathic_outcome' do
45
+ it 'records actual state and returns accuracy' do
46
+ created = runner.take_empathic_perspective(
47
+ agent_id: :carol,
48
+ perspective_type: :motivational,
49
+ predicted_state: { drive: 0.8 },
50
+ confidence: 0.7
51
+ )
52
+ perspective_id = created[:perspective_id]
53
+ result = runner.record_empathic_outcome(
54
+ perspective_id: perspective_id,
55
+ actual_state: { drive: 0.8 }
56
+ )
57
+ expect(result[:success]).to be true
58
+ expect(result[:accuracy]).to be_a(Float)
59
+ expect(result[:accurate]).to be(true).or be(false)
60
+ end
61
+
62
+ it 'returns failure for unknown perspective_id' do
63
+ result = runner.record_empathic_outcome(
64
+ perspective_id: :nonexistent_perspective,
65
+ actual_state: {}
66
+ )
67
+ expect(result[:success]).to be false
68
+ expect(result[:reason]).to eq(:not_found)
69
+ end
70
+ end
71
+
72
+ describe '#empathic_accuracy_for' do
73
+ it 'returns accuracy for an agent with no history' do
74
+ result = runner.empathic_accuracy_for(agent_id: :dave)
75
+ expect(result[:success]).to be true
76
+ expect(result[:agent_id]).to eq(:dave)
77
+ expect(result[:accuracy]).to be_a(Float)
78
+ expect(result[:label]).to be_a(Symbol)
79
+ end
80
+
81
+ it 'returns updated accuracy after recording outcome' do
82
+ created = runner.take_empathic_perspective(
83
+ agent_id: :eve,
84
+ perspective_type: :situational,
85
+ predicted_state: { stress: 0.3 },
86
+ confidence: 0.6
87
+ )
88
+ perspective_id = created[:perspective_id]
89
+ runner.record_empathic_outcome(
90
+ perspective_id: perspective_id,
91
+ actual_state: { stress: 0.3 }
92
+ )
93
+ result = runner.empathic_accuracy_for(agent_id: :eve)
94
+ expect(result[:success]).to be true
95
+ end
96
+ end
97
+
98
+ describe '#overall_empathic_accuracy' do
99
+ it 'returns success with accuracy and label' do
100
+ result = runner.overall_empathic_accuracy
101
+ expect(result[:success]).to be true
102
+ expect(result[:accuracy]).to be_a(Float)
103
+ expect(result[:label]).to be_a(Symbol)
104
+ end
105
+ end
106
+
107
+ describe '#apply_emotional_contagion' do
108
+ it 'increases contagion level and returns empathic_state' do
109
+ result = runner.apply_emotional_contagion(emotion_valence: 0.8, intensity: 0.9)
110
+ expect(result[:success]).to be true
111
+ expect(result[:contagion_level]).to be > 0.0
112
+ expect(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::EMPATHIC_STATES)
113
+ .to include(result[:empathic_state])
114
+ end
115
+ end
116
+
117
+ describe '#current_empathic_state' do
118
+ it 'returns empathic_state and contagion_level' do
119
+ result = runner.current_empathic_state
120
+ expect(result[:success]).to be true
121
+ expect(Legion::Extensions::Agentic::Affect::CognitiveEmpathy::Helpers::Constants::EMPATHIC_STATES)
122
+ .to include(result[:empathic_state])
123
+ expect(result[:contagion_level]).to be_a(Float)
124
+ end
125
+ end
126
+
127
+ describe '#perspectives_for_agent' do
128
+ it 'returns empty list for agent with no perspectives' do
129
+ result = runner.perspectives_for_agent(agent_id: :nobody)
130
+ expect(result[:success]).to be true
131
+ expect(result[:count]).to eq(0)
132
+ expect(result[:perspectives]).to be_empty
133
+ end
134
+
135
+ it 'returns perspectives after taking some' do
136
+ runner.take_empathic_perspective(
137
+ agent_id: :frank,
138
+ perspective_type: :cognitive,
139
+ predicted_state: {},
140
+ confidence: 0.5
141
+ )
142
+ runner.take_empathic_perspective(
143
+ agent_id: :frank,
144
+ perspective_type: :affective,
145
+ predicted_state: {},
146
+ confidence: 0.5
147
+ )
148
+ result = runner.perspectives_for_agent(agent_id: :frank)
149
+ expect(result[:count]).to eq(2)
150
+ end
151
+ end
152
+
153
+ describe '#empathic_blind_spots' do
154
+ it 'returns nil agents when no perspectives exist' do
155
+ result = runner.empathic_blind_spots
156
+ expect(result[:success]).to be true
157
+ expect(result[:least_accurate_agent]).to be_nil
158
+ expect(result[:most_accurate_agent]).to be_nil
159
+ end
160
+
161
+ it 'returns blind spot info after recording outcomes' do
162
+ p1 = runner.take_empathic_perspective(
163
+ agent_id: :good_agent,
164
+ perspective_type: :cognitive,
165
+ predicted_state: { v: 0.5 },
166
+ confidence: 0.9
167
+ )
168
+ runner.record_empathic_outcome(perspective_id: p1[:perspective_id], actual_state: { v: 0.5 })
169
+
170
+ p2 = runner.take_empathic_perspective(
171
+ agent_id: :bad_agent,
172
+ perspective_type: :cognitive,
173
+ predicted_state: { v: 1.0 },
174
+ confidence: 0.9
175
+ )
176
+ runner.record_empathic_outcome(perspective_id: p2[:perspective_id], actual_state: { v: 0.0 })
177
+
178
+ result = runner.empathic_blind_spots
179
+ expect(result[:success]).to be true
180
+ expect(result[:least_accurate_agent]).to eq(:bad_agent)
181
+ expect(result[:most_accurate_agent]).to eq(:good_agent)
182
+ end
183
+ end
184
+
185
+ describe '#update_cognitive_empathy' do
186
+ it 'ticks and returns stats' do
187
+ result = runner.update_cognitive_empathy
188
+ expect(result[:success]).to be true
189
+ expect(result).to include(:perspective_count, :empathic_state)
190
+ end
191
+ end
192
+
193
+ describe '#cognitive_empathy_stats' do
194
+ it 'returns current stats' do
195
+ result = runner.cognitive_empathy_stats
196
+ expect(result[:success]).to be true
197
+ expect(result).to include(:perspective_count, :overall_accuracy, :empathic_state)
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/contagion/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Contagion::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to create_meme' do
9
+ expect(client).to respond_to(:create_meme)
10
+ end
11
+
12
+ it 'responds to register_agent' do
13
+ expect(client).to respond_to(:register_agent)
14
+ end
15
+
16
+ it 'responds to attempt_transmission' do
17
+ expect(client).to respond_to(:attempt_transmission)
18
+ end
19
+
20
+ it 'responds to recover_agent' do
21
+ expect(client).to respond_to(:recover_agent)
22
+ end
23
+
24
+ it 'responds to infect_agent' do
25
+ expect(client).to respond_to(:infect_agent)
26
+ end
27
+
28
+ it 'responds to immunize_agent' do
29
+ expect(client).to respond_to(:immunize_agent)
30
+ end
31
+
32
+ it 'responds to spread_step' do
33
+ expect(client).to respond_to(:spread_step)
34
+ end
35
+
36
+ it 'responds to epidemic_report' do
37
+ expect(client).to respond_to(:epidemic_report)
38
+ end
39
+
40
+ it 'responds to most_viral' do
41
+ expect(client).to respond_to(:most_viral)
42
+ end
43
+
44
+ it 'responds to agent_status' do
45
+ expect(client).to respond_to(:agent_status)
46
+ end
47
+
48
+ it 'responds to susceptible_agents' do
49
+ expect(client).to respond_to(:susceptible_agents)
50
+ end
51
+
52
+ it 'responds to contagion_status' do
53
+ expect(client).to respond_to(:contagion_status)
54
+ end
55
+
56
+ it 'can perform a full create->infect->report cycle' do
57
+ client.register_agent(agent_id: 'agent-a', resistance: 0.0)
58
+ meme = client.create_meme(label: 'fear', virulence: 0.8)
59
+ client.infect_agent(meme_id: meme[:id], agent_id: 'agent-a')
60
+ report = client.epidemic_report(meme_id: meme[:id])
61
+ expect(report[:infected]).to eq(1)
62
+ end
63
+ end