File size: 9,366 Bytes
9425aed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
module ConsensusTypes where
import qualified Data.Map as Map
import Data.Map (Map)
import GHC.Generics (Generic)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Hashable
import Data.List (sortBy, groupBy)
import Data.Ord (comparing)
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Core Types: Agent + Observation + Vote
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- | Unique agent identifier
newtype AgentId = AgentId Int
deriving (Eq, Ord, Show, Generic)
instance Hashable AgentId
-- | Unique observation identifier
newtype ObservationId = ObservationId Int
deriving (Eq, Ord, Show, Generic)
instance Hashable ObservationId
-- | Unique region identifier
newtype RegionId = RegionId Int
deriving (Eq, Ord, Show, Generic)
instance Hashable RegionId
-- | 3D vector for positions
data Vector = Vector { vx :: Double, vy :: Double, vz :: Double }
deriving (Eq, Show, Generic)
instance Hashable Vector
-- | Region type classification (from manifold exploration)
data RegionType = RegionType
{ rtName :: String
, rtDensity :: Double
, rtCurvature :: Double
, rtAnomalyLevel :: Double
} deriving (Eq, Show, Generic, Ord)
instance Hashable RegionType
-- | Measurement bundle (timestamp + readings)
data Measurement = Measurement
{ measTimestamp :: Int
, measValues :: Map String Double
} deriving (Eq, Show, Generic)
-- | Observation from a single agent (immutable, observable-only)
data Observation = Observation
{ obsId :: ObservationId
, agentId :: AgentId
, timestamp :: Int
, coordinates :: Vector
, measurements :: Map String Double -- e.g., "density" -> 0.523
, confidence :: Double -- [0, 1] agent's self-confidence
, regionType :: Maybe RegionType -- classified region
, wormSealed :: Bool -- WORM seal status
, sealRound :: Maybe Int -- round number when sealed
} deriving (Eq, Show, Generic)
-- | Vote from one agent on another's observation
data Vote = Vote
{ voterId :: AgentId
, votedObsId :: ObservationId
, agreement :: Double -- [-1, 1]: -1 disagree, 0 uncertain, +1 agree
, voteRound :: Int
, voteTimestamp :: Int
} deriving (Eq, Show, Generic)
-- | Voting round metadata
data VoteRound = VoteRound
{ roundNum :: Int
, roundTimestamp :: Int
, roundVotes :: [Vote]
, roundObservations :: [Observation]
} deriving (Eq, Show, Generic)
-- | Anomaly detection record
data Anomaly = Anomaly
{ anomalyId :: Int
, anomalyLocation :: Vector
, anomalySeverity :: Double -- [0, 1]
, anomalyRound :: Int
, anomalyAgents :: [AgentId] -- which agents observed it
, anomalyConfidence :: Double -- consensus confidence [0, 1]
} deriving (Eq, Show, Generic)
-- | Conflict detection (two agents disagree on same measurement)
data Conflict = Conflict
{ conflictObsId :: ObservationId
, conflictRegionId :: RegionId
, conflictAgents :: [AgentId]
, conflictMeasureDiff :: Double -- magnitude of disagreement
, conflictResolved :: Bool
} deriving (Eq, Show, Generic)
-- | Shared world model (consensus result)
data WorldModel = WorldModel
{ regionTypes :: Map RegionId RegionType
, agentPositions :: Map AgentId Vector
, anomalies :: [Anomaly]
, frontierRegions :: [RegionId]
, modelConfidence :: Double -- collective confidence [0, 1]
, modelGeneration :: Int -- which round produced this model
} deriving (Eq, Show, Generic)
-- | Complete consensus state (accumulates over rounds)
data ConsensusState = ConsensusState
{ observations :: [Observation]
, votes :: [Vote]
, voteRounds :: [VoteRound]
, worldModel :: WorldModel
, confidence :: Double -- global consensus confidence [0, 1]
, conflicts :: [Conflict]
, wormSealLog :: [SealRecord] -- WORM-sealed audit trail
, generation :: Int -- consensus generation number
} deriving (Eq, Show, Generic)
-- | WORM-sealed audit record
data SealRecord = SealRecord
{ sealRound :: Int
, sealTimestamp :: Int
, sealedObsCount :: Int
, sealedVoteCount :: Int
, sealHash :: ByteString -- Blake3 hash of sealed data
, sealAgents :: [AgentId] -- agents participating in round
} deriving (Eq, Show, Generic)
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Initial State Constructors
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- | Create empty world model
emptyWorldModel :: WorldModel
emptyWorldModel = WorldModel
{ regionTypes = Map.empty
, agentPositions = Map.empty
, anomalies = []
, frontierRegions = []
, modelConfidence = 0.0
, modelGeneration = 0
}
-- | Create empty consensus state
emptyConsensusState :: ConsensusState
emptyConsensusState = ConsensusState
{ observations = []
, votes = []
, voteRounds = []
, worldModel = emptyWorldModel
, confidence = 0.0
, conflicts = []
, wormSealLog = []
, generation = 0
}
-- | Create observation from agent reading
makeObservation :: ObservationId -> AgentId -> Int -> Vector
-> Map String Double -> Double -> Maybe RegionType
-> Observation
makeObservation oid aid ts pos meas conf regType = Observation
{ obsId = oid
, agentId = aid
, timestamp = ts
, coordinates = pos
, measurements = meas
, confidence = max 0.0 (min 1.0 conf)
, regionType = regType
, wormSealed = False
, sealRound = Nothing
}
-- | Create vote
makeVote :: AgentId -> ObservationId -> Double -> Int -> Int -> Vote
makeVote voter obsId agrmt round ts = Vote
{ voterId = voter
, votedObsId = obsId
, agreement = max (-1.0) (min 1.0 agrmt)
, voteRound = round
, voteTimestamp = ts
}
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- Utility Functions
-- βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-- | Calculate Euclidean distance between vectors
vectorDistance :: Vector -> Vector -> Double
vectorDistance v1 v2 = sqrt ((vx v1 - vx v2)^2 + (vy v1 - vy v2)^2 + (vz v1 - vz v2)^2)
-- | Average a list of doubles
averageDouble :: [Double] -> Double
averageDouble [] = 0.0
averageDouble xs = sum xs / fromIntegral (length xs)
-- | Filter votes for a specific observation
votesForObservation :: [Vote] -> ObservationId -> [Vote]
votesForObservation vs obsId = filter (\v -> votedObsId v == obsId) vs
-- | Filter observations by agent
observationsByAgent :: [Observation] -> AgentId -> [Observation]
observationsByAgent obs aid = filter (\o -> agentId o == aid) obs
-- | Calculate consensus score for observation (average agreement)
consensusScore :: [Vote] -> Double
consensusScore [] = 0.0
consensusScore vs = averageDouble (map agreement vs)
-- | Check if observation has consensus (66%+ agreement)
hasConsensus :: [Vote] -> Bool
hasConsensus vs
| null vs = False
| otherwise = let score = consensusScore vs
positiveVotes = fromIntegral (length (filter (\v -> agreement v > 0.5) vs)) :: Double
ratioVotes = positiveVotes / fromIntegral (length vs)
in score > 0.33 && ratioVotes >= 0.66
-- | Measurement difference metric (normalized L2)
measurementDifference :: Map String Double -> Map String Double -> Double
measurementDifference m1 m2 =
let allKeys = Map.keys m1 ++ Map.keys m2
diffs = map (\k -> let v1 = Map.findWithDefault 0.0 k m1
v2 = Map.findWithDefault 0.0 k m2
in abs (v1 - v2)) allKeys
in if null diffs then 0.0 else averageDouble diffs
-- | Group observations by region
groupByRegion :: [Observation] -> Map RegionId [Observation]
groupByRegion obs =
let grouped = groupBy (\o1 o2 -> regionType o1 == regionType o2)
(sortBy (comparing regionType) obs)
in Map.fromList [(RegionId i, g) | (i, g) <- zip [0..] grouped]
-- | Get all agents from observations and votes
extractAgentIds :: ConsensusState -> [AgentId]
extractAgentIds state =
let fromObs = map agentId (observations state)
fromVotes = map voterId (votes state)
in nub (fromObs ++ fromVotes)
where
nub [] = []
nub (x:xs) = x : nub (filter (/= x) xs)
|