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,270 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/multi_program_demo/emergency_dispatch_center.rb
|
3
|
+
|
4
|
+
require_relative '../../lib/smart_message'
|
5
|
+
require_relative 'messages/emergency_911_message'
|
6
|
+
require_relative 'messages/fire_emergency_message'
|
7
|
+
require_relative 'messages/silent_alarm_message'
|
8
|
+
|
9
|
+
require_relative 'common/health_monitor'
|
10
|
+
require_relative 'common/logger'
|
11
|
+
|
12
|
+
class EmergencyDispatchCenter
|
13
|
+
include Common::HealthMonitor
|
14
|
+
include Common::Logger
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@service_name = 'emergency-dispatch-center'
|
18
|
+
|
19
|
+
@status = 'healthy'
|
20
|
+
@start_time = Time.now
|
21
|
+
@call_counter = 0
|
22
|
+
@active_calls = {}
|
23
|
+
@dispatch_stats = Hash.new(0)
|
24
|
+
|
25
|
+
setup_messaging
|
26
|
+
setup_signal_handlers
|
27
|
+
setup_health_monitor
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def setup_messaging
|
32
|
+
Messages::Emergency911Message.from(@service_name)
|
33
|
+
Messages::FireEmergencyMessage.from(@service_name)
|
34
|
+
Messages::SilentAlarmMessage.from(@service_name)
|
35
|
+
|
36
|
+
# Subscribe to 911 emergency calls
|
37
|
+
Messages::Emergency911Message.subscribe(to: '911') do |message|
|
38
|
+
handle_emergency_call(message)
|
39
|
+
end
|
40
|
+
|
41
|
+
puts '📞 Emergency Dispatch Center (911) operational'
|
42
|
+
puts ' Routing emergency calls to appropriate departments'
|
43
|
+
puts ' Monitoring: Fire, Police, Medical, Accidents, Hazmat'
|
44
|
+
puts ' Logging to: emergency_dispatch_center.log'
|
45
|
+
puts " Press Ctrl+C to stop\n\n"
|
46
|
+
logger.info('Emergency Dispatch Center ready for 911 calls')
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def get_status_details
|
51
|
+
[@status, @details]
|
52
|
+
end
|
53
|
+
|
54
|
+
def setup_signal_handlers
|
55
|
+
%w[INT TERM].each do |signal|
|
56
|
+
Signal.trap(signal) do
|
57
|
+
puts "\n📞 Emergency Dispatch Center going offline..."
|
58
|
+
show_statistics
|
59
|
+
logger.info('Emergency Dispatch Center shutting down')
|
60
|
+
exit(0)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def run
|
67
|
+
puts '📞 Emergency Dispatch Center listening for 911 calls...'
|
68
|
+
loop do
|
69
|
+
sleep 1
|
70
|
+
@status = 'healthy'
|
71
|
+
end
|
72
|
+
rescue StandardError => e
|
73
|
+
puts "📞 Error in dispatch center: #{e.message}"
|
74
|
+
logger.error({alert:"Error in dispatch center", message: e.message, traceback: e.backtrace})
|
75
|
+
logger.error(e.backtrace.join("\n"))
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def handle_emergency_call(call)
|
81
|
+
@call_counter += 1
|
82
|
+
call_id = "911-#{Time.now.strftime('%Y%m%d-%H%M%S')}-#{@call_counter.to_s.rjust(4, '0')}"
|
83
|
+
|
84
|
+
# Color code based on severity
|
85
|
+
severity_color = case call.severity
|
86
|
+
when 'critical' then "\e[91m" # Bright red
|
87
|
+
when 'high' then "\e[31m" # Red
|
88
|
+
when 'medium' then "\e[33m" # Yellow
|
89
|
+
else "\e[32m" # Green
|
90
|
+
end
|
91
|
+
|
92
|
+
puts "\n#{severity_color}📞 911 CALL RECEIVED\e[0m ##{call_id}"
|
93
|
+
puts " 📍 Location: #{call.caller_location}"
|
94
|
+
puts " 🚨 Type: #{call.emergency_type.upcase}"
|
95
|
+
puts " 📝 Description: #{call.description}"
|
96
|
+
puts " ⚠️ Severity: #{call.severity&.upcase || 'UNKNOWN'}"
|
97
|
+
puts " 👤 Caller: #{call.caller_name || 'Anonymous'} (#{call.caller_phone || 'No phone'})"
|
98
|
+
|
99
|
+
logger.warn("911 CALL #{call_id}: #{call.emergency_type} at #{call.caller_location} - #{call.description}")
|
100
|
+
|
101
|
+
@active_calls[call_id] = {
|
102
|
+
call: call,
|
103
|
+
received_at: Time.now,
|
104
|
+
dispatched_to: []
|
105
|
+
}
|
106
|
+
|
107
|
+
# Determine which departments to dispatch to
|
108
|
+
departments_to_dispatch = determine_dispatch_departments(call)
|
109
|
+
|
110
|
+
puts " 🚨 Dispatching to: #{departments_to_dispatch.join(', ')}"
|
111
|
+
|
112
|
+
# Route to appropriate departments
|
113
|
+
departments_to_dispatch.each do |dept|
|
114
|
+
route_to_department(call, dept, call_id)
|
115
|
+
@dispatch_stats[dept] += 1
|
116
|
+
end
|
117
|
+
|
118
|
+
# After a delay, consider the call handled
|
119
|
+
Thread.new do
|
120
|
+
sleep(rand(60..180)) # Simulate response time
|
121
|
+
if @active_calls[call_id]
|
122
|
+
puts "📞 Call #{call_id} marked as handled"
|
123
|
+
logger.info("Call #{call_id} handled after #{(Time.now - @active_calls[call_id][:received_at]).round} seconds")
|
124
|
+
@active_calls.delete(call_id)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue StandardError => e
|
128
|
+
puts "📞 Error handling 911 call: #{e.message} #{e.backtrace.join("\n")}"
|
129
|
+
logger.error({alert:"Error handling 911 call", message: e.message, traceback: e.backtrace})
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def determine_dispatch_departments(call)
|
134
|
+
departments = []
|
135
|
+
|
136
|
+
# Fire department dispatch rules
|
137
|
+
if call.fire_involved ||
|
138
|
+
call.emergency_type == 'fire' ||
|
139
|
+
call.emergency_type == 'rescue' ||
|
140
|
+
(call.emergency_type == 'accident' && call.vehicles_involved && call.vehicles_involved > 2) ||
|
141
|
+
call.hazardous_materials
|
142
|
+
departments << 'fire_department'
|
143
|
+
end
|
144
|
+
|
145
|
+
# Police department dispatch rules
|
146
|
+
if call.emergency_type == 'crime' ||
|
147
|
+
call.weapons_involved ||
|
148
|
+
call.suspects_on_scene ||
|
149
|
+
call.emergency_type == 'accident' ||
|
150
|
+
(call.emergency_type == 'other' && call.description =~ /theft|robbery|assault|break.?in|suspicious/i)
|
151
|
+
departments << 'police_department'
|
152
|
+
end
|
153
|
+
|
154
|
+
# Medical (would go to EMS, but we'll send to fire for this demo)
|
155
|
+
if (call.emergency_type == 'medical' ||
|
156
|
+
call.injuries_reported ||
|
157
|
+
(call.number_of_victims && call.number_of_victims > 0)) && !departments.include?('fire_department')
|
158
|
+
departments << 'fire_department'
|
159
|
+
end
|
160
|
+
|
161
|
+
# Default to police if no specific department determined
|
162
|
+
departments << 'police_department' if departments.empty?
|
163
|
+
|
164
|
+
departments
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def route_to_department(call, department, call_id)
|
169
|
+
case department
|
170
|
+
when 'fire_department'
|
171
|
+
# Convert to FireEmergencyMessage for fire department
|
172
|
+
if call.fire_involved || call.emergency_type == 'fire'
|
173
|
+
fire_msg = Messages::FireEmergencyMessage.new(
|
174
|
+
house_address: call.caller_location,
|
175
|
+
fire_type: determine_fire_type(call),
|
176
|
+
severity: call.severity || 'medium',
|
177
|
+
occupants_status: build_occupants_status(call),
|
178
|
+
spread_risk: call.hazardous_materials ? 'high' : 'medium',
|
179
|
+
timestamp: call._sm_header.published_at,
|
180
|
+
emergency_id: call_id
|
181
|
+
)
|
182
|
+
fire_msg._sm_header.from = '911-dispatch'
|
183
|
+
fire_msg._sm_header.to = 'fire_department'
|
184
|
+
fire_msg.publish
|
185
|
+
logger.info("Routed call #{call_id} to Fire Department as fire emergency")
|
186
|
+
else
|
187
|
+
# For rescue/medical, forward the 911 call directly
|
188
|
+
forward_call = call.dup
|
189
|
+
forward_call.call_id = call_id
|
190
|
+
forward_call._sm_header.from = '911-dispatch'
|
191
|
+
forward_call._sm_header.to = 'fire_department'
|
192
|
+
forward_call.publish
|
193
|
+
logger.info("Forwarded call #{call_id} to Fire Department for rescue/medical")
|
194
|
+
end
|
195
|
+
|
196
|
+
when 'police_department'
|
197
|
+
# Forward all police calls directly as Emergency911Message
|
198
|
+
forward_call = Messages::Emergency911Message.new(**call.to_h.merge(
|
199
|
+
call_id: call_id,
|
200
|
+
from: '911-dispatch',
|
201
|
+
to: 'police_department'
|
202
|
+
))
|
203
|
+
forward_call._sm_header.from = '911-dispatch'
|
204
|
+
forward_call._sm_header.to = 'police_department'
|
205
|
+
forward_call.publish
|
206
|
+
|
207
|
+
if call.emergency_type == 'crime' || call.weapons_involved || call.suspects_on_scene
|
208
|
+
logger.info("Routed call #{call_id} to Police Department as crime report")
|
209
|
+
else
|
210
|
+
logger.info("Forwarded call #{call_id} to Police Department")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
@active_calls[call_id][:dispatched_to] << department if @active_calls[call_id]
|
215
|
+
rescue StandardError => e
|
216
|
+
logger.error({alert:"Error routing to #{department}", message: e.message, trace: e.backtrace})
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def determine_fire_type(call)
|
222
|
+
return 'chemical' if call.hazardous_materials
|
223
|
+
return 'vehicle' if call.vehicles_involved && call.vehicles_involved > 0
|
224
|
+
|
225
|
+
desc = call.description.downcase
|
226
|
+
return 'electrical' if desc.include?('electrical') || desc.include?('wire')
|
227
|
+
return 'grease' if desc.include?('kitchen') || desc.include?('grease')
|
228
|
+
return 'chemical' if desc.include?('chemical') || desc.include?('gas')
|
229
|
+
|
230
|
+
'general'
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
def determine_alarm_type(call)
|
235
|
+
return 'armed_robbery' if call.weapons_involved
|
236
|
+
return 'break_in' if call.description =~ /break.?in/i
|
237
|
+
return 'assault' if call.description =~ /assault|attack/i
|
238
|
+
return 'robbery' if call.description =~ /robbery|theft/i
|
239
|
+
|
240
|
+
'suspicious_activity'
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def build_occupants_status(call)
|
245
|
+
if call.injuries_reported
|
246
|
+
"#{call.number_of_victims || 'Unknown number of'} injured"
|
247
|
+
elsif call.number_of_victims && call.number_of_victims > 0
|
248
|
+
"#{call.number_of_victims} people involved"
|
249
|
+
else
|
250
|
+
'Unknown'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
def show_statistics
|
256
|
+
puts "\n📊 Dispatch Statistics:"
|
257
|
+
puts " Total 911 calls handled: #{@call_counter}"
|
258
|
+
puts " Active calls: #{@active_calls.size}"
|
259
|
+
puts ' Dispatches by department:'
|
260
|
+
@dispatch_stats.each do |dept, count|
|
261
|
+
puts " #{dept}: #{count}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Run the dispatch center
|
267
|
+
if __FILE__ == $0
|
268
|
+
dispatch = EmergencyDispatchCenter.new
|
269
|
+
dispatch.run
|
270
|
+
end
|