smart_message 0.0.10 → 0.0.12
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 +4 -4
- data/.github/workflows/deploy-github-pages.yml +38 -0
- data/.gitignore +5 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +35 -4
- data/README.md +169 -71
- data/Rakefile +29 -4
- data/docs/assets/images/ddq_architecture.svg +130 -0
- data/docs/assets/images/dlq_architecture.svg +115 -0
- data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
- data/docs/assets/images/enhanced-fluent-api.svg +149 -0
- data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
- data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
- data/docs/assets/images/fluent-api-demo.svg +59 -0
- data/docs/assets/images/performance-comparison.svg +161 -0
- data/docs/assets/images/redis-basic-architecture.svg +53 -0
- data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
- data/docs/assets/images/redis-queue-architecture.svg +101 -0
- data/docs/assets/images/smart_message.jpg +0 -0
- data/docs/assets/images/smart_message_walking.jpg +0 -0
- data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
- data/docs/assets/images/transport-comparison-matrix.svg +171 -0
- data/docs/assets/javascripts/mathjax.js +17 -0
- data/docs/assets/stylesheets/extra.css +51 -0
- data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
- data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
- data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
- data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
- data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
- data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
- data/docs/{examples.md → getting-started/examples.md} +115 -89
- data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
- data/docs/guides/redis-queue-getting-started.md +697 -0
- data/docs/guides/redis-queue-patterns.md +889 -0
- data/docs/guides/redis-queue-production.md +1091 -0
- data/docs/index.md +64 -0
- data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
- data/docs/{logging.md → reference/logging.md} +1 -1
- data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
- data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
- data/docs/{serializers.md → reference/serializers.md} +3 -5
- data/docs/{transports.md → reference/transports.md} +133 -11
- data/docs/transports/memory-transport.md +374 -0
- data/docs/transports/redis-enhanced-transport.md +524 -0
- data/docs/transports/redis-queue-transport.md +1304 -0
- data/docs/transports/redis-transport-comparison.md +496 -0
- data/docs/transports/redis-transport.md +509 -0
- data/examples/README.md +98 -5
- data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
- data/examples/city_scenario/README.md +515 -0
- data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
- data/examples/city_scenario/citizen.rb +195 -0
- data/examples/city_scenario/city_diagram.svg +125 -0
- data/examples/city_scenario/common/health_monitor.rb +80 -0
- data/examples/city_scenario/common/logger.rb +30 -0
- data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
- data/examples/city_scenario/fire_department.rb +446 -0
- data/examples/city_scenario/fire_emergency_flow.svg +95 -0
- data/examples/city_scenario/health_department.rb +100 -0
- data/examples/city_scenario/health_monitoring_system.svg +130 -0
- data/examples/city_scenario/house.rb +244 -0
- data/examples/city_scenario/local_bank.rb +217 -0
- data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
- data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
- data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
- data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
- data/examples/city_scenario/messages/health_check_message.rb +22 -0
- data/examples/city_scenario/messages/health_status_message.rb +35 -0
- data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
- data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
- data/examples/city_scenario/police_department.rb +316 -0
- data/examples/city_scenario/redis_monitor.rb +129 -0
- data/examples/city_scenario/redis_stats.rb +743 -0
- data/examples/city_scenario/room_for_improvement.md +240 -0
- data/examples/city_scenario/security_emergency_flow.svg +95 -0
- data/examples/city_scenario/service_internal_architecture.svg +154 -0
- data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
- data/examples/city_scenario/start_demo.sh +236 -0
- data/examples/city_scenario/stop_demo.sh +106 -0
- data/examples/city_scenario/visitor.rb +631 -0
- data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
- data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
- data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
- data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
- data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
- data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
- data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
- data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
- data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
- data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
- data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
- data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
- data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
- data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
- data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
- data/examples/memory/README.md +163 -0
- data/examples/memory/memory_transport_architecture.svg +90 -0
- data/examples/memory/point_to_point_pattern.svg +94 -0
- data/examples/memory/publish_subscribe_pattern.svg +125 -0
- data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
- data/examples/redis/README.md +230 -0
- data/examples/redis/alert_system_flow.svg +127 -0
- data/examples/redis/dashboard_status_flow.svg +107 -0
- data/examples/redis/device_command_flow.svg +113 -0
- data/examples/redis/redis_transport_architecture.svg +115 -0
- data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
- data/examples/redis/smart_home_system_architecture.svg +133 -0
- data/examples/redis_enhanced/README.md +319 -0
- data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/01_basic_messaging.rb +221 -0
- data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
- data/examples/redis_queue/02_pattern_routing.rb +405 -0
- data/examples/redis_queue/03_fluent_api.rb +422 -0
- data/examples/redis_queue/04_load_balancing.rb +486 -0
- data/examples/redis_queue/05_microservices.rb +735 -0
- data/examples/redis_queue/06_emergency_alerts.rb +777 -0
- data/examples/redis_queue/07_queue_management.rb +587 -0
- data/examples/redis_queue/README.md +366 -0
- data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
- data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
- data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
- data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
- data/examples/redis_queue/redis_queue_architecture.svg +148 -0
- data/ideas/README.md +41 -0
- data/ideas/agents.md +1001 -0
- data/ideas/database_transport.md +980 -0
- data/ideas/improvement.md +359 -0
- data/ideas/meshage.md +1788 -0
- data/ideas/message_discovery.md +178 -0
- data/ideas/message_schema.md +1381 -0
- data/lib/smart_message/.idea/.gitignore +8 -0
- data/lib/smart_message/.idea/markdown.xml +6 -0
- data/lib/smart_message/.idea/misc.xml +4 -0
- data/lib/smart_message/.idea/modules.xml +8 -0
- data/lib/smart_message/.idea/smart_message.iml +16 -0
- data/lib/smart_message/.idea/vcs.xml +6 -0
- data/lib/smart_message/addressing.rb +15 -0
- data/lib/smart_message/base.rb +0 -2
- data/lib/smart_message/configuration.rb +1 -1
- data/lib/smart_message/logger.rb +15 -4
- data/lib/smart_message/plugins.rb +5 -2
- data/lib/smart_message/serializer.rb +14 -0
- data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
- data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
- data/lib/smart_message/transport/registry.rb +1 -0
- data/lib/smart_message/transport.rb +34 -1
- data/lib/smart_message/version.rb +1 -1
- data/lib/smart_message.rb +5 -52
- data/mkdocs.yml +184 -0
- data/p2p_plan.md +326 -0
- data/p2p_roadmap.md +287 -0
- data/smart_message.gemspec +2 -0
- data/smart_message.svg +51 -0
- metadata +170 -44
- data/docs/README.md +0 -57
- data/examples/dead_letters.jsonl +0 -12
- data/examples/temp.txt +0 -94
- data/examples/tmux_chat/README.md +0 -283
- data/examples/tmux_chat/bot_agent.rb +0 -278
- data/examples/tmux_chat/human_agent.rb +0 -199
- data/examples/tmux_chat/room_monitor.rb +0 -160
- data/examples/tmux_chat/shared_chat_system.rb +0 -328
- data/examples/tmux_chat/start_chat_demo.sh +0 -190
- data/examples/tmux_chat/stop_chat_demo.sh +0 -22
- /data/docs/{properties.md → core-concepts/properties.md} +0 -0
- /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,777 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/redis_queue/06_emergency_alerts.rb
|
3
|
+
# Emergency alert system with Redis Queue Transport
|
4
|
+
|
5
|
+
require_relative '../../lib/smart_message'
|
6
|
+
|
7
|
+
puts "🚨 Redis Queue Transport - Emergency Alert System Demo"
|
8
|
+
puts "=" * 60
|
9
|
+
|
10
|
+
#==============================================================================
|
11
|
+
# Emergency Alert System Configuration
|
12
|
+
#==============================================================================
|
13
|
+
|
14
|
+
SmartMessage.configure do |config|
|
15
|
+
config.transport = :redis_queue
|
16
|
+
config.transport_options = {
|
17
|
+
url: 'redis://localhost:6379',
|
18
|
+
db: 6, # Use database 6 for emergency alert demo
|
19
|
+
queue_prefix: 'emergency_alerts',
|
20
|
+
consumer_group: 'emergency_responders'
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
#==============================================================================
|
25
|
+
# Emergency Message Classes
|
26
|
+
#==============================================================================
|
27
|
+
|
28
|
+
class EmergencyAlert < SmartMessage::Base
|
29
|
+
transport :redis_queue
|
30
|
+
|
31
|
+
property :alert_id, required: true
|
32
|
+
property :alert_type, required: true # fire, medical, security, natural_disaster, infrastructure
|
33
|
+
property :severity, required: true # low, medium, high, critical
|
34
|
+
property :title, required: true
|
35
|
+
property :description, required: true
|
36
|
+
property :location, required: true
|
37
|
+
property :coordinates, default: {} # lat/lng
|
38
|
+
property :affected_area_radius, default: 0 # in kilometers
|
39
|
+
property :estimated_affected_people, default: 0
|
40
|
+
property :response_required, default: true
|
41
|
+
property :contact_info, default: {}
|
42
|
+
property :metadata, default: {}
|
43
|
+
property :timestamp, default: -> { Time.now }
|
44
|
+
|
45
|
+
def process
|
46
|
+
severity_icon = case severity
|
47
|
+
when 'critical' then '🔥'
|
48
|
+
when 'high' then '🚨'
|
49
|
+
when 'medium' then '⚠️'
|
50
|
+
when 'low' then '⚡'
|
51
|
+
else '📢'
|
52
|
+
end
|
53
|
+
|
54
|
+
type_icon = case alert_type
|
55
|
+
when 'fire' then '🔥'
|
56
|
+
when 'medical' then '🚑'
|
57
|
+
when 'security' then '👮'
|
58
|
+
when 'natural_disaster' then '🌪️'
|
59
|
+
when 'infrastructure' then '🏗️'
|
60
|
+
else '🚨'
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "#{severity_icon}#{type_icon} ALERT #{alert_id} [#{severity.upcase}]: #{title}"
|
64
|
+
puts " Type: #{alert_type.upcase}"
|
65
|
+
puts " Location: #{location}"
|
66
|
+
puts " Description: #{description}"
|
67
|
+
puts " People affected: #{estimated_affected_people}" if estimated_affected_people > 0
|
68
|
+
puts " Response required: #{response_required ? 'YES' : 'NO'}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class EmergencyResponse < SmartMessage::Base
|
73
|
+
transport :redis_queue
|
74
|
+
|
75
|
+
property :response_id, required: true
|
76
|
+
property :alert_id, required: true
|
77
|
+
property :responding_unit, required: true # fire_dept, police, hospital, utility_company
|
78
|
+
property :response_type, required: true # dispatched, on_scene, resolved, cancelled
|
79
|
+
property :eta_minutes
|
80
|
+
property :personnel_count, default: 0
|
81
|
+
property :equipment, default: []
|
82
|
+
property :status_update, required: true
|
83
|
+
property :timestamp, default: -> { Time.now }
|
84
|
+
|
85
|
+
def process
|
86
|
+
response_icon = case response_type
|
87
|
+
when 'dispatched' then '🚀'
|
88
|
+
when 'on_scene' then '📍'
|
89
|
+
when 'resolved' then '✅'
|
90
|
+
when 'cancelled' then '❌'
|
91
|
+
else '📝'
|
92
|
+
end
|
93
|
+
|
94
|
+
unit_icon = case responding_unit
|
95
|
+
when 'fire_dept' then '🚒'
|
96
|
+
when 'police' then '🚔'
|
97
|
+
when 'hospital' then '🚑'
|
98
|
+
when 'utility_company' then '⚡'
|
99
|
+
else '🚗'
|
100
|
+
end
|
101
|
+
|
102
|
+
puts "#{response_icon}#{unit_icon} RESPONSE #{response_id}: #{responding_unit.upcase} - #{response_type.upcase}"
|
103
|
+
puts " Alert ID: #{alert_id}"
|
104
|
+
puts " Status: #{status_update}"
|
105
|
+
puts " ETA: #{eta_minutes} minutes" if eta_minutes
|
106
|
+
puts " Personnel: #{personnel_count}" if personnel_count > 0
|
107
|
+
puts " Equipment: #{equipment.join(', ')}" if equipment.any?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class CitizenReport < SmartMessage::Base
|
112
|
+
transport :redis_queue
|
113
|
+
|
114
|
+
property :report_id, required: true
|
115
|
+
property :reporter_id
|
116
|
+
property :report_type, required: true # incident, tip, request_help, status_update
|
117
|
+
property :description, required: true
|
118
|
+
property :location, required: true
|
119
|
+
property :urgency, default: 'medium' # low, medium, high
|
120
|
+
property :contact_phone
|
121
|
+
property :photos, default: []
|
122
|
+
property :verified, default: false
|
123
|
+
property :timestamp, default: -> { Time.now }
|
124
|
+
|
125
|
+
def process
|
126
|
+
urgency_icon = case urgency
|
127
|
+
when 'high' then '🚨'
|
128
|
+
when 'medium' then '⚠️'
|
129
|
+
when 'low' then 'ℹ️'
|
130
|
+
else '📢'
|
131
|
+
end
|
132
|
+
|
133
|
+
puts "#{urgency_icon} CITIZEN REPORT #{report_id} [#{urgency.upcase}]: #{report_type.upcase}"
|
134
|
+
puts " Location: #{location}"
|
135
|
+
puts " Description: #{description}"
|
136
|
+
puts " Contact: #{contact_phone}" if contact_phone
|
137
|
+
puts " Photos: #{photos.size} attached" if photos.any?
|
138
|
+
puts " Verified: #{verified ? 'YES' : 'PENDING'}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
#==============================================================================
|
143
|
+
# Emergency Response Services
|
144
|
+
#==============================================================================
|
145
|
+
|
146
|
+
class EmergencyDispatchCenter
|
147
|
+
def initialize(transport)
|
148
|
+
@transport = transport
|
149
|
+
@active_alerts = {}
|
150
|
+
@response_units = {}
|
151
|
+
setup_subscriptions
|
152
|
+
end
|
153
|
+
|
154
|
+
def setup_subscriptions
|
155
|
+
# Listen for new citizen reports
|
156
|
+
@transport.where
|
157
|
+
.type('CitizenReport')
|
158
|
+
.to('dispatch_center')
|
159
|
+
.subscribe do |message_class, message_data|
|
160
|
+
handle_citizen_report(JSON.parse(message_data))
|
161
|
+
end
|
162
|
+
|
163
|
+
# Listen for response updates
|
164
|
+
@transport.where
|
165
|
+
.type('EmergencyResponse')
|
166
|
+
.to('dispatch_center')
|
167
|
+
.subscribe do |message_class, message_data|
|
168
|
+
track_response(JSON.parse(message_data))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_alert(alert_type, severity, title, description, location, options = {})
|
173
|
+
alert_id = "ALERT-#{Time.now.strftime('%Y%m%d')}-#{sprintf('%04d', rand(9999))}"
|
174
|
+
|
175
|
+
alert = EmergencyAlert.new({
|
176
|
+
alert_id: alert_id,
|
177
|
+
alert_type: alert_type,
|
178
|
+
severity: severity,
|
179
|
+
title: title,
|
180
|
+
description: description,
|
181
|
+
location: location,
|
182
|
+
coordinates: options[:coordinates] || {},
|
183
|
+
affected_area_radius: options[:radius] || 0,
|
184
|
+
estimated_affected_people: options[:affected_people] || 0,
|
185
|
+
contact_info: options[:contact] || {},
|
186
|
+
metadata: options[:metadata] || {}
|
187
|
+
}.merge(options))
|
188
|
+
|
189
|
+
# Broadcast to all emergency services
|
190
|
+
alert._sm_header.from = 'dispatch_center'
|
191
|
+
alert._sm_header.to = 'emergency_broadcast'
|
192
|
+
alert.publish
|
193
|
+
|
194
|
+
# Route to specific services based on alert type
|
195
|
+
route_alert_to_services(alert, alert_type, severity)
|
196
|
+
|
197
|
+
@active_alerts[alert_id] = alert
|
198
|
+
alert_id
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def handle_citizen_report(report_data)
|
204
|
+
puts "📞 Dispatch: Received citizen report #{report_data['report_id']}"
|
205
|
+
|
206
|
+
# Evaluate if report requires emergency alert
|
207
|
+
if report_data['urgency'] == 'high'
|
208
|
+
# Escalate to emergency alert
|
209
|
+
alert_id = create_alert(
|
210
|
+
determine_alert_type(report_data['description']),
|
211
|
+
'medium',
|
212
|
+
"Citizen Report: #{report_data['report_type'].capitalize}",
|
213
|
+
report_data['description'],
|
214
|
+
report_data['location'],
|
215
|
+
{ contact: { phone: report_data['contact_phone'] } }
|
216
|
+
)
|
217
|
+
|
218
|
+
puts " ⬆️ Escalated to emergency alert: #{alert_id}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def track_response(response_data)
|
223
|
+
puts "📊 Dispatch: Tracking response #{response_data['response_id']} for alert #{response_data['alert_id']}"
|
224
|
+
@response_units[response_data['response_id']] = response_data
|
225
|
+
end
|
226
|
+
|
227
|
+
def route_alert_to_services(alert, alert_type, severity)
|
228
|
+
services = case alert_type
|
229
|
+
when 'fire'
|
230
|
+
['fire_department', 'police_department'] + (severity == 'critical' ? ['hospital'] : [])
|
231
|
+
when 'medical'
|
232
|
+
['hospital', 'fire_department']
|
233
|
+
when 'security'
|
234
|
+
['police_department'] + (severity == 'critical' ? ['fire_department'] : [])
|
235
|
+
when 'natural_disaster'
|
236
|
+
['fire_department', 'police_department', 'hospital', 'utility_company']
|
237
|
+
when 'infrastructure'
|
238
|
+
['utility_company', 'fire_department']
|
239
|
+
else
|
240
|
+
['police_department']
|
241
|
+
end
|
242
|
+
|
243
|
+
services.each do |service|
|
244
|
+
alert_copy = EmergencyAlert.new(alert.to_h)
|
245
|
+
alert_copy._sm_header.to = service
|
246
|
+
alert_copy.publish
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def determine_alert_type(description)
|
251
|
+
case description.downcase
|
252
|
+
when /fire|smoke|burning/ then 'fire'
|
253
|
+
when /medical|injury|accident|hurt/ then 'medical'
|
254
|
+
when /robbery|theft|assault|suspicious/ then 'security'
|
255
|
+
when /flood|earthquake|storm|tornado/ then 'natural_disaster'
|
256
|
+
when /power|gas|water|sewer/ then 'infrastructure'
|
257
|
+
else 'security'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class FireDepartment
|
263
|
+
def initialize(transport)
|
264
|
+
@transport = transport
|
265
|
+
@available_units = 3
|
266
|
+
@deployed_units = {}
|
267
|
+
setup_subscriptions
|
268
|
+
end
|
269
|
+
|
270
|
+
def setup_subscriptions
|
271
|
+
# Listen for emergency alerts
|
272
|
+
@transport.where
|
273
|
+
.to('fire_department')
|
274
|
+
.subscribe do |message_class, message_data|
|
275
|
+
handle_alert(JSON.parse(message_data)) if message_class == 'EmergencyAlert'
|
276
|
+
end
|
277
|
+
|
278
|
+
# Listen for broadcasts
|
279
|
+
@transport.where
|
280
|
+
.to('emergency_broadcast')
|
281
|
+
.subscribe do |message_class, message_data|
|
282
|
+
monitor_alert(JSON.parse(message_data)) if message_class == 'EmergencyAlert'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
private
|
287
|
+
|
288
|
+
def handle_alert(alert_data)
|
289
|
+
alert_type = alert_data['alert_type']
|
290
|
+
severity = alert_data['severity']
|
291
|
+
alert_id = alert_data['alert_id']
|
292
|
+
|
293
|
+
puts "🚒 Fire Department: Received #{severity} #{alert_type} alert #{alert_id}"
|
294
|
+
|
295
|
+
if should_respond?(alert_type, severity)
|
296
|
+
dispatch_response(alert_data)
|
297
|
+
else
|
298
|
+
puts " ⏸️ Standing by (not primary responder for this alert type)"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def monitor_alert(alert_data)
|
303
|
+
puts "🚒 Fire Department: Monitoring alert #{alert_data['alert_id']} (broadcast)"
|
304
|
+
end
|
305
|
+
|
306
|
+
def should_respond?(alert_type, severity)
|
307
|
+
case alert_type
|
308
|
+
when 'fire' then true
|
309
|
+
when 'medical' then true
|
310
|
+
when 'natural_disaster' then true
|
311
|
+
when 'infrastructure' then severity == 'high' || severity == 'critical'
|
312
|
+
when 'security' then severity == 'critical'
|
313
|
+
else false
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def dispatch_response(alert_data)
|
318
|
+
if @available_units > 0
|
319
|
+
@available_units -= 1
|
320
|
+
response_id = "FD-#{Time.now.strftime('%H%M%S')}-#{rand(99)}"
|
321
|
+
|
322
|
+
personnel = case alert_data['severity']
|
323
|
+
when 'critical' then 8
|
324
|
+
when 'high' then 6
|
325
|
+
when 'medium' then 4
|
326
|
+
else 2
|
327
|
+
end
|
328
|
+
|
329
|
+
equipment = case alert_data['alert_type']
|
330
|
+
when 'fire' then ['fire_engine', 'ladder_truck', 'water_tank']
|
331
|
+
when 'medical' then ['ambulance', 'rescue_equipment']
|
332
|
+
else ['fire_engine', 'rescue_equipment']
|
333
|
+
end
|
334
|
+
|
335
|
+
EmergencyResponse.new(
|
336
|
+
response_id: response_id,
|
337
|
+
alert_id: alert_data['alert_id'],
|
338
|
+
responding_unit: 'fire_dept',
|
339
|
+
response_type: 'dispatched',
|
340
|
+
eta_minutes: rand(5..15),
|
341
|
+
personnel_count: personnel,
|
342
|
+
equipment: equipment,
|
343
|
+
status_update: "Unit dispatched to #{alert_data['location']}",
|
344
|
+
_sm_header: {
|
345
|
+
from: 'fire_department',
|
346
|
+
to: 'dispatch_center'
|
347
|
+
}
|
348
|
+
).publish
|
349
|
+
|
350
|
+
@deployed_units[response_id] = alert_data['alert_id']
|
351
|
+
|
352
|
+
# Simulate arrival and resolution
|
353
|
+
Thread.new do
|
354
|
+
sleep(2) # Simulate travel time
|
355
|
+
update_response_status(response_id, 'on_scene', 'Fire department on scene, assessing situation')
|
356
|
+
|
357
|
+
sleep(3) # Simulate response time
|
358
|
+
resolution = ['resolved', 'resolved', 'resolved', 'cancelled'].sample # 75% success rate
|
359
|
+
status = resolution == 'resolved' ? 'Incident resolved successfully' : 'False alarm - no action required'
|
360
|
+
update_response_status(response_id, resolution, status)
|
361
|
+
|
362
|
+
@available_units += 1
|
363
|
+
@deployed_units.delete(response_id)
|
364
|
+
end
|
365
|
+
else
|
366
|
+
puts " 🚫 No available units - requesting mutual aid"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def update_response_status(response_id, response_type, status)
|
371
|
+
EmergencyResponse.new(
|
372
|
+
response_id: response_id,
|
373
|
+
alert_id: @deployed_units[response_id],
|
374
|
+
responding_unit: 'fire_dept',
|
375
|
+
response_type: response_type,
|
376
|
+
status_update: status,
|
377
|
+
_sm_header: {
|
378
|
+
from: 'fire_department',
|
379
|
+
to: 'dispatch_center'
|
380
|
+
}
|
381
|
+
).publish
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class PoliceDepartment
|
386
|
+
def initialize(transport)
|
387
|
+
@transport = transport
|
388
|
+
@available_units = 5
|
389
|
+
@deployed_units = {}
|
390
|
+
setup_subscriptions
|
391
|
+
end
|
392
|
+
|
393
|
+
def setup_subscriptions
|
394
|
+
@transport.where
|
395
|
+
.to('police_department')
|
396
|
+
.subscribe do |message_class, message_data|
|
397
|
+
handle_alert(JSON.parse(message_data)) if message_class == 'EmergencyAlert'
|
398
|
+
end
|
399
|
+
|
400
|
+
@transport.where
|
401
|
+
.to('emergency_broadcast')
|
402
|
+
.subscribe do |message_class, message_data|
|
403
|
+
monitor_alert(JSON.parse(message_data)) if message_class == 'EmergencyAlert'
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
def handle_alert(alert_data)
|
410
|
+
alert_type = alert_data['alert_type']
|
411
|
+
severity = alert_data['severity']
|
412
|
+
alert_id = alert_data['alert_id']
|
413
|
+
|
414
|
+
puts "👮 Police Department: Received #{severity} #{alert_type} alert #{alert_id}"
|
415
|
+
|
416
|
+
if should_respond?(alert_type, severity)
|
417
|
+
dispatch_response(alert_data)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def monitor_alert(alert_data)
|
422
|
+
puts "👮 Police Department: Monitoring alert #{alert_data['alert_id']}"
|
423
|
+
end
|
424
|
+
|
425
|
+
def should_respond?(alert_type, severity)
|
426
|
+
case alert_type
|
427
|
+
when 'security' then true
|
428
|
+
when 'fire' then severity == 'medium' || severity == 'high' || severity == 'critical'
|
429
|
+
when 'natural_disaster' then true
|
430
|
+
when 'medical' then severity == 'high' || severity == 'critical'
|
431
|
+
else false
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def dispatch_response(alert_data)
|
436
|
+
if @available_units > 0
|
437
|
+
@available_units -= 1
|
438
|
+
response_id = "PD-#{Time.now.strftime('%H%M%S')}-#{rand(99)}"
|
439
|
+
|
440
|
+
officers = case alert_data['severity']
|
441
|
+
when 'critical' then 6
|
442
|
+
when 'high' then 4
|
443
|
+
when 'medium' then 2
|
444
|
+
else 1
|
445
|
+
end
|
446
|
+
|
447
|
+
equipment = case alert_data['alert_type']
|
448
|
+
when 'security' then ['patrol_car', 'radio', 'backup_requested']
|
449
|
+
else ['patrol_car', 'radio']
|
450
|
+
end
|
451
|
+
|
452
|
+
EmergencyResponse.new(
|
453
|
+
response_id: response_id,
|
454
|
+
alert_id: alert_data['alert_id'],
|
455
|
+
responding_unit: 'police',
|
456
|
+
response_type: 'dispatched',
|
457
|
+
eta_minutes: rand(3..12),
|
458
|
+
personnel_count: officers,
|
459
|
+
equipment: equipment,
|
460
|
+
status_update: "Officers dispatched to #{alert_data['location']}",
|
461
|
+
_sm_header: {
|
462
|
+
from: 'police_department',
|
463
|
+
to: 'dispatch_center'
|
464
|
+
}
|
465
|
+
).publish
|
466
|
+
|
467
|
+
@deployed_units[response_id] = alert_data['alert_id']
|
468
|
+
|
469
|
+
Thread.new do
|
470
|
+
sleep(1.5)
|
471
|
+
update_response_status(response_id, 'on_scene', 'Police on scene, securing area')
|
472
|
+
|
473
|
+
sleep(2.5)
|
474
|
+
update_response_status(response_id, 'resolved', 'Situation resolved, area secured')
|
475
|
+
|
476
|
+
@available_units += 1
|
477
|
+
@deployed_units.delete(response_id)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def update_response_status(response_id, response_type, status)
|
483
|
+
EmergencyResponse.new(
|
484
|
+
response_id: response_id,
|
485
|
+
alert_id: @deployed_units[response_id],
|
486
|
+
responding_unit: 'police',
|
487
|
+
response_type: response_type,
|
488
|
+
status_update: status,
|
489
|
+
_sm_header: {
|
490
|
+
from: 'police_department',
|
491
|
+
to: 'dispatch_center'
|
492
|
+
}
|
493
|
+
).publish
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
class Hospital
|
498
|
+
def initialize(transport)
|
499
|
+
@transport = transport
|
500
|
+
@available_ambulances = 2
|
501
|
+
@deployed_ambulances = {}
|
502
|
+
setup_subscriptions
|
503
|
+
end
|
504
|
+
|
505
|
+
def setup_subscriptions
|
506
|
+
@transport.where
|
507
|
+
.to('hospital')
|
508
|
+
.subscribe do |message_class, message_data|
|
509
|
+
handle_alert(JSON.parse(message_data)) if message_class == 'EmergencyAlert'
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
private
|
514
|
+
|
515
|
+
def handle_alert(alert_data)
|
516
|
+
alert_type = alert_data['alert_type']
|
517
|
+
severity = alert_data['severity']
|
518
|
+
|
519
|
+
puts "🏥 Hospital: Received #{severity} #{alert_type} alert #{alert_data['alert_id']}"
|
520
|
+
|
521
|
+
if alert_type == 'medical' || (alert_type == 'fire' && severity == 'high')
|
522
|
+
dispatch_ambulance(alert_data)
|
523
|
+
else
|
524
|
+
puts " 📋 Preparing to receive potential patients"
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
def dispatch_ambulance(alert_data)
|
529
|
+
if @available_ambulances > 0
|
530
|
+
@available_ambulances -= 1
|
531
|
+
response_id = "AMB-#{Time.now.strftime('%H%M%S')}-#{rand(99)}"
|
532
|
+
|
533
|
+
EmergencyResponse.new(
|
534
|
+
response_id: response_id,
|
535
|
+
alert_id: alert_data['alert_id'],
|
536
|
+
responding_unit: 'hospital',
|
537
|
+
response_type: 'dispatched',
|
538
|
+
eta_minutes: rand(6..18),
|
539
|
+
personnel_count: 2,
|
540
|
+
equipment: ['ambulance', 'medical_equipment', 'defibrillator'],
|
541
|
+
status_update: "Ambulance dispatched to #{alert_data['location']}",
|
542
|
+
_sm_header: {
|
543
|
+
from: 'hospital',
|
544
|
+
to: 'dispatch_center'
|
545
|
+
}
|
546
|
+
).publish
|
547
|
+
|
548
|
+
@deployed_ambulances[response_id] = alert_data['alert_id']
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
#==============================================================================
|
554
|
+
# Emergency Alert System Demo
|
555
|
+
#==============================================================================
|
556
|
+
|
557
|
+
puts "\n🚨 Initializing Emergency Response System..."
|
558
|
+
|
559
|
+
transport = SmartMessage::Transport::RedisQueueTransport.new(
|
560
|
+
url: 'redis://localhost:6379',
|
561
|
+
db: 6,
|
562
|
+
queue_prefix: 'emergency_alerts',
|
563
|
+
consumer_group: 'emergency_responders',
|
564
|
+
block_time: 1000
|
565
|
+
)
|
566
|
+
|
567
|
+
# Initialize emergency services
|
568
|
+
dispatch = EmergencyDispatchCenter.new(transport)
|
569
|
+
fire_dept = FireDepartment.new(transport)
|
570
|
+
police = PoliceDepartment.new(transport)
|
571
|
+
hospital = Hospital.new(transport)
|
572
|
+
|
573
|
+
sleep 2
|
574
|
+
puts "✅ All emergency services online and ready"
|
575
|
+
|
576
|
+
#==============================================================================
|
577
|
+
# Emergency Scenarios
|
578
|
+
#==============================================================================
|
579
|
+
|
580
|
+
puts "\n🎭 Emergency Alert Scenarios:"
|
581
|
+
|
582
|
+
# Scenario 1: Building Fire
|
583
|
+
puts "\n🔥 Scenario 1: Building Fire Emergency"
|
584
|
+
fire_alert_id = dispatch.create_alert(
|
585
|
+
'fire',
|
586
|
+
'high',
|
587
|
+
'Building Fire at Downtown Office Complex',
|
588
|
+
'Large office building on fire, multiple floors affected, people may be trapped',
|
589
|
+
'123 Main Street, Downtown',
|
590
|
+
{
|
591
|
+
coordinates: { lat: 40.7128, lng: -74.0060 },
|
592
|
+
affected_people: 200,
|
593
|
+
radius: 2,
|
594
|
+
contact: { phone: '911', name: 'Dispatch Center' }
|
595
|
+
}
|
596
|
+
)
|
597
|
+
|
598
|
+
sleep 4
|
599
|
+
|
600
|
+
# Scenario 2: Medical Emergency
|
601
|
+
puts "\n🚑 Scenario 2: Medical Emergency"
|
602
|
+
medical_alert_id = dispatch.create_alert(
|
603
|
+
'medical',
|
604
|
+
'critical',
|
605
|
+
'Multi-Vehicle Accident with Injuries',
|
606
|
+
'Three-car collision on highway, multiple injuries, road blocked',
|
607
|
+
'Highway 101 Mile Marker 45',
|
608
|
+
{
|
609
|
+
coordinates: { lat: 40.7589, lng: -73.9851 },
|
610
|
+
affected_people: 8,
|
611
|
+
radius: 1,
|
612
|
+
contact: { phone: '911', name: 'Highway Patrol' }
|
613
|
+
}
|
614
|
+
)
|
615
|
+
|
616
|
+
sleep 4
|
617
|
+
|
618
|
+
# Scenario 3: Security Incident
|
619
|
+
puts "\n👮 Scenario 3: Security Emergency"
|
620
|
+
security_alert_id = dispatch.create_alert(
|
621
|
+
'security',
|
622
|
+
'medium',
|
623
|
+
'Armed Robbery in Progress',
|
624
|
+
'Armed suspect robbing convenience store, suspect described as male, 5\'10", wearing black hoodie',
|
625
|
+
'456 Oak Avenue, West Side',
|
626
|
+
{
|
627
|
+
coordinates: { lat: 40.7505, lng: -73.9934 },
|
628
|
+
affected_people: 5,
|
629
|
+
contact: { phone: '911', name: 'Store Manager' }
|
630
|
+
}
|
631
|
+
)
|
632
|
+
|
633
|
+
sleep 4
|
634
|
+
|
635
|
+
# Scenario 4: Natural Disaster
|
636
|
+
puts "\n🌪️ Scenario 4: Natural Disaster"
|
637
|
+
disaster_alert_id = dispatch.create_alert(
|
638
|
+
'natural_disaster',
|
639
|
+
'critical',
|
640
|
+
'Tornado Warning - Immediate Shelter Required',
|
641
|
+
'Large tornado spotted 5 miles south, moving northeast at 45 mph, take shelter immediately',
|
642
|
+
'Central County Area',
|
643
|
+
{
|
644
|
+
coordinates: { lat: 40.7282, lng: -74.0776 },
|
645
|
+
affected_people: 50000,
|
646
|
+
radius: 15,
|
647
|
+
contact: { phone: 'Emergency Management', name: 'County Emergency Services' }
|
648
|
+
}
|
649
|
+
)
|
650
|
+
|
651
|
+
sleep 5
|
652
|
+
|
653
|
+
#==============================================================================
|
654
|
+
# Citizen Reports
|
655
|
+
#==============================================================================
|
656
|
+
|
657
|
+
puts "\n📱 Citizen Reports:"
|
658
|
+
|
659
|
+
# Citizen Report 1: Suspicious Activity
|
660
|
+
CitizenReport.new(
|
661
|
+
report_id: "CR-#{Time.now.strftime('%Y%m%d%H%M%S')}",
|
662
|
+
reporter_id: 'citizen_123',
|
663
|
+
report_type: 'tip',
|
664
|
+
description: 'Suspicious person looking into car windows in parking lot',
|
665
|
+
location: '789 Pine Street parking lot',
|
666
|
+
urgency: 'medium',
|
667
|
+
contact_phone: '+1-555-0123',
|
668
|
+
_sm_header: {
|
669
|
+
from: 'citizen_app',
|
670
|
+
to: 'dispatch_center'
|
671
|
+
}
|
672
|
+
).publish
|
673
|
+
|
674
|
+
sleep 2
|
675
|
+
|
676
|
+
# Citizen Report 2: Gas Leak
|
677
|
+
CitizenReport.new(
|
678
|
+
report_id: "CR-#{Time.now.strftime('%Y%m%d%H%M%S')}",
|
679
|
+
reporter_id: 'citizen_456',
|
680
|
+
report_type: 'incident',
|
681
|
+
description: 'Strong gas smell near apartment building, possible gas leak',
|
682
|
+
location: '321 Elm Street Apartments',
|
683
|
+
urgency: 'high',
|
684
|
+
contact_phone: '+1-555-0456',
|
685
|
+
photos: ['gas_leak_1.jpg', 'gas_leak_2.jpg'],
|
686
|
+
_sm_header: {
|
687
|
+
from: 'citizen_app',
|
688
|
+
to: 'dispatch_center'
|
689
|
+
}
|
690
|
+
).publish
|
691
|
+
|
692
|
+
sleep 3
|
693
|
+
|
694
|
+
#==============================================================================
|
695
|
+
# Mass Alert Scenario
|
696
|
+
#==============================================================================
|
697
|
+
|
698
|
+
puts "\n📢 Mass Alert Scenario: Multiple Simultaneous Emergencies"
|
699
|
+
|
700
|
+
# Simulate multiple emergencies happening at once
|
701
|
+
emergencies = [
|
702
|
+
['fire', 'medium', 'Kitchen Fire at Restaurant', 'Grease fire in commercial kitchen'],
|
703
|
+
['medical', 'high', 'Heart Attack at Gym', 'Elderly man collapsed during workout'],
|
704
|
+
['security', 'low', 'Vandalism Report', 'Graffiti on public building'],
|
705
|
+
['infrastructure', 'medium', 'Water Main Break', 'Large water main break flooding street']
|
706
|
+
]
|
707
|
+
|
708
|
+
puts "⚡ Simultaneous emergency alerts..."
|
709
|
+
emergencies.each_with_index do |(type, severity, title, description), i|
|
710
|
+
dispatch.create_alert(
|
711
|
+
type,
|
712
|
+
severity,
|
713
|
+
title,
|
714
|
+
description,
|
715
|
+
"Location #{i + 1}",
|
716
|
+
{ affected_people: rand(10..100) }
|
717
|
+
)
|
718
|
+
|
719
|
+
sleep 0.5 # Slight delay between alerts
|
720
|
+
end
|
721
|
+
|
722
|
+
sleep 8
|
723
|
+
|
724
|
+
#==============================================================================
|
725
|
+
# System Statistics
|
726
|
+
#==============================================================================
|
727
|
+
|
728
|
+
puts "\n📊 Emergency Alert System Statistics:"
|
729
|
+
|
730
|
+
stats = transport.queue_stats
|
731
|
+
puts "\nQueue statistics:"
|
732
|
+
emergency_queues = stats.select { |name, _| name.include?('emergency') || name.include?('department') || name.include?('hospital') }
|
733
|
+
emergency_queues.each do |queue_name, info|
|
734
|
+
service_name = queue_name.split('.').last.tr('_', ' ').titleize
|
735
|
+
puts " #{service_name}: #{info[:length]} pending alerts"
|
736
|
+
end
|
737
|
+
|
738
|
+
routing_table = transport.routing_table
|
739
|
+
puts "\nActive routing patterns:"
|
740
|
+
routing_table.each do |pattern, queues|
|
741
|
+
puts " '#{pattern}' → #{queues.size} queue(s)"
|
742
|
+
end
|
743
|
+
|
744
|
+
total_alerts = stats.values.sum { |info| info[:length] }
|
745
|
+
puts "\nTotal active alerts in system: #{total_alerts}"
|
746
|
+
|
747
|
+
# Show alert type distribution
|
748
|
+
alert_types = ['fire', 'medical', 'security', 'natural_disaster', 'infrastructure']
|
749
|
+
puts "\nAlert Type Coverage:"
|
750
|
+
alert_types.each do |type|
|
751
|
+
pattern_exists = routing_table.any? { |pattern, _| pattern.downcase.include?(type) }
|
752
|
+
puts " #{type.capitalize}: #{pattern_exists ? '✅ Covered' : '❌ No coverage'}"
|
753
|
+
end
|
754
|
+
|
755
|
+
transport.disconnect
|
756
|
+
|
757
|
+
puts "\n🚨 Emergency Alert System demonstration completed!"
|
758
|
+
|
759
|
+
puts "\n💡 Emergency System Features Demonstrated:"
|
760
|
+
puts " ✓ Multi-service emergency coordination"
|
761
|
+
puts " ✓ Severity-based alert routing"
|
762
|
+
puts " ✓ Real-time response tracking"
|
763
|
+
puts " ✓ Citizen reporting integration"
|
764
|
+
puts " ✓ Broadcast and targeted alerts"
|
765
|
+
puts " ✓ Multi-agency response coordination"
|
766
|
+
puts " ✓ Geographic area coverage"
|
767
|
+
puts " ✓ Mass casualty incident handling"
|
768
|
+
|
769
|
+
puts "\n🚀 Key Emergency Response Benefits:"
|
770
|
+
puts " • Instant alert distribution to all relevant agencies"
|
771
|
+
puts " • Coordinated multi-agency response"
|
772
|
+
puts " • Real-time status updates and tracking"
|
773
|
+
puts " • Citizen engagement and reporting"
|
774
|
+
puts " • Scalable from single incident to mass casualty"
|
775
|
+
puts " • Persistent alert queues ensure no missed notifications"
|
776
|
+
puts " • Geographic and severity-based smart routing"
|
777
|
+
puts " • Historical incident tracking and analysis"
|