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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +38 -0
  3. data/.gitignore +5 -0
  4. data/CHANGELOG.md +30 -0
  5. data/Gemfile.lock +35 -4
  6. data/README.md +169 -71
  7. data/Rakefile +29 -4
  8. data/docs/assets/images/ddq_architecture.svg +130 -0
  9. data/docs/assets/images/dlq_architecture.svg +115 -0
  10. data/docs/assets/images/enhanced-dual-publishing.svg +136 -0
  11. data/docs/assets/images/enhanced-fluent-api.svg +149 -0
  12. data/docs/assets/images/enhanced-microservices-routing.svg +115 -0
  13. data/docs/assets/images/enhanced-pattern-matching.svg +107 -0
  14. data/docs/assets/images/fluent-api-demo.svg +59 -0
  15. data/docs/assets/images/performance-comparison.svg +161 -0
  16. data/docs/assets/images/redis-basic-architecture.svg +53 -0
  17. data/docs/assets/images/redis-enhanced-architecture.svg +88 -0
  18. data/docs/assets/images/redis-queue-architecture.svg +101 -0
  19. data/docs/assets/images/smart_message.jpg +0 -0
  20. data/docs/assets/images/smart_message_walking.jpg +0 -0
  21. data/docs/assets/images/smartmessage_architecture_overview.svg +173 -0
  22. data/docs/assets/images/transport-comparison-matrix.svg +171 -0
  23. data/docs/assets/javascripts/mathjax.js +17 -0
  24. data/docs/assets/stylesheets/extra.css +51 -0
  25. data/docs/{addressing.md → core-concepts/addressing.md} +5 -7
  26. data/docs/{architecture.md → core-concepts/architecture.md} +78 -138
  27. data/docs/{dispatcher.md → core-concepts/dispatcher.md} +21 -21
  28. data/docs/{message_filtering.md → core-concepts/message-filtering.md} +2 -3
  29. data/docs/{message_processing.md → core-concepts/message-processing.md} +17 -17
  30. data/docs/{troubleshooting.md → development/troubleshooting.md} +7 -7
  31. data/docs/{examples.md → getting-started/examples.md} +115 -89
  32. data/docs/{getting-started.md → getting-started/quick-start.md} +47 -18
  33. data/docs/guides/redis-queue-getting-started.md +697 -0
  34. data/docs/guides/redis-queue-patterns.md +889 -0
  35. data/docs/guides/redis-queue-production.md +1091 -0
  36. data/docs/index.md +64 -0
  37. data/docs/{dead_letter_queue.md → reference/dead-letter-queue.md} +2 -3
  38. data/docs/{logging.md → reference/logging.md} +1 -1
  39. data/docs/{message_deduplication.md → reference/message-deduplication.md} +1 -0
  40. data/docs/{proc_handlers_summary.md → reference/proc-handlers.md} +7 -6
  41. data/docs/{serializers.md → reference/serializers.md} +3 -5
  42. data/docs/{transports.md → reference/transports.md} +133 -11
  43. data/docs/transports/memory-transport.md +374 -0
  44. data/docs/transports/redis-enhanced-transport.md +524 -0
  45. data/docs/transports/redis-queue-transport.md +1304 -0
  46. data/docs/transports/redis-transport-comparison.md +496 -0
  47. data/docs/transports/redis-transport.md +509 -0
  48. data/examples/README.md +98 -5
  49. data/examples/city_scenario/911_emergency_call_flow.svg +99 -0
  50. data/examples/city_scenario/README.md +515 -0
  51. data/examples/city_scenario/ai_visitor_intelligence_flow.svg +108 -0
  52. data/examples/city_scenario/citizen.rb +195 -0
  53. data/examples/city_scenario/city_diagram.svg +125 -0
  54. data/examples/city_scenario/common/health_monitor.rb +80 -0
  55. data/examples/city_scenario/common/logger.rb +30 -0
  56. data/examples/city_scenario/emergency_dispatch_center.rb +270 -0
  57. data/examples/city_scenario/fire_department.rb +446 -0
  58. data/examples/city_scenario/fire_emergency_flow.svg +95 -0
  59. data/examples/city_scenario/health_department.rb +100 -0
  60. data/examples/city_scenario/health_monitoring_system.svg +130 -0
  61. data/examples/city_scenario/house.rb +244 -0
  62. data/examples/city_scenario/local_bank.rb +217 -0
  63. data/examples/city_scenario/messages/emergency_911_message.rb +81 -0
  64. data/examples/city_scenario/messages/emergency_resolved_message.rb +43 -0
  65. data/examples/city_scenario/messages/fire_dispatch_message.rb +43 -0
  66. data/examples/city_scenario/messages/fire_emergency_message.rb +45 -0
  67. data/examples/city_scenario/messages/health_check_message.rb +22 -0
  68. data/examples/city_scenario/messages/health_status_message.rb +35 -0
  69. data/examples/city_scenario/messages/police_dispatch_message.rb +46 -0
  70. data/examples/city_scenario/messages/silent_alarm_message.rb +38 -0
  71. data/examples/city_scenario/police_department.rb +316 -0
  72. data/examples/city_scenario/redis_monitor.rb +129 -0
  73. data/examples/city_scenario/redis_stats.rb +743 -0
  74. data/examples/city_scenario/room_for_improvement.md +240 -0
  75. data/examples/city_scenario/security_emergency_flow.svg +95 -0
  76. data/examples/city_scenario/service_internal_architecture.svg +154 -0
  77. data/examples/city_scenario/smart_message_ai_agent.rb +364 -0
  78. data/examples/city_scenario/start_demo.sh +236 -0
  79. data/examples/city_scenario/stop_demo.sh +106 -0
  80. data/examples/city_scenario/visitor.rb +631 -0
  81. data/examples/{10_message_deduplication.rb → memory/01_message_deduplication_demo.rb} +1 -1
  82. data/examples/{09_dead_letter_queue_demo.rb → memory/02_dead_letter_queue_demo.rb} +13 -40
  83. data/examples/{01_point_to_point_orders.rb → memory/03_point_to_point_orders.rb} +1 -1
  84. data/examples/{02_publish_subscribe_events.rb → memory/04_publish_subscribe_events.rb} +2 -2
  85. data/examples/{03_many_to_many_chat.rb → memory/05_many_to_many_chat.rb} +4 -4
  86. data/examples/{show_me.rb → memory/06_pretty_print_demo.rb} +1 -1
  87. data/examples/{05_proc_handlers.rb → memory/07_proc_handlers_demo.rb} +2 -2
  88. data/examples/{06_custom_logger_example.rb → memory/08_custom_logger_demo.rb} +17 -14
  89. data/examples/{07_error_handling_scenarios.rb → memory/09_error_handling_demo.rb} +4 -4
  90. data/examples/{08_entity_addressing_basic.rb → memory/10_entity_addressing_basic.rb} +8 -8
  91. data/examples/{08_entity_addressing_with_filtering.rb → memory/11_entity_addressing_with_filtering.rb} +6 -6
  92. data/examples/{09_regex_filtering_microservices.rb → memory/12_regex_filtering_microservices.rb} +2 -2
  93. data/examples/{10_header_block_configuration.rb → memory/13_header_block_configuration.rb} +6 -6
  94. data/examples/{11_global_configuration_example.rb → memory/14_global_configuration_demo.rb} +19 -8
  95. data/examples/{show_logger.rb → memory/15_logger_demo.rb} +1 -1
  96. data/examples/memory/README.md +163 -0
  97. data/examples/memory/memory_transport_architecture.svg +90 -0
  98. data/examples/memory/point_to_point_pattern.svg +94 -0
  99. data/examples/memory/publish_subscribe_pattern.svg +125 -0
  100. data/examples/{04_redis_smart_home_iot.rb → redis/01_smart_home_iot_demo.rb} +5 -5
  101. data/examples/redis/README.md +230 -0
  102. data/examples/redis/alert_system_flow.svg +127 -0
  103. data/examples/redis/dashboard_status_flow.svg +107 -0
  104. data/examples/redis/device_command_flow.svg +113 -0
  105. data/examples/redis/redis_transport_architecture.svg +115 -0
  106. data/examples/{smart_home_iot_dataflow.md → redis/smart_home_iot_dataflow.md} +4 -116
  107. data/examples/redis/smart_home_system_architecture.svg +133 -0
  108. data/examples/redis_enhanced/README.md +319 -0
  109. data/examples/redis_enhanced/enhanced_01_basic_patterns.rb +233 -0
  110. data/examples/redis_enhanced/enhanced_02_fluent_api.rb +331 -0
  111. data/examples/redis_enhanced/enhanced_03_dual_publishing.rb +281 -0
  112. data/examples/redis_enhanced/enhanced_04_advanced_routing.rb +419 -0
  113. data/examples/redis_queue/01_basic_messaging.rb +221 -0
  114. data/examples/redis_queue/01_comprehensive_examples.rb +508 -0
  115. data/examples/redis_queue/02_pattern_routing.rb +405 -0
  116. data/examples/redis_queue/03_fluent_api.rb +422 -0
  117. data/examples/redis_queue/04_load_balancing.rb +486 -0
  118. data/examples/redis_queue/05_microservices.rb +735 -0
  119. data/examples/redis_queue/06_emergency_alerts.rb +777 -0
  120. data/examples/redis_queue/07_queue_management.rb +587 -0
  121. data/examples/redis_queue/README.md +366 -0
  122. data/examples/redis_queue/enhanced_01_basic_patterns.rb +233 -0
  123. data/examples/redis_queue/enhanced_02_fluent_api.rb +331 -0
  124. data/examples/redis_queue/enhanced_03_dual_publishing.rb +281 -0
  125. data/examples/redis_queue/enhanced_04_advanced_routing.rb +419 -0
  126. data/examples/redis_queue/redis_queue_architecture.svg +148 -0
  127. data/ideas/README.md +41 -0
  128. data/ideas/agents.md +1001 -0
  129. data/ideas/database_transport.md +980 -0
  130. data/ideas/improvement.md +359 -0
  131. data/ideas/meshage.md +1788 -0
  132. data/ideas/message_discovery.md +178 -0
  133. data/ideas/message_schema.md +1381 -0
  134. data/lib/smart_message/.idea/.gitignore +8 -0
  135. data/lib/smart_message/.idea/markdown.xml +6 -0
  136. data/lib/smart_message/.idea/misc.xml +4 -0
  137. data/lib/smart_message/.idea/modules.xml +8 -0
  138. data/lib/smart_message/.idea/smart_message.iml +16 -0
  139. data/lib/smart_message/.idea/vcs.xml +6 -0
  140. data/lib/smart_message/addressing.rb +15 -0
  141. data/lib/smart_message/base.rb +0 -2
  142. data/lib/smart_message/configuration.rb +1 -1
  143. data/lib/smart_message/logger.rb +15 -4
  144. data/lib/smart_message/plugins.rb +5 -2
  145. data/lib/smart_message/serializer.rb +14 -0
  146. data/lib/smart_message/transport/redis_enhanced_transport.rb +399 -0
  147. data/lib/smart_message/transport/redis_queue_transport.rb +555 -0
  148. data/lib/smart_message/transport/registry.rb +1 -0
  149. data/lib/smart_message/transport.rb +34 -1
  150. data/lib/smart_message/version.rb +1 -1
  151. data/lib/smart_message.rb +5 -52
  152. data/mkdocs.yml +184 -0
  153. data/p2p_plan.md +326 -0
  154. data/p2p_roadmap.md +287 -0
  155. data/smart_message.gemspec +2 -0
  156. data/smart_message.svg +51 -0
  157. metadata +170 -44
  158. data/docs/README.md +0 -57
  159. data/examples/dead_letters.jsonl +0 -12
  160. data/examples/temp.txt +0 -94
  161. data/examples/tmux_chat/README.md +0 -283
  162. data/examples/tmux_chat/bot_agent.rb +0 -278
  163. data/examples/tmux_chat/human_agent.rb +0 -199
  164. data/examples/tmux_chat/room_monitor.rb +0 -160
  165. data/examples/tmux_chat/shared_chat_system.rb +0 -328
  166. data/examples/tmux_chat/start_chat_demo.sh +0 -190
  167. data/examples/tmux_chat/stop_chat_demo.sh +0 -22
  168. /data/docs/{properties.md → core-concepts/properties.md} +0 -0
  169. /data/docs/{ideas_to_think_about.md → development/ideas.md} +0 -0
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/multi_program_demo/fire_department.rb
3
+
4
+ require_relative '../../lib/smart_message'
5
+
6
+ require_relative 'messages/health_check_message'
7
+ require_relative 'messages/health_status_message'
8
+ require_relative 'messages/fire_emergency_message'
9
+ require_relative 'messages/fire_dispatch_message'
10
+ require_relative 'messages/emergency_resolved_message'
11
+ require_relative 'messages/emergency_911_message'
12
+
13
+ require_relative 'common/health_monitor'
14
+ require_relative 'common/logger'
15
+
16
+ class FireDepartment
17
+ include Common::HealthMonitor
18
+ include Common::Logger
19
+
20
+ def initialize
21
+ @service_name = 'fire_department'
22
+ @status = 'healthy'
23
+ @start_time = Time.now
24
+ @active_fires = {}
25
+ @available_engines = ['Engine-1', 'Engine-2', 'Engine-3', 'Ladder-1', 'Rescue-1']
26
+
27
+ setup_messaging
28
+ setup_signal_handlers
29
+ setup_health_monitor
30
+ end
31
+
32
+ # def setup_logging
33
+ # log_file = File.join(__dir__, 'fire_department.log')
34
+ # logger = Logger.new(log_file)
35
+ # logger.level = Logger::INFO
36
+ # logger.formatter = proc do |severity, datetime, progname, msg|
37
+ # "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
38
+ # end
39
+ # logger.info("Fire Department logging started")
40
+ # end
41
+
42
+ def setup_messaging
43
+ Messages::FireDispatchMessage.from = @service_name
44
+ Messages::EmergencyResolvedMessage.from = @service_name
45
+
46
+ # Subscribe to fire emergencies from houses
47
+ Messages::FireEmergencyMessage.subscribe(to: [@service_name, /fire/i]) do |message|
48
+ handle_fire_emergency(message)
49
+ end
50
+
51
+ # Subscribe to 911 calls routed from dispatch (medical, rescue, hazmat)
52
+ Messages::Emergency911Message.subscribe(to: [@service_name, /medical|rescue|hazmat/i]) do |message|
53
+ handle_911_call(message)
54
+ end
55
+
56
+ puts "🚒 Fire Department ready for service"
57
+ puts " Available engines: #{@available_engines.join(', ')}"
58
+ puts " Responding to health checks, fire emergencies, and 911 medical/rescue calls"
59
+ puts " Press Ctrl+C to stop\n\n"
60
+ logger.info("Fire Department ready for service with engines: #{@available_engines.join(', ')}")
61
+ end
62
+
63
+ def setup_signal_handlers
64
+ %w[INT TERM].each do |signal|
65
+ Signal.trap(signal) do
66
+ puts "\n🚒 Fire Department out of service..."
67
+ logger.info("Fire Department out of service")
68
+ exit(0)
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ def start_service
75
+ loop do
76
+ check_fire_resolutions
77
+ sleep(3)
78
+ end
79
+ rescue => e
80
+ puts "🚒 Error in fire service: #{e.message}"
81
+ logger.error("Error in fire service: #{e.message}")
82
+ retry
83
+ end
84
+
85
+ private
86
+
87
+ def service_emoji
88
+ "🚒"
89
+ end
90
+
91
+ def get_status_details
92
+ # Status depends on active fires and available engines
93
+ @status = if @active_fires.size >= 3
94
+ 'critical'
95
+ elsif @active_fires.size >= 2
96
+ 'warning'
97
+ elsif @available_engines.size < 2
98
+ 'warning'
99
+ else
100
+ 'healthy'
101
+ end
102
+
103
+ details = case @status
104
+ when 'healthy' then "All engines available, #{@active_fires.size} active fires"
105
+ when 'warning' then "#{@available_engines.size} engines available, #{@active_fires.size} active fires"
106
+ when 'critical' then "All engines deployed, #{@active_fires.size} major fires"
107
+ when 'failed' then "Equipment failure, emergency mutual aid requested"
108
+ end
109
+
110
+ [@status, details]
111
+ end
112
+
113
+ def handle_fire_emergency(emergency)
114
+ # Process fire emergency message with colored output
115
+ severity_color = case emergency.severity
116
+ when 'small' then "\e[33m" # Yellow
117
+ when 'medium' then "\e[93m" # Orange
118
+ when 'large' then "\e[31m" # Red
119
+ when 'out_of_control' then "\e[91m" # Bright red
120
+ else "\e[0m"
121
+ end
122
+
123
+ puts "🔥 #{severity_color}FIRE EMERGENCY\e[0m: #{emergency.house_address}"
124
+ puts " Type: #{emergency.fire_type} | Severity: #{emergency.severity.upcase}"
125
+ puts " Occupants: #{emergency.occupants_status}" if emergency.occupants_status
126
+ puts " Spread Risk: #{emergency.spread_risk}" if emergency.spread_risk
127
+ puts " Time: #{emergency.timestamp}"
128
+ logger.warn("FIRE EMERGENCY: #{emergency.fire_type} fire at #{emergency.house_address} (#{emergency.severity} severity)")
129
+
130
+ # Determine engines needed based on fire severity
131
+ engines_needed = case emergency.severity
132
+ when 'small' then 1
133
+ when 'medium' then 2
134
+ when 'large' then 3
135
+ when 'out_of_control' then @available_engines.size
136
+ else 2
137
+ end
138
+
139
+ assigned_engines = @available_engines.take(engines_needed)
140
+ @available_engines = @available_engines.drop(engines_needed)
141
+
142
+ # Determine equipment needed
143
+ equipment = determine_equipment_needed(emergency.fire_type, emergency.severity)
144
+
145
+ dispatch_id = SecureRandom.hex(4)
146
+ @active_fires[dispatch_id] = {
147
+ address: emergency.house_address,
148
+ start_time: Time.now,
149
+ engines: assigned_engines,
150
+ fire_type: emergency.fire_type,
151
+ severity: emergency.severity
152
+ }
153
+
154
+ # Send dispatch message
155
+ dispatch = Messages::FireDispatchMessage.new(
156
+ dispatch_id: dispatch_id,
157
+ engines_assigned: assigned_engines,
158
+ location: emergency.house_address,
159
+ fire_type: emergency.fire_type,
160
+ equipment_needed: equipment,
161
+ estimated_arrival: "#{rand(4..10)} minutes",
162
+ timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
163
+ )
164
+ dispatch.publish
165
+
166
+ puts "🚒 Dispatched #{assigned_engines.size} engines to #{emergency.house_address}"
167
+ puts " Fire type: #{emergency.fire_type} (#{emergency.severity})"
168
+ puts " Available engines: #{@available_engines.size}"
169
+ logger.info("Dispatched engines #{assigned_engines.join(', ')} to #{emergency.house_address} (#{dispatch_id})")
170
+ rescue => e
171
+ puts "🚒 Error handling fire emergency: #{e.message}"
172
+ logger.error("Error handling fire emergency: #{e.message}")
173
+ end
174
+
175
+ def handle_911_call(call)
176
+ # Color code based on emergency type
177
+ type_color = case call.emergency_type
178
+ when 'medical' then "\e[91m" # Bright red
179
+ when 'rescue' then "\e[33m" # Yellow
180
+ when 'hazmat' then "\e[35m" # Magenta
181
+ when 'accident' then "\e[31m" # Red (multi-car)
182
+ else "\e[36m" # Cyan
183
+ end
184
+
185
+ puts "\n#{type_color}🚒 911 EMERGENCY\e[0m from dispatch"
186
+ puts " 📍 Location: #{call.caller_location}"
187
+ puts " 🚨 Type: #{call.emergency_type.upcase}"
188
+ puts " 📝 Description: #{call.description}"
189
+ puts " 👤 Caller: #{call.caller_name || 'Unknown'}"
190
+
191
+ logger.warn("911 Emergency: #{call.emergency_type} at #{call.caller_location} - #{call.description}")
192
+
193
+ # Handle based on emergency type
194
+ case call.emergency_type
195
+ when 'medical'
196
+ handle_medical_call(call)
197
+ when 'rescue'
198
+ handle_rescue_call(call)
199
+ when 'hazmat'
200
+ handle_hazmat_call(call)
201
+ when 'accident'
202
+ handle_major_accident(call)
203
+ else
204
+ handle_general_emergency(call)
205
+ end
206
+ rescue => e
207
+ puts "🚒 Error handling 911 call: #{e.message}"
208
+ logger.error("Error handling 911 call: #{e.message}")
209
+ end
210
+
211
+ def handle_medical_call(call)
212
+ incident_id = "MED-#{Time.now.strftime('%H%M%S')}"
213
+
214
+ # Medical calls get rescue unit and potentially an engine
215
+ units_needed = call.severity == 'critical' ? 2 : 1
216
+ units_needed = [units_needed, @available_engines.size].min
217
+
218
+ # Prefer Rescue unit for medical
219
+ assigned_engines = []
220
+ if @available_engines.include?('Rescue-1')
221
+ assigned_engines << @available_engines.delete('Rescue-1')
222
+ units_needed -= 1
223
+ end
224
+ assigned_engines.concat(@available_engines.shift(units_needed)) if units_needed > 0
225
+
226
+ if assigned_engines.empty?
227
+ puts "🚒 ⚠️ No units available for medical response!"
228
+ logger.error("No units available for medical at #{call.caller_location}")
229
+ return
230
+ end
231
+
232
+ @active_fires[incident_id] = {
233
+ type: 'medical',
234
+ location: call.caller_location,
235
+ engines: assigned_engines,
236
+ start_time: Time.now,
237
+ severity: call.severity,
238
+ call: call
239
+ }
240
+
241
+ puts "🚒 Dispatched #{assigned_engines.join(', ')} to medical emergency"
242
+ puts " Injuries: #{call.number_of_victims || 'Unknown'} victims"
243
+ puts " Severity: #{call.severity&.upcase || 'UNKNOWN'}"
244
+ logger.info("Dispatched #{assigned_engines.join(', ')} to medical #{incident_id}")
245
+ end
246
+
247
+ def handle_rescue_call(call)
248
+ incident_id = "RSC-#{Time.now.strftime('%H%M%S')}"
249
+
250
+ # Rescue operations need ladder and rescue units
251
+ units_needed = 2
252
+ units_needed = [units_needed, @available_engines.size].min
253
+
254
+ # Prefer Ladder and Rescue units
255
+ assigned_engines = []
256
+ ['Rescue-1', 'Ladder-1'].each do |preferred|
257
+ if @available_engines.include?(preferred)
258
+ assigned_engines << @available_engines.delete(preferred)
259
+ units_needed -= 1
260
+ end
261
+ end
262
+ assigned_engines.concat(@available_engines.shift(units_needed)) if units_needed > 0
263
+
264
+ if assigned_engines.empty?
265
+ puts "🚒 ⚠️ No units available for rescue!"
266
+ logger.error("No units available for rescue at #{call.caller_location}")
267
+ return
268
+ end
269
+
270
+ @active_fires[incident_id] = {
271
+ type: 'rescue',
272
+ location: call.caller_location,
273
+ engines: assigned_engines,
274
+ start_time: Time.now,
275
+ severity: call.severity,
276
+ call: call
277
+ }
278
+
279
+ puts "🚒 Dispatched #{assigned_engines.join(', ')} to rescue operation"
280
+ puts " People trapped: #{call.number_of_victims || 'Unknown'}"
281
+ logger.info("Dispatched #{assigned_engines.join(', ')} to rescue #{incident_id}")
282
+ end
283
+
284
+ def handle_hazmat_call(call)
285
+ incident_id = "HAZ-#{Time.now.strftime('%H%M%S')}"
286
+
287
+ # Hazmat needs multiple units
288
+ units_needed = 3
289
+ units_needed = [units_needed, @available_engines.size].min
290
+ assigned_engines = @available_engines.shift(units_needed)
291
+
292
+ if assigned_engines.empty?
293
+ puts "🚒 ⚠️ No units available for hazmat response!"
294
+ logger.error("No units available for hazmat at #{call.caller_location}")
295
+ return
296
+ end
297
+
298
+ @active_fires[incident_id] = {
299
+ type: 'hazmat',
300
+ location: call.caller_location,
301
+ engines: assigned_engines,
302
+ start_time: Time.now,
303
+ severity: 'high',
304
+ call: call
305
+ }
306
+
307
+ puts "🚒 ⚠️ HAZMAT RESPONSE: #{assigned_engines.join(', ')} dispatched"
308
+ puts " Chemical hazard at #{call.caller_location}"
309
+ logger.warn("HAZMAT incident #{incident_id} - #{assigned_engines.join(', ')} dispatched")
310
+ end
311
+
312
+ def handle_major_accident(call)
313
+ incident_id = "MVA-#{Time.now.strftime('%H%M%S')}"
314
+
315
+ # Major accidents need rescue and engines
316
+ units_needed = call.vehicles_involved && call.vehicles_involved > 3 ? 3 : 2
317
+ units_needed = [units_needed, @available_engines.size].min
318
+ assigned_engines = @available_engines.shift(units_needed)
319
+
320
+ if assigned_engines.empty?
321
+ puts "🚒 ⚠️ No units available for accident response!"
322
+ logger.error("No units available for accident at #{call.caller_location}")
323
+ return
324
+ end
325
+
326
+ @active_fires[incident_id] = {
327
+ type: 'accident',
328
+ location: call.caller_location,
329
+ engines: assigned_engines,
330
+ start_time: Time.now,
331
+ severity: call.severity,
332
+ call: call
333
+ }
334
+
335
+ puts "🚒 Dispatched #{assigned_engines.join(', ')} to major accident"
336
+ puts " Vehicles: #{call.vehicles_involved || 'Multiple'}"
337
+ puts " Injuries: #{call.injuries_reported ? 'Yes' : 'Unknown'}"
338
+ logger.info("Dispatched #{assigned_engines.join(', ')} to accident #{incident_id}")
339
+ end
340
+
341
+ def handle_general_emergency(call)
342
+ incident_id = "EMG-#{Time.now.strftime('%H%M%S')}"
343
+
344
+ assigned_engines = @available_engines.shift(1)
345
+
346
+ if assigned_engines.empty?
347
+ puts "🚒 ⚠️ No units available!"
348
+ logger.error("No units available for emergency at #{call.caller_location}")
349
+ return
350
+ end
351
+
352
+ @active_fires[incident_id] = {
353
+ type: call.emergency_type,
354
+ location: call.caller_location,
355
+ engines: assigned_engines,
356
+ start_time: Time.now,
357
+ severity: call.severity,
358
+ call: call
359
+ }
360
+
361
+ puts "🚒 Dispatched #{assigned_engines.join(', ')} to #{call.emergency_type}"
362
+ logger.info("Dispatched #{assigned_engines.join(', ')} to #{call.emergency_type} #{incident_id}")
363
+ end
364
+
365
+ def check_fire_resolutions
366
+ @active_fires.each do |fire_id, fire|
367
+ # Fire resolution time depends on severity (10-15 seconds)
368
+ base_time = case fire[:severity]
369
+ when 'small' then 10..12
370
+ when 'medium' then 11..13
371
+ when 'large' then 12..14
372
+ when 'out_of_control' then 13..15
373
+ else 11..13
374
+ end
375
+
376
+ duration = (Time.now - fire[:start_time]).to_i
377
+ if duration > rand(base_time)
378
+ resolve_fire(fire_id, fire, duration)
379
+ end
380
+ end
381
+ end
382
+
383
+ def resolve_fire(fire_id, fire, duration_seconds)
384
+ # Return engines to available pool
385
+ @available_engines.concat(fire[:engines])
386
+ @active_fires.delete(fire_id)
387
+
388
+ outcomes = [
389
+ 'Fire extinguished successfully',
390
+ 'Fire contained with minimal damage',
391
+ 'Structure saved, investigating cause',
392
+ 'Total loss, area secured'
393
+ ]
394
+
395
+ resolution = Messages::EmergencyResolvedMessage.new(
396
+ incident_id: fire_id,
397
+ incident_type: "#{fire[:fire_type]} fire",
398
+ location: fire[:location],
399
+ resolved_by: @service_name,
400
+ resolution_time: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
401
+ duration_minutes: (duration_seconds / 60.0).round(1),
402
+ outcome: outcomes.sample,
403
+ units_involved: fire[:engines]
404
+ )
405
+ resolution.publish
406
+
407
+ puts "🚒 Fire #{fire_id} extinguished after #{(duration_seconds / 60.0).round(1)} minutes"
408
+ puts " Engines #{fire[:engines].join(', ')} returning to station"
409
+ logger.info("Fire #{fire_id} extinguished: #{outcomes.last} after #{(duration_seconds / 60.0).round(1)} minutes")
410
+ rescue => e
411
+ puts "🚒 Error resolving fire: #{e.message}"
412
+ logger.error("Error resolving fire: #{e.message}")
413
+ end
414
+
415
+ def determine_equipment_needed(fire_type, severity)
416
+ equipment = []
417
+
418
+ case fire_type
419
+ when 'kitchen'
420
+ equipment << 'Class K extinguishers'
421
+ when 'electrical'
422
+ equipment << 'Class C extinguishers'
423
+ equipment << 'Power shut-off tools'
424
+ when 'basement'
425
+ equipment << 'Ventilation fans'
426
+ equipment << 'Search and rescue gear'
427
+ when 'garage'
428
+ equipment << 'Foam suppressant'
429
+ when 'wildfire'
430
+ equipment << 'Brush trucks'
431
+ equipment << 'Water tenders'
432
+ end
433
+
434
+ if ['large', 'out_of_control'].include?(severity)
435
+ equipment << 'Aerial ladder'
436
+ equipment << 'Additional water supply'
437
+ end
438
+
439
+ equipment.join(', ')
440
+ end
441
+ end
442
+
443
+ if __FILE__ == $0
444
+ fire_dept = FireDepartment.new
445
+ fire_dept.start_service
446
+ end
@@ -0,0 +1,95 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="700" height="500" viewBox="0 0 700 500" xmlns="http://www.w3.org/2000/svg">
3
+ <defs>
4
+ <style>
5
+ .bg { fill: transparent; }
6
+ .house { fill: #1a3a1a; stroke: #4aff4a; stroke-width: 2; rx: 8; }
7
+ .fire-dept { fill: #4a2a1a; stroke: #ffaa4a; stroke-width: 2; rx: 8; }
8
+ .all-services { fill: #2a2a3a; stroke: #4a9eff; stroke-width: 2; rx: 8; }
9
+ .message { stroke: #ff4a4a; stroke-width: 3; fill: none; marker-end: url(#arrowhead); }
10
+ .broadcast { stroke: #4aff4a; stroke-width: 2; fill: none; marker-end: url(#arrowhead-green); }
11
+ .text { fill: #ffffff; font-family: 'Arial', sans-serif; font-size: 12px; }
12
+ .title { fill: #4a9eff; font-family: 'Arial', sans-serif; font-size: 18px; font-weight: bold; }
13
+ .subtitle { fill: #ffaa4a; font-family: 'Arial', sans-serif; font-size: 11px; }
14
+ .note { fill: #aaaaaa; font-family: 'Arial', sans-serif; font-size: 10px; }
15
+ .process { fill: #3a2a1a; stroke: #ffaa4a; stroke-width: 1; rx: 4; }
16
+ </style>
17
+ <marker id="arrowhead" markerWidth="8" markerHeight="6"
18
+ refX="8" refY="3" orient="auto">
19
+ <polygon points="0 0, 8 3, 0 6" fill="#ff4a4a" />
20
+ </marker>
21
+ <marker id="arrowhead-green" markerWidth="8" markerHeight="6"
22
+ refX="8" refY="3" orient="auto">
23
+ <polygon points="0 0, 8 3, 0 6" fill="#4aff4a" />
24
+ </marker>
25
+ </defs>
26
+
27
+ <!-- Background -->
28
+ <rect class="bg" width="700" height="500"/>
29
+
30
+ <!-- Title -->
31
+ <text x="350" y="25" text-anchor="middle" class="title">Fire Emergency Flow</text>
32
+ <text x="350" y="45" text-anchor="middle" class="subtitle">House Fire Detection and Emergency Response</text>
33
+
34
+ <!-- Participants -->
35
+ <rect x="50" y="80" width="120" height="80" class="house"/>
36
+ <text x="110" y="105" text-anchor="middle" class="text">🏠 House</text>
37
+ <text x="110" y="125" text-anchor="middle" class="subtitle">Smoke Detection</text>
38
+ <text x="110" y="145" text-anchor="middle" class="subtitle">Safety System</text>
39
+
40
+ <rect x="290" y="80" width="120" height="80" class="fire-dept"/>
41
+ <text x="350" y="105" text-anchor="middle" class="text">🚒 Fire</text>
42
+ <text x="350" y="125" text-anchor="middle" class="text">Department</text>
43
+ <text x="350" y="145" text-anchor="middle" class="subtitle">Emergency Response</text>
44
+
45
+ <rect x="530" y="80" width="120" height="80" class="all-services"/>
46
+ <text x="590" y="105" text-anchor="middle" class="text">📢 All Services</text>
47
+ <text x="590" y="125" text-anchor="middle" class="subtitle">City Emergency</text>
48
+ <text x="590" y="145" text-anchor="middle" class="subtitle">Network</text>
49
+
50
+ <!-- Vertical lifelines -->
51
+ <line x1="110" y1="160" x2="110" y2="450" stroke="#666" stroke-width="2" stroke-dasharray="3,3"/>
52
+ <line x1="350" y1="160" x2="350" y2="450" stroke="#666" stroke-width="2" stroke-dasharray="3,3"/>
53
+ <line x1="590" y1="160" x2="590" y2="450" stroke="#666" stroke-width="2" stroke-dasharray="3,3"/>
54
+
55
+ <!-- Message Flow -->
56
+ <!-- 1. Fire emergency detected -->
57
+ <path d="M 110 200 L 350 200" class="message"/>
58
+ <text x="230" y="195" text-anchor="middle" class="text">FireEmergencyMessage</text>
59
+ <rect x="160" y="210" width="140" height="50" fill="#1a1a1a" stroke="#ff4a4a" rx="4"/>
60
+ <text x="230" y="225" text-anchor="middle" class="note">Smoke detected</text>
61
+ <text x="230" y="240" text-anchor="middle" class="note">Fire type: electrical/grease</text>
62
+ <text x="230" y="255" text-anchor="middle" class="note">Severity: critical/high/medium</text>
63
+
64
+ <!-- 2. Fire department assessment -->
65
+ <rect x="290" y="280" width="120" height="30" class="process"/>
66
+ <text x="350" y="300" text-anchor="middle" class="note">Assess fire severity</text>
67
+
68
+ <rect x="290" y="320" width="120" height="40" class="process"/>
69
+ <text x="350" y="340" text-anchor="middle" class="note">Dispatch equipment:</text>
70
+ <text x="350" y="355" text-anchor="middle" class="note">Engine-1, Ladder-1, Rescue-1</text>
71
+
72
+ <!-- 3. Dispatch broadcast -->
73
+ <path d="M 350 380 L 590 380" class="broadcast"/>
74
+ <text x="470" y="375" text-anchor="middle" class="text">FireDispatchMessage</text>
75
+ <rect x="420" y="390" width="120" height="40" fill="#2a3a2a" stroke="#4aff4a" rx="4"/>
76
+ <text x="480" y="405" text-anchor="middle" class="note">Equipment dispatched</text>
77
+ <text x="480" y="420" text-anchor="middle" class="note">ETA: 3-8 minutes</text>
78
+
79
+ <!-- 4. Resolution broadcast -->
80
+ <path d="M 350 440 L 590 440" class="broadcast"/>
81
+ <text x="470" y="435" text-anchor="middle" class="text">EmergencyResolvedMessage</text>
82
+ <text x="470" y="455" text-anchor="middle" class="note">Fire suppressed • Scene secure</text>
83
+
84
+ <!-- Process flow indicators -->
85
+ <circle cx="350" cy="320" r="3" fill="#ffaa4a"/>
86
+ <circle cx="350" cy="350" r="3" fill="#ffaa4a"/>
87
+
88
+ <!-- Equipment details -->
89
+ <rect x="50" y="380" width="200" height="80" fill="#1a1a1a" stroke="#ffaa4a" rx="6"/>
90
+ <text x="60" y="400" class="text">Fire Department Equipment:</text>
91
+ <text x="60" y="415" class="note">• Engine-1: Primary response unit</text>
92
+ <text x="60" y="430" class="note">• Ladder-1: Aerial rescue operations</text>
93
+ <text x="60" y="445" class="note">• Rescue-1: Emergency medical support</text>
94
+ <text x="60" y="460" class="note">• Hazmat-1: Hazardous materials unit</text>
95
+ </svg>
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/multi_program_demo/health_department.rb
3
+
4
+ HEALTH_CHECK_RATE = 5 # seconds; setting to zero gets overruns and 1300+ msg/sec
5
+
6
+ require_relative '../../lib/smart_message'
7
+ require_relative 'messages/health_check_message'
8
+ require_relative 'messages/health_status_message'
9
+
10
+ require_relative 'common/logger'
11
+
12
+ class HealthDepartment
13
+ include Common::Logger
14
+
15
+ def initialize
16
+ @service_name = 'health-department'
17
+ Messages::HealthCheckMessage.from = @service_name
18
+ @check_id = 0
19
+
20
+ setup_messaging
21
+ setup_signal_handlers
22
+ end
23
+
24
+ def setup_logging
25
+ log_file = File.join(__dir__, 'health_department.log')
26
+ logger = Logger.new(log_file)
27
+ logger.level = Logger::INFO
28
+ logger.formatter = proc do |severity, datetime, progname, msg|
29
+ "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
30
+ end
31
+ logger.info("Health Department logging started")
32
+ end
33
+
34
+ def setup_messaging
35
+ # Subscribe to all broadcast health status messages
36
+ Messages::HealthStatusMessage.subscribe(broadcast: true, to: @service_name) do |message|
37
+ # Process health status message with colored output
38
+ status_color = case message.status
39
+ when 'healthy' then "\e[32m" # Green
40
+ when 'warning' then "\e[33m" # Yellow
41
+ when 'critical' then "\e[93m" # Orange
42
+ when 'failed' then "\e[31m" # Red
43
+ else "\e[0m" # Reset
44
+ end
45
+
46
+ puts "#{status_color}#{message.service_name}: #{message.status.upcase}\e[0m #{message.details}"
47
+ logger.info("Received health status: #{message.service_name} = #{message.status} (#{message.details})")
48
+ end
49
+
50
+ puts "🏥 Health Department started"
51
+ puts " Monitoring city services health status..."
52
+ puts " Will send HealthCheck broadcasts every 5 seconds"
53
+ puts " Logging to: health_department.log"
54
+ puts " Press Ctrl+C to stop\n\n"
55
+ logger.info("Health Department started monitoring city services")
56
+ end
57
+
58
+ def setup_signal_handlers
59
+ %w[INT TERM].each do |signal|
60
+ Signal.trap(signal) do
61
+ puts "\n🏥 Health Department shutting down..."
62
+ logger.info("Health Department shutting down")
63
+ exit(0)
64
+ end
65
+ end
66
+ end
67
+
68
+ def start_monitoring
69
+ loop do
70
+ send_health_check
71
+ sleep(HEALTH_CHECK_RATE)
72
+ end
73
+ rescue => e
74
+ puts "🏥 Error in health monitoring: #{e.message}"
75
+ logger.error("Error in health monitoring: #{e.message}")
76
+ retry
77
+ end
78
+
79
+ private
80
+
81
+ def send_health_check
82
+ @check_id += 1
83
+
84
+ health_check = Messages::HealthCheckMessage.new(check_id: @check_id)
85
+
86
+ puts
87
+ puts "🏥 Broadcasting health check ##{@check_id} (#{health_check.uuid[0..7]}...)"
88
+ logger.info("Broadcasting health check ##{@check_id}")
89
+
90
+ health_check.publish
91
+ rescue => e
92
+ puts "🏥 Error sending health check: #{e.message}"
93
+ logger.error("Error sending health check: #{e.message}")
94
+ end
95
+ end
96
+
97
+ if __FILE__ == $0
98
+ health_dept = HealthDepartment.new
99
+ health_dept.start_monitoring
100
+ end