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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +448 -0
- data/lib/equipment/consensus_engine/conflict_resolution.rb +507 -0
- data/lib/equipment/consensus_engine/consensus_engine.rb +451 -0
- data/lib/equipment/consensus_engine/tripartite_deliberation.rb +645 -0
- data/lib/equipment/consensus_engine/types.rb +229 -0
- data/lib/equipment/consensus_engine/version.rb +9 -0
- data/lib/equipment/consensus_engine/weight_calculator.rb +438 -0
- data/lib/equipment/consensus_engine.rb +17 -0
- metadata +65 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module SuperInstance
|
|
6
|
+
module Equipment
|
|
7
|
+
module ConsensusEngine
|
|
8
|
+
# Types module containing shared type constants and enums
|
|
9
|
+
|
|
10
|
+
# Types of tripartite perspectives
|
|
11
|
+
module PerspectiveType
|
|
12
|
+
PATHOS = :pathos
|
|
13
|
+
LOGOS = :logos
|
|
14
|
+
ETHOS = :ethos
|
|
15
|
+
|
|
16
|
+
ALL = Set[:pathos, :logos, :ethos].freeze
|
|
17
|
+
|
|
18
|
+
def self.valid?(value)
|
|
19
|
+
ALL.include?(value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Alias for TripartitePerspective
|
|
24
|
+
TripartitePerspective = PerspectiveType
|
|
25
|
+
|
|
26
|
+
# Types of domains that affect perspective weighting
|
|
27
|
+
module DomainType
|
|
28
|
+
FACTUAL = :factual
|
|
29
|
+
EMOTIONAL = :emotional
|
|
30
|
+
SENSITIVE = :sensitive
|
|
31
|
+
CREATIVE = :creative
|
|
32
|
+
BALANCED = :balanced
|
|
33
|
+
TECHNICAL = :technical
|
|
34
|
+
SOCIAL = :social
|
|
35
|
+
BUSINESS = :business
|
|
36
|
+
PERSONAL = :personal
|
|
37
|
+
MARITIME = :maritime
|
|
38
|
+
|
|
39
|
+
ALL = Set[
|
|
40
|
+
:factual, :emotional, :sensitive, :creative, :balanced,
|
|
41
|
+
:technical, :social, :business, :personal, :maritime
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
def self.valid?(value)
|
|
45
|
+
ALL.include?(value)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Types of conflicts that can occur between perspectives
|
|
50
|
+
module ConflictType
|
|
51
|
+
FUNDAMENTAL_DISAGREEMENT = :fundamental_disagreement
|
|
52
|
+
UNCERTAINTY = :uncertainty
|
|
53
|
+
PARTIAL_DISAGREEMENT = :partial_disagreement
|
|
54
|
+
WEIGHT_IMBALANCE = :weight_imbalance
|
|
55
|
+
CROSS_PERSPECTIVE_TENSION = :cross_perspective_tension
|
|
56
|
+
CONTEXT_INCONSISTENCY = :context_inconsistency
|
|
57
|
+
|
|
58
|
+
ALL = Set[
|
|
59
|
+
:fundamental_disagreement, :uncertainty, :partial_disagreement,
|
|
60
|
+
:weight_imbalance, :cross_perspective_tension, :context_inconsistency
|
|
61
|
+
].freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Severity levels for conflicts
|
|
65
|
+
module ConflictSeverity
|
|
66
|
+
LOW = :low
|
|
67
|
+
MEDIUM = :medium
|
|
68
|
+
HIGH = :high
|
|
69
|
+
CRITICAL = :critical
|
|
70
|
+
|
|
71
|
+
ALL = Set[:low, :medium, :high, :critical].freeze
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Strategies for resolving conflicts
|
|
75
|
+
module ResolutionStrategy
|
|
76
|
+
WEIGHTED_VOTING = :weighted_voting
|
|
77
|
+
DELIBERATION_EXTENSION = :deliberation_extension
|
|
78
|
+
REFRAMING = :reframing
|
|
79
|
+
ESCALATION = :escalation
|
|
80
|
+
COMPROMISE = :compromise
|
|
81
|
+
CONDITIONAL_APPROVAL = :conditional_approval
|
|
82
|
+
SUSPENSION = :suspension
|
|
83
|
+
PERSPECTIVE_DOMINANCE = :perspective_dominance
|
|
84
|
+
|
|
85
|
+
ALL = Set[
|
|
86
|
+
:weighted_voting, :deliberation_extension, :reframing,
|
|
87
|
+
:escalation, :compromise, :conditional_approval,
|
|
88
|
+
:suspension, :perspective_dominance
|
|
89
|
+
].freeze
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Deliberation mode affects how perspectives interact
|
|
93
|
+
module DeliberationMode
|
|
94
|
+
COLLABORATIVE = :collaborative
|
|
95
|
+
ADVERSARIAL = :adversarial
|
|
96
|
+
INQUISITIVE = :inquisitive
|
|
97
|
+
SYNTHESIZING = :synthesizing
|
|
98
|
+
|
|
99
|
+
ALL = Set[:collaborative, :adversarial, :inquisitive, :synthesizing].freeze
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Audit entry action types
|
|
103
|
+
module AuditAction
|
|
104
|
+
DELIBERATION_START = :deliberation_start
|
|
105
|
+
ROUND_COMPLETE = :round_complete
|
|
106
|
+
CONFLICT_DETECTED = :conflict_detected
|
|
107
|
+
CONFLICT_RESOLVED = :conflict_resolved
|
|
108
|
+
CONSENSUS_REACHED = :consensus_reached
|
|
109
|
+
TIMEOUT = :timeout
|
|
110
|
+
ERROR = :error
|
|
111
|
+
DELIBERATION_COMPLETE = :deliberation_complete
|
|
112
|
+
|
|
113
|
+
ALL = Set[
|
|
114
|
+
:deliberation_start, :round_complete, :conflict_detected,
|
|
115
|
+
:conflict_resolved, :consensus_reached, :timeout,
|
|
116
|
+
:error, :deliberation_complete
|
|
117
|
+
].freeze
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Logial structure types for logos perspective
|
|
121
|
+
module LogicalStructure
|
|
122
|
+
DEDUCTIVE = :deductive
|
|
123
|
+
INDUCTIVE = :inductive
|
|
124
|
+
ABDUCTIVE = :abductive
|
|
125
|
+
ANALOGICAL = :analogical
|
|
126
|
+
|
|
127
|
+
ALL = Set[:deductive, :inductive, :abductive, :analogical].freeze
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Ethical framework types for ethos perspective
|
|
131
|
+
module EthicalFramework
|
|
132
|
+
UTILITARIAN = :utilitarian
|
|
133
|
+
DEONTOLOGICAL = :deontological
|
|
134
|
+
VIRTUE_ETHICS = :virtue_ethics
|
|
135
|
+
CARE_ETHICS = :care_ethics
|
|
136
|
+
|
|
137
|
+
ALL = Set[:utilitarian, :deontological, :virtue_ethics, :care_ethics].freeze
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Cross-examination evaluation types
|
|
141
|
+
module CrossExaminationEvaluation
|
|
142
|
+
SATISFACTORY = :satisfactory
|
|
143
|
+
UNSATISFACTORY = :unsatisfactory
|
|
144
|
+
NEEDS_CLARIFICATION = :needs_clarification
|
|
145
|
+
|
|
146
|
+
ALL = Set[:satisfactory, :unsatisfactory, :needs_clarification].freeze
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Keyword sets for verdict analysis
|
|
150
|
+
module VerdictIndicators
|
|
151
|
+
POSITIVE = Set[
|
|
152
|
+
'yes', 'agree', 'support', 'affirm', 'positive', 'true', 'proceed'
|
|
153
|
+
].freeze
|
|
154
|
+
|
|
155
|
+
NEGATIVE = Set[
|
|
156
|
+
'no', 'disagree', 'oppose', 'reject', 'negative', 'false', 'decline'
|
|
157
|
+
].freeze
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Keyword sets for domain detection
|
|
161
|
+
module DomainKeywords
|
|
162
|
+
FACTUAL = {
|
|
163
|
+
'data' => 2, 'research' => 2, 'study' => 1, 'analysis' => 1,
|
|
164
|
+
'evidence' => 2, 'scientific' => 2, 'measurement' => 1,
|
|
165
|
+
'statistic' => 2, 'empirical' => 2, 'hypothesis' => 1,
|
|
166
|
+
'experiment' => 1, 'result' => 1, 'fact' => 1
|
|
167
|
+
}.freeze
|
|
168
|
+
|
|
169
|
+
EMOTIONAL = {
|
|
170
|
+
'feel' => 2, 'emotion' => 2, 'relationship' => 1, 'care' => 1,
|
|
171
|
+
'love' => 2, 'passion' => 2, 'heart' => 2, 'empathy' => 2,
|
|
172
|
+
'connection' => 1, 'support' => 1, 'understand' => 1,
|
|
173
|
+
'personal' => 1, 'family' => 1, 'friend' => 1
|
|
174
|
+
}.freeze
|
|
175
|
+
|
|
176
|
+
SENSITIVE = {
|
|
177
|
+
'ethics' => 2, 'moral' => 2, 'right' => 1, 'wrong' => 1,
|
|
178
|
+
'justice' => 2, 'fair' => 1, 'equality' => 2, 'rights' => 2,
|
|
179
|
+
'dignity' => 2, 'vulnerable' => 2, 'discrimination' => 2,
|
|
180
|
+
'privacy' => 1, 'consent' => 2, 'harm' => 2
|
|
181
|
+
}.freeze
|
|
182
|
+
|
|
183
|
+
CREATIVE = {
|
|
184
|
+
'creative' => 2, 'innovative' => 2, 'design' => 1, 'art' => 2,
|
|
185
|
+
'imagine' => 1, 'explore' => 1, 'novel' => 1, 'original' => 2,
|
|
186
|
+
'express' => 1, 'aesthetic' => 2, 'beauty' => 1, 'inspire' => 1,
|
|
187
|
+
'vision' => 1, 'transform' => 1
|
|
188
|
+
}.freeze
|
|
189
|
+
|
|
190
|
+
TECHNICAL = {
|
|
191
|
+
'technical' => 2, 'engineering' => 2, 'system' => 1,
|
|
192
|
+
'implement' => 1, 'architecture' => 2, 'code' => 1,
|
|
193
|
+
'algorithm' => 2, 'optimize' => 1, 'performance' => 1,
|
|
194
|
+
'infrastructure' => 2, 'specification' => 1, 'integration' => 1,
|
|
195
|
+
'deploy' => 1, 'scalability' => 1
|
|
196
|
+
}.freeze
|
|
197
|
+
|
|
198
|
+
SOCIAL = {
|
|
199
|
+
'community' => 2, 'society' => 2, 'public' => 1, 'social' => 2,
|
|
200
|
+
'people' => 1, 'collective' => 2, 'together' => 1, 'group' => 1,
|
|
201
|
+
'shared' => 1, 'common' => 1, 'collaborative' => 1,
|
|
202
|
+
'participate' => 1, 'democratic' => 2, 'citizens' => 2
|
|
203
|
+
}.freeze
|
|
204
|
+
|
|
205
|
+
BUSINESS = {
|
|
206
|
+
'business' => 2, 'profit' => 2, 'market' => 1, 'revenue' => 2,
|
|
207
|
+
'customer' => 1, 'strategy' => 1, 'competitive' => 1,
|
|
208
|
+
'investment' => 1, 'roi' => 2, 'stakeholder' => 1, 'growth' => 1,
|
|
209
|
+
'enterprise' => 1, 'commercial' => 2, 'industry' => 1
|
|
210
|
+
}.freeze
|
|
211
|
+
|
|
212
|
+
PERSONAL = {
|
|
213
|
+
'personal' => 2, 'individual' => 1, 'self' => 2, 'my' => 1,
|
|
214
|
+
'myself' => 2, 'life' => 1, 'choice' => 1, 'decision' => 1,
|
|
215
|
+
'goal' => 1, 'future' => 1, 'happiness' => 2, 'fulfillment' => 2,
|
|
216
|
+
'wellbeing' => 1, 'career' => 1
|
|
217
|
+
}.freeze
|
|
218
|
+
|
|
219
|
+
MARITIME = {
|
|
220
|
+
'vessel' => 2, 'crew' => 2, 'safety' => 2, 'captain' => 2,
|
|
221
|
+
'fleet' => 1, 'navigation' => 2, 'weather' => 1, 'sea' => 1,
|
|
222
|
+
'port' => 1, 'harbor' => 1, 'maritime' => 2, 'fishing' => 1,
|
|
223
|
+
'catch' => 1, 'tide' => 1, 'anchor' => 1, 'deck' => 1,
|
|
224
|
+
'haul' => 1, 'sortie' => 2, 'bearing' => 1, 'course' => 1
|
|
225
|
+
}.freeze
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types'
|
|
4
|
+
|
|
5
|
+
module SuperInstance
|
|
6
|
+
module Equipment
|
|
7
|
+
module ConsensusEngine
|
|
8
|
+
# WeightCalculator - Domain-specific weight calculation for perspectives
|
|
9
|
+
#
|
|
10
|
+
# Calculates appropriate weights for Pathos, Logos, and Ethos perspectives
|
|
11
|
+
# based on the domain of the deliberation.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# calculator = SuperInstance::Equipment::ConsensusEngine::WeightCalculator.new
|
|
15
|
+
#
|
|
16
|
+
# # Get weights for a factual domain
|
|
17
|
+
# factual_profile = calculator.get_profile(:factual)
|
|
18
|
+
# puts factual_profile[:logos_weight] # 0.60
|
|
19
|
+
#
|
|
20
|
+
# # Calculate adjusted weights based on content
|
|
21
|
+
# adjusted = calculator.calculate_adjusted_weights(
|
|
22
|
+
# :sensitive,
|
|
23
|
+
# 'This decision affects vulnerable populations...'
|
|
24
|
+
# )
|
|
25
|
+
class WeightCalculator
|
|
26
|
+
# Domain characteristics for analysis
|
|
27
|
+
DOMAIN_CHARACTERISTICS = {
|
|
28
|
+
factual: {
|
|
29
|
+
emotional_importance: 0.2,
|
|
30
|
+
logical_importance: 0.8,
|
|
31
|
+
ethical_importance: 0.4,
|
|
32
|
+
uncertainty_level: 0.3,
|
|
33
|
+
stakeholder_complexity: 0.3
|
|
34
|
+
},
|
|
35
|
+
emotional: {
|
|
36
|
+
emotional_importance: 0.8,
|
|
37
|
+
logical_importance: 0.3,
|
|
38
|
+
ethical_importance: 0.5,
|
|
39
|
+
uncertainty_level: 0.5,
|
|
40
|
+
stakeholder_complexity: 0.7
|
|
41
|
+
},
|
|
42
|
+
sensitive: {
|
|
43
|
+
emotional_importance: 0.6,
|
|
44
|
+
logical_importance: 0.4,
|
|
45
|
+
ethical_importance: 0.9,
|
|
46
|
+
uncertainty_level: 0.4,
|
|
47
|
+
stakeholder_complexity: 0.8
|
|
48
|
+
},
|
|
49
|
+
creative: {
|
|
50
|
+
emotional_importance: 0.7,
|
|
51
|
+
logical_importance: 0.5,
|
|
52
|
+
ethical_importance: 0.4,
|
|
53
|
+
uncertainty_level: 0.6,
|
|
54
|
+
stakeholder_complexity: 0.4
|
|
55
|
+
},
|
|
56
|
+
balanced: {
|
|
57
|
+
emotional_importance: 0.5,
|
|
58
|
+
logical_importance: 0.5,
|
|
59
|
+
ethical_importance: 0.5,
|
|
60
|
+
uncertainty_level: 0.5,
|
|
61
|
+
stakeholder_complexity: 0.5
|
|
62
|
+
},
|
|
63
|
+
technical: {
|
|
64
|
+
emotional_importance: 0.1,
|
|
65
|
+
logical_importance: 0.9,
|
|
66
|
+
ethical_importance: 0.3,
|
|
67
|
+
uncertainty_level: 0.2,
|
|
68
|
+
stakeholder_complexity: 0.2
|
|
69
|
+
},
|
|
70
|
+
social: {
|
|
71
|
+
emotional_importance: 0.7,
|
|
72
|
+
logical_importance: 0.4,
|
|
73
|
+
ethical_importance: 0.6,
|
|
74
|
+
uncertainty_level: 0.5,
|
|
75
|
+
stakeholder_complexity: 0.9
|
|
76
|
+
},
|
|
77
|
+
business: {
|
|
78
|
+
emotional_importance: 0.4,
|
|
79
|
+
logical_importance: 0.7,
|
|
80
|
+
ethical_importance: 0.5,
|
|
81
|
+
uncertainty_level: 0.4,
|
|
82
|
+
stakeholder_complexity: 0.6
|
|
83
|
+
},
|
|
84
|
+
maritime: {
|
|
85
|
+
emotional_importance: 0.3,
|
|
86
|
+
logical_importance: 0.8,
|
|
87
|
+
ethical_importance: 0.6,
|
|
88
|
+
uncertainty_level: 0.5,
|
|
89
|
+
stakeholder_complexity: 0.4
|
|
90
|
+
},
|
|
91
|
+
personal: {
|
|
92
|
+
emotional_importance: 0.8,
|
|
93
|
+
logical_importance: 0.4,
|
|
94
|
+
ethical_importance: 0.4,
|
|
95
|
+
uncertainty_level: 0.6,
|
|
96
|
+
stakeholder_complexity: 0.3
|
|
97
|
+
}
|
|
98
|
+
}.freeze
|
|
99
|
+
|
|
100
|
+
# Creates a new WeightCalculator instance
|
|
101
|
+
# @param custom_weights [Hash] Optional custom weight overrides to apply to all profiles
|
|
102
|
+
def initialize(custom_weights = {})
|
|
103
|
+
@profiles = build_default_profiles
|
|
104
|
+
@custom_overrides = custom_weights
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Gets the weight profile for a specific domain
|
|
108
|
+
# @param domain [Symbol] The domain to get weights for
|
|
109
|
+
# @return [Hash] The weight profile for the domain
|
|
110
|
+
def get_profile(domain)
|
|
111
|
+
base_profile = @profiles[domain]
|
|
112
|
+
raise ArgumentError, "Unknown domain: #{domain}" unless base_profile
|
|
113
|
+
|
|
114
|
+
# Apply custom overrides
|
|
115
|
+
base_profile.merge(@custom_overrides).merge(domain: domain)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Sets a custom weight profile for a domain
|
|
119
|
+
# @param domain [Symbol] The domain to set
|
|
120
|
+
# @param weights [Hash] The weights to apply
|
|
121
|
+
def set_profile(domain, weights)
|
|
122
|
+
current = @profiles[domain]
|
|
123
|
+
raise ArgumentError, "Unknown domain: #{domain}" unless current
|
|
124
|
+
|
|
125
|
+
# Normalize weights to sum to 1
|
|
126
|
+
raw_pathos = weights[:pathos_weight] || current[:pathos_weight]
|
|
127
|
+
raw_logos = weights[:logos_weight] || current[:logos_weight]
|
|
128
|
+
raw_ethos = weights[:ethos_weight] || current[:ethos_weight]
|
|
129
|
+
total = raw_pathos + raw_logos + raw_ethos
|
|
130
|
+
|
|
131
|
+
@profiles[domain] = {
|
|
132
|
+
pathos_weight: raw_pathos / total,
|
|
133
|
+
logos_weight: raw_logos / total,
|
|
134
|
+
ethos_weight: raw_ethos / total,
|
|
135
|
+
domain: domain,
|
|
136
|
+
description: weights[:description] || current[:description],
|
|
137
|
+
adjustment_rules: current[:adjustment_rules]
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Calculates adjusted weights based on content analysis
|
|
142
|
+
# @param domain [Symbol] The base domain
|
|
143
|
+
# @param content [String] The content to analyze
|
|
144
|
+
# @param base_weights [Hash, nil] Optional base weights to start from
|
|
145
|
+
# @return [Hash] Adjusted weight profile
|
|
146
|
+
def calculate_adjusted_weights(domain, content, base_weights = nil)
|
|
147
|
+
base_profile = base_weights || get_profile(domain)
|
|
148
|
+
rules = base_profile[:adjustment_rules] || []
|
|
149
|
+
|
|
150
|
+
# Sort rules by priority
|
|
151
|
+
sorted_rules = rules.sort_by { |r| -r[:priority] }
|
|
152
|
+
|
|
153
|
+
pathos_weight = base_profile[:pathos_weight]
|
|
154
|
+
logos_weight = base_profile[:logos_weight]
|
|
155
|
+
ethos_weight = base_profile[:ethos_weight]
|
|
156
|
+
|
|
157
|
+
# Apply matching rules
|
|
158
|
+
sorted_rules.each do |rule|
|
|
159
|
+
if matches_condition(content, rule[:condition])
|
|
160
|
+
case rule[:perspective]
|
|
161
|
+
when :pathos
|
|
162
|
+
pathos_weight += rule[:adjustment]
|
|
163
|
+
when :logos
|
|
164
|
+
logos_weight += rule[:adjustment]
|
|
165
|
+
when :ethos
|
|
166
|
+
ethos_weight += rule[:adjustment]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Normalize to sum to 1
|
|
172
|
+
total = pathos_weight + logos_weight + ethos_weight
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
pathos_weight: pathos_weight / total,
|
|
176
|
+
logos_weight: logos_weight / total,
|
|
177
|
+
ethos_weight: ethos_weight / total,
|
|
178
|
+
domain: domain,
|
|
179
|
+
description: "Adjusted profile for #{domain} domain based on content analysis",
|
|
180
|
+
adjustment_rules: rules
|
|
181
|
+
}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Detects the most appropriate domain from content
|
|
185
|
+
# @param content [String] The content to analyze
|
|
186
|
+
# @return [Symbol] The detected domain
|
|
187
|
+
def detect_domain(content)
|
|
188
|
+
lower_content = content.downcase
|
|
189
|
+
scores = []
|
|
190
|
+
|
|
191
|
+
# Check for each domain type
|
|
192
|
+
scores << { domain: :factual, score: score_domain(lower_content, DomainKeywords::FACTUAL) }
|
|
193
|
+
scores << { domain: :emotional, score: score_domain(lower_content, DomainKeywords::EMOTIONAL) }
|
|
194
|
+
scores << { domain: :sensitive, score: score_domain(lower_content, DomainKeywords::SENSITIVE) }
|
|
195
|
+
scores << { domain: :creative, score: score_domain(lower_content, DomainKeywords::CREATIVE) }
|
|
196
|
+
scores << { domain: :technical, score: score_domain(lower_content, DomainKeywords::TECHNICAL) }
|
|
197
|
+
scores << { domain: :social, score: score_domain(lower_content, DomainKeywords::SOCIAL) }
|
|
198
|
+
scores << { domain: :business, score: score_domain(lower_content, DomainKeywords::BUSINESS) }
|
|
199
|
+
scores << { domain: :personal, score: score_domain(lower_content, DomainKeywords::PERSONAL) }
|
|
200
|
+
scores << { domain: :maritime, score: score_domain(lower_content, DomainKeywords::MARITIME) }
|
|
201
|
+
|
|
202
|
+
# Find highest score
|
|
203
|
+
scores.sort! { |a, b| b[:score] <=> a[:score] }
|
|
204
|
+
|
|
205
|
+
# If no strong signal, return balanced
|
|
206
|
+
return :balanced if scores[0][:score] < 3
|
|
207
|
+
|
|
208
|
+
scores[0][:domain]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Gets domain characteristics
|
|
212
|
+
# @param domain [Symbol] The domain to get characteristics for
|
|
213
|
+
# @return [Hash] The domain characteristics
|
|
214
|
+
def get_domain_characteristics(domain)
|
|
215
|
+
DOMAIN_CHARACTERISTICS[domain].dup
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Lists all available domains
|
|
219
|
+
# @return [Array<Symbol>] Array of available domains
|
|
220
|
+
def list_domains
|
|
221
|
+
%i[factual emotional sensitive creative balanced technical social business personal maritime]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Creates a custom weight profile by blending multiple domains
|
|
225
|
+
# @param domains [Array<Hash>] Domains to blend with weights [{ domain: :factual, weight: 0.5 }]
|
|
226
|
+
# @return [Hash] Blended weight profile
|
|
227
|
+
def blend_domains(domains)
|
|
228
|
+
if domains.empty?
|
|
229
|
+
return get_profile(:balanced)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Normalize input weights
|
|
233
|
+
total_input_weight = domains.sum { |d| d[:weight] }
|
|
234
|
+
normalized_domains = domains.map do |d|
|
|
235
|
+
{ domain: d[:domain], weight: d[:weight] / total_input_weight }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Blend the weights
|
|
239
|
+
pathos_weight = 0.0
|
|
240
|
+
logos_weight = 0.0
|
|
241
|
+
ethos_weight = 0.0
|
|
242
|
+
all_rules = []
|
|
243
|
+
|
|
244
|
+
normalized_domains.each do |item|
|
|
245
|
+
profile = get_profile(item[:domain])
|
|
246
|
+
pathos_weight += profile[:pathos_weight] * item[:weight]
|
|
247
|
+
logos_weight += profile[:logos_weight] * item[:weight]
|
|
248
|
+
ethos_weight += profile[:ethos_weight] * item[:weight]
|
|
249
|
+
all_rules.concat(profile[:adjustment_rules] || [])
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Normalize to sum to 1
|
|
253
|
+
total = pathos_weight + logos_weight + ethos_weight
|
|
254
|
+
|
|
255
|
+
{
|
|
256
|
+
pathos_weight: pathos_weight / total,
|
|
257
|
+
logos_weight: logos_weight / total,
|
|
258
|
+
ethos_weight: ethos_weight / total,
|
|
259
|
+
domain: :balanced,
|
|
260
|
+
description: "Blended profile from: #{domains.map { |d| d[:domain] }.join(', ')}",
|
|
261
|
+
adjustment_rules: all_rules
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Validates a weight profile
|
|
266
|
+
# @param profile [Hash] Profile to validate
|
|
267
|
+
# @return [Hash] Validation result { valid: Boolean, errors: Array<String> }
|
|
268
|
+
def validate_profile(profile)
|
|
269
|
+
errors = []
|
|
270
|
+
|
|
271
|
+
if profile[:pathos_weight]
|
|
272
|
+
if profile[:pathos_weight] < 0 || profile[:pathos_weight] > 1
|
|
273
|
+
errors << 'pathos_weight must be between 0 and 1'
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
if profile[:logos_weight]
|
|
278
|
+
if profile[:logos_weight] < 0 || profile[:logos_weight] > 1
|
|
279
|
+
errors << 'logos_weight must be between 0 and 1'
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if profile[:ethos_weight]
|
|
284
|
+
if profile[:ethos_weight] < 0 || profile[:ethos_weight] > 1
|
|
285
|
+
errors << 'ethos_weight must be between 0 and 1'
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if profile[:pathos_weight] && profile[:logos_weight] && profile[:ethos_weight]
|
|
290
|
+
sum = profile[:pathos_weight] + profile[:logos_weight] + profile[:ethos_weight]
|
|
291
|
+
if (sum - 1.0).abs > 0.001
|
|
292
|
+
errors << "Weights must sum to 1, got #{sum.round(3)}"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
{ valid: errors.empty?, errors: errors }
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private
|
|
300
|
+
|
|
301
|
+
def build_default_profiles
|
|
302
|
+
{
|
|
303
|
+
factual: {
|
|
304
|
+
pathos_weight: 0.15,
|
|
305
|
+
logos_weight: 0.60,
|
|
306
|
+
ethos_weight: 0.25,
|
|
307
|
+
domain: :factual,
|
|
308
|
+
description: 'Scientific and data-driven decisions prioritize logic over emotion',
|
|
309
|
+
adjustment_rules: [
|
|
310
|
+
{ name: 'data_present', condition: 'Content contains statistical data or research findings', perspective: :logos, adjustment: 0.1, priority: 10 },
|
|
311
|
+
{ name: 'human_subjects', condition: 'Research involves human subjects', perspective: :ethos, adjustment: 0.1, priority: 8 }
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
emotional: {
|
|
315
|
+
pathos_weight: 0.50,
|
|
316
|
+
logos_weight: 0.20,
|
|
317
|
+
ethos_weight: 0.30,
|
|
318
|
+
domain: :emotional,
|
|
319
|
+
description: 'Human-centered decisions value emotional resonance highly',
|
|
320
|
+
adjustment_rules: [
|
|
321
|
+
{ name: 'personal_story', condition: 'Content includes personal narratives or testimonials', perspective: :pathos, adjustment: 0.15, priority: 10 },
|
|
322
|
+
{ name: 'group_dynamics', condition: 'Decision affects group relationships', perspective: :pathos, adjustment: 0.1, priority: 8 }
|
|
323
|
+
]
|
|
324
|
+
},
|
|
325
|
+
sensitive: {
|
|
326
|
+
pathos_weight: 0.30,
|
|
327
|
+
logos_weight: 0.25,
|
|
328
|
+
ethos_weight: 0.45,
|
|
329
|
+
domain: :sensitive,
|
|
330
|
+
description: 'Ethically complex decisions prioritize moral considerations',
|
|
331
|
+
adjustment_rules: [
|
|
332
|
+
{ name: 'vulnerable_populations', condition: 'Decision affects vulnerable populations', perspective: :ethos, adjustment: 0.15, priority: 10 },
|
|
333
|
+
{ name: 'rights_implications', condition: 'Decision has rights implications', perspective: :ethos, adjustment: 0.1, priority: 9 }
|
|
334
|
+
]
|
|
335
|
+
},
|
|
336
|
+
creative: {
|
|
337
|
+
pathos_weight: 0.40,
|
|
338
|
+
logos_weight: 0.30,
|
|
339
|
+
ethos_weight: 0.30,
|
|
340
|
+
domain: :creative,
|
|
341
|
+
description: 'Creative decisions balance all perspectives with slight emotional edge',
|
|
342
|
+
adjustment_rules: [
|
|
343
|
+
{ name: 'innovation_focus', condition: 'Decision involves innovation or new approaches', perspective: :logos, adjustment: 0.05, priority: 7 },
|
|
344
|
+
{ name: 'artistic_expression', condition: 'Decision involves artistic or creative expression', perspective: :pathos, adjustment: 0.1, priority: 8 }
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
balanced: {
|
|
348
|
+
pathos_weight: 0.333,
|
|
349
|
+
logos_weight: 0.334,
|
|
350
|
+
ethos_weight: 0.333,
|
|
351
|
+
domain: :balanced,
|
|
352
|
+
description: 'Equal weighting for general-purpose deliberations',
|
|
353
|
+
adjustment_rules: []
|
|
354
|
+
},
|
|
355
|
+
technical: {
|
|
356
|
+
pathos_weight: 0.10,
|
|
357
|
+
logos_weight: 0.70,
|
|
358
|
+
ethos_weight: 0.20,
|
|
359
|
+
domain: :technical,
|
|
360
|
+
description: 'Engineering decisions strongly prioritize logical reasoning',
|
|
361
|
+
adjustment_rules: [
|
|
362
|
+
{ name: 'safety_critical', condition: 'Decision involves safety-critical systems', perspective: :logos, adjustment: 0.05, priority: 10 },
|
|
363
|
+
{ name: 'user_impact', condition: 'Technical change affects end users', perspective: :pathos, adjustment: 0.1, priority: 7 }
|
|
364
|
+
]
|
|
365
|
+
},
|
|
366
|
+
social: {
|
|
367
|
+
pathos_weight: 0.40,
|
|
368
|
+
logos_weight: 0.25,
|
|
369
|
+
ethos_weight: 0.35,
|
|
370
|
+
domain: :social,
|
|
371
|
+
description: 'Community decisions value emotional and ethical dimensions',
|
|
372
|
+
adjustment_rules: [
|
|
373
|
+
{ name: 'community_impact', condition: 'Decision affects community cohesion', perspective: :ethos, adjustment: 0.1, priority: 9 },
|
|
374
|
+
{ name: 'public_opinion', condition: 'Decision will be subject to public scrutiny', perspective: :pathos, adjustment: 0.05, priority: 8 }
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
business: {
|
|
378
|
+
pathos_weight: 0.25,
|
|
379
|
+
logos_weight: 0.45,
|
|
380
|
+
ethos_weight: 0.30,
|
|
381
|
+
domain: :business,
|
|
382
|
+
description: 'Business decisions balance logic with stakeholder impact',
|
|
383
|
+
adjustment_rules: [
|
|
384
|
+
{ name: 'profit_pressure', condition: 'Financial metrics are primary consideration', perspective: :logos, adjustment: 0.1, priority: 8 },
|
|
385
|
+
{ name: 'reputation_risk', condition: 'Decision affects company reputation', perspective: :ethos, adjustment: 0.1, priority: 9 }
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
maritime: {
|
|
389
|
+
pathos_weight: 0.20,
|
|
390
|
+
logos_weight: 0.55,
|
|
391
|
+
ethos_weight: 0.25,
|
|
392
|
+
domain: :maritime,
|
|
393
|
+
description: 'Maritime operations prioritize safety, logistics, and crew welfare',
|
|
394
|
+
adjustment_rules: [
|
|
395
|
+
{ name: 'safety_critical', condition: 'Decision involves crew safety or vessel integrity', perspective: :ethos, adjustment: 0.15, priority: 10 },
|
|
396
|
+
{ name: 'weather_hazard', condition: 'Weather or sea conditions are a factor', perspective: :logos, adjustment: 0.1, priority: 9 },
|
|
397
|
+
{ name: 'crew_welfare', condition: 'Decision affects crew rest, health, or morale', perspective: :pathos, adjustment: 0.1, priority: 8 },
|
|
398
|
+
{ name: 'regulatory_compliance', condition: 'Decision involves catch limits, seasons, or regulations', perspective: :ethos, adjustment: 0.1, priority: 9 }
|
|
399
|
+
]
|
|
400
|
+
},
|
|
401
|
+
personal: {
|
|
402
|
+
pathos_weight: 0.45,
|
|
403
|
+
logos_weight: 0.30,
|
|
404
|
+
ethos_weight: 0.25,
|
|
405
|
+
domain: :personal,
|
|
406
|
+
description: 'Personal decisions emphasize individual emotional needs',
|
|
407
|
+
adjustment_rules: [
|
|
408
|
+
{ name: 'life_changing', condition: 'Decision has significant life impact', perspective: :pathos, adjustment: 0.1, priority: 10 },
|
|
409
|
+
{ name: 'relationships', condition: 'Decision affects personal relationships', perspective: :ethos, adjustment: 0.1, priority: 9 }
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Scores content against keyword weights
|
|
416
|
+
def score_domain(content, keywords)
|
|
417
|
+
score = 0.0
|
|
418
|
+
keywords.each do |keyword, weight|
|
|
419
|
+
score += weight if content.include?(keyword)
|
|
420
|
+
end
|
|
421
|
+
score
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Checks if content matches a condition
|
|
425
|
+
def matches_condition(content, condition)
|
|
426
|
+
lower_content = content.downcase
|
|
427
|
+
lower_condition = condition.downcase
|
|
428
|
+
|
|
429
|
+
# Simple keyword matching
|
|
430
|
+
keywords = lower_condition.scan(/\b[a-z]+\b/)
|
|
431
|
+
matching_keywords = keywords.select { |kw| lower_content.include?(kw) }
|
|
432
|
+
|
|
433
|
+
matching_keywords.length >= (keywords.length * 0.3).ceil
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'equipment/consensus_engine/version'
|
|
4
|
+
|
|
5
|
+
require_relative 'equipment/consensus_engine/types'
|
|
6
|
+
require_relative 'equipment/consensus_engine/weight_calculator'
|
|
7
|
+
require_relative 'equipment/consensus_engine/tripartite_deliberation'
|
|
8
|
+
require_relative 'equipment/consensus_engine/conflict_resolution'
|
|
9
|
+
require_relative 'equipment/consensus_engine/consensus_engine'
|
|
10
|
+
|
|
11
|
+
module SuperInstance
|
|
12
|
+
module Equipment
|
|
13
|
+
module ConsensusEngine
|
|
14
|
+
# Main entry point for the ConsensusEngine gem
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|