superinstance-equipment-consensus-engine 1.0.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.
@@ -0,0 +1,507 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'types'
4
+
5
+ module SuperInstance
6
+ module Equipment
7
+ module ConsensusEngine
8
+ # ConflictResolution - Resolve disagreements between perspectives
9
+ #
10
+ # Implements strategies for detecting and resolving conflicts when
11
+ # Pathos, Logos, and Ethos perspectives disagree on a proposition.
12
+ class ConflictResolution
13
+ # Default resolution configuration
14
+ DEFAULT_CONFIG = {
15
+ max_attempts: 3,
16
+ min_confidence: 0.6,
17
+ allow_escalation: true
18
+ }.freeze
19
+
20
+ # Creates a new ConflictResolution instance
21
+ # @param config [Hash] Optional configuration overrides
22
+ def initialize(config = {})
23
+ @config = DEFAULT_CONFIG.merge(config)
24
+ @resolution_history = []
25
+ end
26
+
27
+ # Resolves a conflict between perspectives
28
+ # @param conflict [Hash] The conflict to resolve
29
+ # @param opinions [Array<Hash>] Current perspective opinions
30
+ # @return [Hash] The resolution result
31
+ def resolve(conflict, opinions)
32
+ # Select appropriate resolution strategy based on conflict type
33
+ strategy = select_strategy(conflict, opinions)
34
+
35
+ # Apply the selected strategy
36
+ result = apply_strategy(strategy, conflict, opinions)
37
+
38
+ # If resolution failed, try alternative strategies
39
+ attempts = 1
40
+ while !result[:resolved] && attempts < @config[:max_attempts]
41
+ alternative_strategy = select_alternative_strategy(strategy, conflict, result)
42
+ result = apply_strategy(alternative_strategy, conflict, opinions)
43
+ attempts += 1
44
+ end
45
+
46
+ # Record the resolution attempt
47
+ @resolution_history << {
48
+ conflict: conflict,
49
+ result: result,
50
+ timestamp: Time.now
51
+ }
52
+
53
+ result
54
+ end
55
+
56
+ # Gets the resolution history
57
+ # @return [Array<Hash>] Resolution history
58
+ def get_history
59
+ @resolution_history.dup
60
+ end
61
+
62
+ # Clears the resolution history
63
+ def clear_history
64
+ @resolution_history.clear
65
+ end
66
+
67
+ # Gets statistics about resolutions
68
+ # @return [Hash] Resolution statistics
69
+ def get_stats
70
+ total = @resolution_history.length
71
+ successful = @resolution_history.count { |r| r[:result][:resolved] }
72
+
73
+ strategy_usage = Hash.new(0)
74
+ total_confidence = 0.0
75
+
76
+ @resolution_history.each do |entry|
77
+ strategy_usage[entry[:result][:strategy]] += 1
78
+ total_confidence += entry[:result][:confidence]
79
+ end
80
+
81
+ {
82
+ total_resolutions: total,
83
+ successful_resolutions: successful,
84
+ strategy_usage: strategy_usage,
85
+ average_confidence: total > 0 ? total_confidence / total : 0
86
+ }
87
+ end
88
+
89
+ private
90
+
91
+ # Selects the best resolution strategy for a conflict
92
+ def select_strategy(conflict, opinions)
93
+ # Strategy selection based on conflict type
94
+ strategy_map = {
95
+ fundamental_disagreement: %i[compromise reframing escalation],
96
+ uncertainty: %i[deliberation_extension suspension],
97
+ partial_disagreement: %i[weighted_voting conditional_approval],
98
+ weight_imbalance: %i[perspective_dominance reframing],
99
+ cross_perspective_tension: %i[compromise deliberation_extension],
100
+ context_inconsistency: %i[reframing deliberation_extension]
101
+ }
102
+
103
+ strategies = strategy_map[conflict[:type]] || %i[weighted_voting]
104
+
105
+ # Consider severity
106
+ if conflict[:severity] == :critical && @config[:allow_escalation]
107
+ return :escalation
108
+ end
109
+
110
+ if conflict[:severity] == :high
111
+ # Use more deliberative strategies for high severity
112
+ deliberative_strategy = strategies.find do |s|
113
+ s == :compromise || s == :deliberation_extension
114
+ end
115
+ return deliberative_strategy if deliberative_strategy
116
+ end
117
+
118
+ # Use first available strategy
119
+ strategies[0] || :weighted_voting
120
+ end
121
+
122
+ # Selects an alternative strategy when primary fails
123
+ def select_alternative_strategy(failed_strategy, conflict, previous_result)
124
+ preferred_strategies = %i[
125
+ weighted_voting compromise conditional_approval
126
+ deliberation_extension reframing escalation
127
+ ]
128
+
129
+ current_index = preferred_strategies.index(failed_strategy)
130
+ next_index = current_index + 1
131
+
132
+ if next_index < preferred_strategies.length
133
+ return preferred_strategies[next_index]
134
+ end
135
+
136
+ # If we've tried all preferred strategies, use escalation as last resort
137
+ return :escalation if @config[:allow_escalation]
138
+
139
+ :suspension
140
+ end
141
+
142
+ # Applies a specific resolution strategy
143
+ def apply_strategy(strategy, conflict, opinions)
144
+ case strategy
145
+ when :weighted_voting
146
+ apply_weighted_voting(conflict, opinions)
147
+ when :deliberation_extension
148
+ apply_deliberation_extension(conflict, opinions)
149
+ when :reframing
150
+ apply_reframing(conflict, opinions)
151
+ when :escalation
152
+ apply_escalation(conflict, opinions)
153
+ when :compromise
154
+ apply_compromise(conflict, opinions)
155
+ when :conditional_approval
156
+ apply_conditional_approval(conflict, opinions)
157
+ when :suspension
158
+ apply_suspension(conflict, opinions)
159
+ when :perspective_dominance
160
+ apply_perspective_dominance(conflict, opinions)
161
+ else
162
+ create_unresolved_result(strategy, conflict)
163
+ end
164
+ end
165
+
166
+ # Applies weighted voting strategy
167
+ def apply_weighted_voting(conflict, opinions)
168
+ # Calculate weighted scores
169
+ positive_score = 0.0
170
+ negative_score = 0.0
171
+
172
+ opinions.each do |opinion|
173
+ verdict = opinion[:verdict].downcase
174
+ is_positive = VerdictIndicators::POSITIVE.any? { |p| verdict.include?(p) }
175
+ is_negative = VerdictIndicators::NEGATIVE.any? { |n| verdict.include?(n) }
176
+
177
+ if is_positive
178
+ positive_score += opinion[:weight] * opinion[:confidence]
179
+ elsif is_negative
180
+ negative_score += opinion[:weight] * opinion[:confidence]
181
+ end
182
+ end
183
+
184
+ total_score = positive_score + negative_score
185
+ verdict = positive_score > negative_score ? 'Proceed with the proposition' : 'Decline the proposition'
186
+ confidence = total_score > 0 ? (positive_score - negative_score).abs / total_score : 0.5
187
+
188
+ accepted = opinions.select do |op|
189
+ verdict_lower = op[:verdict].downcase
190
+ is_positive = VerdictIndicators::POSITIVE.any? { |p| verdict_lower.include?(p) }
191
+ (positive_score > negative_score) == is_positive
192
+ end.map { |op| op[:perspective] }
193
+
194
+ rejected = opinions.select do |op|
195
+ verdict_lower = op[:verdict].downcase
196
+ is_positive = VerdictIndicators::POSITIVE.any? { |p| verdict_lower.include?(p) }
197
+ (positive_score > negative_score) != is_positive
198
+ end.map { |op| op[:perspective] }
199
+
200
+ {
201
+ resolved: confidence >= @config[:min_confidence],
202
+ strategy: :weighted_voting,
203
+ outcome: "Weighted voting resulted in #{positive_score > negative_score ? 'approval' : 'rejection'} with #{(confidence * 100).round(1)}% confidence",
204
+ verdict: verdict,
205
+ confidence: confidence,
206
+ remaining_concerns: extract_remaining_concerns(opinions),
207
+ accepted_by: accepted,
208
+ rejected_by: rejected
209
+ }
210
+ end
211
+
212
+ # Applies deliberation extension strategy
213
+ def apply_deliberation_extension(conflict, opinions)
214
+ focus_areas = identify_focus_areas(conflict, opinions)
215
+
216
+ {
217
+ resolved: false,
218
+ strategy: :deliberation_extension,
219
+ outcome: 'Additional deliberation rounds required',
220
+ confidence: 0,
221
+ conditions: [
222
+ "Conduct additional deliberation focusing on: #{focus_areas.join(', ')}",
223
+ 'Re-evaluate after gathering more information'
224
+ ],
225
+ remaining_concerns: extract_remaining_concerns(opinions),
226
+ accepted_by: [],
227
+ rejected_by: []
228
+ }
229
+ end
230
+
231
+ # Applies reframing strategy
232
+ def apply_reframing(conflict, opinions)
233
+ # Generate reframed propositions
234
+ reframed_options = generate_reframed_options(opinions)
235
+
236
+ {
237
+ resolved: false,
238
+ strategy: :reframing,
239
+ outcome: 'Proposition should be reframed to address conflicting perspectives',
240
+ confidence: 0,
241
+ conditions: reframed_options.each_with_index.map { |option, i| "Alternative #{i + 1}: #{option}" },
242
+ remaining_concerns: extract_remaining_concerns(opinions),
243
+ accepted_by: [],
244
+ rejected_by: []
245
+ }
246
+ end
247
+
248
+ # Applies escalation strategy
249
+ def apply_escalation(conflict, opinions)
250
+ summary = summarize_conflict(conflict, opinions)
251
+
252
+ {
253
+ resolved: false,
254
+ strategy: :escalation,
255
+ outcome: 'Decision requires escalation to higher authority',
256
+ confidence: 0,
257
+ conditions: [
258
+ 'Present all perspectives to decision-making authority',
259
+ "Conflict summary: #{summary}",
260
+ 'Await authoritative decision'
261
+ ],
262
+ remaining_concerns: extract_remaining_concerns(opinions),
263
+ accepted_by: [],
264
+ rejected_by: opinions.map { |op| op[:perspective] }
265
+ }
266
+ end
267
+
268
+ # Applies compromise strategy
269
+ def apply_compromise(conflict, opinions)
270
+ mediation = mediate_between_perspectives(opinions)
271
+
272
+ {
273
+ resolved: mediation[:success],
274
+ strategy: :compromise,
275
+ outcome: mediation[:success] ? 'Compromise reached between perspectives' : 'Unable to reach compromise',
276
+ verdict: mediation[:position],
277
+ confidence: mediation[:confidence],
278
+ conditions: mediation[:compromises].map { |c| "#{c[:perspective]}: #{c[:concession]}" },
279
+ remaining_concerns: mediation[:needs_further_deliberation] ? ['Further deliberation may refine the compromise'] : [],
280
+ accepted_by: mediation[:compromises].map { |c| c[:perspective] },
281
+ rejected_by: []
282
+ }
283
+ end
284
+
285
+ # Applies conditional approval strategy
286
+ def apply_conditional_approval(conflict, opinions)
287
+ conditions = generate_conditions(opinions)
288
+ accepting_perspectives = opinions.filter { |op| op[:confidence] >= 0.5 }
289
+
290
+ {
291
+ resolved: conditions.any?,
292
+ strategy: :conditional_approval,
293
+ outcome: 'Conditional approval granted subject to requirements',
294
+ verdict: 'Conditionally approved',
295
+ confidence: 0.65,
296
+ conditions: conditions,
297
+ remaining_concerns: extract_remaining_concerns(opinions),
298
+ accepted_by: accepting_perspectives.map { |op| op[:perspective] },
299
+ rejected_by: opinions.filter { |op| op[:confidence] < 0.5 }.map { |op| op[:perspective] }
300
+ }
301
+ end
302
+
303
+ # Applies suspension strategy
304
+ def apply_suspension(conflict, opinions)
305
+ requirements = identify_information_requirements(conflict, opinions)
306
+
307
+ {
308
+ resolved: false,
309
+ strategy: :suspension,
310
+ outcome: 'Decision suspended pending additional information',
311
+ confidence: 0,
312
+ conditions: requirements,
313
+ remaining_concerns: extract_remaining_concerns(opinions),
314
+ accepted_by: [],
315
+ rejected_by: []
316
+ }
317
+ end
318
+
319
+ # Applies perspective dominance strategy
320
+ def apply_perspective_dominance(conflict, opinions)
321
+ # Find the perspective with highest weighted confidence
322
+ weighted_opinions = opinions.map do |op|
323
+ op.merge(weighted_confidence: op[:confidence] * op[:weight])
324
+ end
325
+
326
+ weighted_opinions.sort! { |a, b| b[:weighted_confidence] <=> a[:weighted_confidence] }
327
+ dominant_opinion = weighted_opinions[0]
328
+
329
+ {
330
+ resolved: true,
331
+ strategy: :perspective_dominance,
332
+ outcome: "Decision based on dominant #{dominant_opinion[:perspective]} perspective",
333
+ verdict: dominant_opinion[:verdict],
334
+ confidence: dominant_opinion[:confidence],
335
+ conditions: [
336
+ "Note: Decision primarily driven by #{dominant_opinion[:perspective]} considerations",
337
+ 'Other perspectives had lower weighted confidence'
338
+ ],
339
+ remaining_concerns: opinions
340
+ .reject { |op| op[:perspective] == dominant_opinion[:perspective] }
341
+ .flat_map { |op| op[:concerns] },
342
+ accepted_by: [dominant_opinion[:perspective]],
343
+ rejected_by: opinions
344
+ .reject { |op| op[:perspective] == dominant_opinion[:perspective] }
345
+ .map { |op| op[:perspective] }
346
+ }
347
+ end
348
+
349
+ # Creates an unresolved result
350
+ def create_unresolved_result(strategy, conflict)
351
+ {
352
+ resolved: false,
353
+ strategy: strategy,
354
+ outcome: 'Unable to resolve conflict with available strategies',
355
+ confidence: 0,
356
+ remaining_concerns: [conflict[:description]],
357
+ accepted_by: [],
358
+ rejected_by: conflict[:perspectives]
359
+ }
360
+ end
361
+
362
+ # Identifies focus areas for extended deliberation
363
+ def identify_focus_areas(conflict, opinions)
364
+ areas = []
365
+
366
+ opinions.each do |opinion|
367
+ if opinion[:concerns]&.any?
368
+ areas.concat(opinion[:concerns].slice(0, 2))
369
+ end
370
+ end
371
+
372
+ areas.uniq.slice(0, 5)
373
+ end
374
+
375
+ # Generates reframed proposition options
376
+ def generate_reframed_options(opinions)
377
+ options = []
378
+
379
+ logos_op = opinions.find { |op| op[:perspective] == :logos }
380
+ pathos_op = opinions.find { |op| op[:perspective] == :pathos }
381
+ ethos_op = opinions.find { |op| op[:perspective] == :ethos }
382
+
383
+ if logos_op && pathos_op
384
+ options << "Consider a modified approach that addresses #{logos_op[:arguments][0] || 'logical concerns'} while being mindful of #{pathos_op[:arguments][0] || 'emotional impact'}"
385
+ end
386
+
387
+ if ethos_op
388
+ options << "Ensure the proposition aligns with #{ethos_op[:arguments][0] || 'ethical principles'} before proceeding"
389
+ end
390
+
391
+ options
392
+ end
393
+
394
+ # Summarizes a conflict for escalation
395
+ def summarize_conflict(conflict, opinions)
396
+ perspective_views = opinions.map do |op|
397
+ "#{op[:perspective]}: #{op[:verdict][0..50]}..."
398
+ end.join('; ')
399
+
400
+ "#{conflict[:type]} (#{conflict[:severity]}): #{conflict[:description]}. Perspectives: #{perspective_views}"
401
+ end
402
+
403
+ # Mediates between perspectives to find compromise
404
+ def mediate_between_perspectives(opinions)
405
+ compromises = []
406
+
407
+ # Find common ground
408
+ common_arguments = find_common_arguments(opinions)
409
+
410
+ # Generate compromise positions for each perspective
411
+ opinions.each do |opinion|
412
+ concession = generate_concession(opinion, common_arguments)
413
+ compromises << {
414
+ perspective: opinion[:perspective],
415
+ concession: concession
416
+ }
417
+ end
418
+
419
+ # Calculate confidence based on compromise alignment
420
+ avg_confidence = opinions.sum { |op| op[:confidence] }.to_f / opinions.length
421
+
422
+ {
423
+ success: compromises.length == opinions.length,
424
+ position: "Compromise position: #{common_arguments.join(' and ')}",
425
+ confidence: avg_confidence * 0.8,
426
+ compromises: compromises,
427
+ needs_further_deliberation: avg_confidence < @config[:min_confidence]
428
+ }
429
+ end
430
+
431
+ # Finds common arguments across perspectives
432
+ def find_common_arguments(opinions)
433
+ all_arguments = opinions.flat_map { |op| op[:arguments] || [] }
434
+
435
+ unique_arguments = []
436
+ all_arguments.each do |arg|
437
+ is_similar = unique_arguments.any? { |existing| are_arguments_similar(existing, arg) }
438
+ unless is_similar
439
+ unique_arguments << arg
440
+ end
441
+ end
442
+
443
+ unique_arguments.slice(0, 3)
444
+ end
445
+
446
+ # Checks if two arguments are semantically similar
447
+ def are_arguments_similar(arg1, arg2)
448
+ words1 = arg1.downcase.split(/\s+/)
449
+ words2 = arg2.downcase.split(/\s+/)
450
+ common_words = words1 & words2
451
+ common_words.length >= 3
452
+ end
453
+
454
+ # Generates a concession for a perspective
455
+ def generate_concession(opinion, common_arguments)
456
+ if opinion[:concerns]&.any?
457
+ "Willing to proceed if #{opinion[:concerns][0].downcase} is addressed"
458
+ else
459
+ "Accepting common ground on #{common_arguments[0] || 'shared interests'}"
460
+ end
461
+ end
462
+
463
+ # Generates conditions for conditional approval
464
+ def generate_conditions(opinions)
465
+ conditions = []
466
+
467
+ opinions.each do |opinion|
468
+ if opinion[:concerns]&.any?
469
+ conditions << "#{opinion[:perspective]}: Address #{opinion[:concerns][0].downcase}"
470
+ end
471
+ end
472
+
473
+ conditions.slice(0, 5)
474
+ end
475
+
476
+ # Identifies information requirements for suspension
477
+ def identify_information_requirements(conflict, opinions)
478
+ requirements = []
479
+
480
+ case conflict[:type]
481
+ when :fundamental_disagreement
482
+ requirements << 'Gather additional evidence to resolve fundamental disagreements'
483
+ when :uncertainty
484
+ requirements << 'Obtain more information to reduce uncertainty'
485
+ when :context_inconsistency
486
+ requirements << 'Clarify contextual factors causing interpretation differences'
487
+ else
488
+ requirements << 'Conduct further analysis before proceeding'
489
+ end
490
+
491
+ opinions.each do |opinion|
492
+ if opinion[:confidence] < 0.5
493
+ requirements << "Address #{opinion[:perspective]} perspective's low confidence"
494
+ end
495
+ end
496
+
497
+ requirements
498
+ end
499
+
500
+ # Extracts remaining concerns from opinions
501
+ def extract_remaining_concerns(opinions)
502
+ opinions.flat_map { |op| op[:concerns] || [] }.slice(0, 5)
503
+ end
504
+ end
505
+ end
506
+ end
507
+ end