circuit_breaker-wf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +52 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +116 -0
  6. data/LICENSE +21 -0
  7. data/README.md +324 -0
  8. data/examples/document/README.md +150 -0
  9. data/examples/document/document_assistant.rb +535 -0
  10. data/examples/document/document_rules.rb +60 -0
  11. data/examples/document/document_token.rb +83 -0
  12. data/examples/document/document_workflow.rb +114 -0
  13. data/examples/document/mock_executor.rb +80 -0
  14. data/lib/circuit_breaker/executors/README.md +664 -0
  15. data/lib/circuit_breaker/executors/agent_executor.rb +187 -0
  16. data/lib/circuit_breaker/executors/assistant_executor.rb +245 -0
  17. data/lib/circuit_breaker/executors/base_executor.rb +56 -0
  18. data/lib/circuit_breaker/executors/docker_executor.rb +56 -0
  19. data/lib/circuit_breaker/executors/dsl.rb +97 -0
  20. data/lib/circuit_breaker/executors/llm/memory.rb +82 -0
  21. data/lib/circuit_breaker/executors/llm/tools.rb +94 -0
  22. data/lib/circuit_breaker/executors/nats_executor.rb +230 -0
  23. data/lib/circuit_breaker/executors/serverless_executor.rb +25 -0
  24. data/lib/circuit_breaker/executors/step_executor.rb +47 -0
  25. data/lib/circuit_breaker/history.rb +81 -0
  26. data/lib/circuit_breaker/rules.rb +251 -0
  27. data/lib/circuit_breaker/templates/mermaid.html.erb +51 -0
  28. data/lib/circuit_breaker/templates/plantuml.html.erb +55 -0
  29. data/lib/circuit_breaker/token.rb +486 -0
  30. data/lib/circuit_breaker/visualizer.rb +173 -0
  31. data/lib/circuit_breaker/workflow_dsl.rb +359 -0
  32. data/lib/circuit_breaker.rb +236 -0
  33. data/workflow-editor/.gitignore +24 -0
  34. data/workflow-editor/README.md +106 -0
  35. data/workflow-editor/eslint.config.js +28 -0
  36. data/workflow-editor/index.html +13 -0
  37. data/workflow-editor/package-lock.json +6864 -0
  38. data/workflow-editor/package.json +50 -0
  39. data/workflow-editor/postcss.config.js +6 -0
  40. data/workflow-editor/public/vite.svg +1 -0
  41. data/workflow-editor/src/App.css +42 -0
  42. data/workflow-editor/src/App.tsx +365 -0
  43. data/workflow-editor/src/assets/react.svg +1 -0
  44. data/workflow-editor/src/components/AddNodeButton.tsx +68 -0
  45. data/workflow-editor/src/components/EdgeDetails.tsx +175 -0
  46. data/workflow-editor/src/components/NodeDetails.tsx +177 -0
  47. data/workflow-editor/src/components/ResizablePanel.tsx +74 -0
  48. data/workflow-editor/src/components/SaveButton.tsx +45 -0
  49. data/workflow-editor/src/config/change_workflow.yaml +59 -0
  50. data/workflow-editor/src/config/constants.ts +11 -0
  51. data/workflow-editor/src/config/flowConfig.ts +189 -0
  52. data/workflow-editor/src/config/uiConfig.ts +77 -0
  53. data/workflow-editor/src/config/workflow.yaml +58 -0
  54. data/workflow-editor/src/hooks/useKeyPress.ts +29 -0
  55. data/workflow-editor/src/index.css +34 -0
  56. data/workflow-editor/src/main.tsx +10 -0
  57. data/workflow-editor/src/server/saveWorkflow.ts +81 -0
  58. data/workflow-editor/src/utils/saveWorkflow.ts +92 -0
  59. data/workflow-editor/src/utils/workflowLoader.ts +26 -0
  60. data/workflow-editor/src/utils/workflowTransformer.ts +91 -0
  61. data/workflow-editor/src/vite-env.d.ts +1 -0
  62. data/workflow-editor/src/yaml.d.ts +4 -0
  63. data/workflow-editor/tailwind.config.js +15 -0
  64. data/workflow-editor/tsconfig.app.json +26 -0
  65. data/workflow-editor/tsconfig.json +7 -0
  66. data/workflow-editor/tsconfig.node.json +24 -0
  67. data/workflow-editor/vite.config.ts +8 -0
  68. metadata +267 -0
@@ -0,0 +1,535 @@
1
+ module Examples
2
+ # Document Assistant using Ollama for content analysis and suggestions
3
+ class DocumentAssistant
4
+ class AnalyzeContentTool < CircuitBreaker::Executors::LLM::Tool
5
+ def initialize
6
+ super(
7
+ name: 'analyze_content',
8
+ description: 'Analyze document content and provide feedback',
9
+ parameters: {
10
+ content: { type: 'string', description: 'Document content to analyze' },
11
+ word_count: { type: 'integer', description: 'Current word count' },
12
+ min_words: { type: 'integer', description: 'Minimum required words' }
13
+ }
14
+ )
15
+ end
16
+
17
+ def execute(content:, word_count:, min_words: 100)
18
+ analysis = []
19
+ analysis << analyze_length(word_count, min_words)
20
+ analysis << analyze_structure(content)
21
+ analysis << analyze_clarity(content)
22
+ analysis << analyze_completeness(content)
23
+ analysis.join("\n\n")
24
+ rescue => e
25
+ "Error analyzing content: #{e.message}"
26
+ end
27
+
28
+ private
29
+
30
+ def analyze_length(word_count, min_words)
31
+ <<~ANALYSIS
32
+ Word Count Analysis:
33
+ - Current count: #{word_count} words
34
+ - Minimum required: #{min_words} words
35
+ - Status: #{word_count >= min_words ? 'Meets' : 'Does not meet'} requirement
36
+ ANALYSIS
37
+ end
38
+
39
+ def analyze_structure(content)
40
+ return "Unable to analyze structure" unless content.is_a?(String)
41
+
42
+ paragraphs = content.split(/\n\n+/).size
43
+ sections = content.scan(/^[A-Z][^.!?]*[:|\n]/).size
44
+
45
+ <<~ANALYSIS
46
+ Structure Analysis:
47
+ - Paragraphs: #{paragraphs}
48
+ - Sections: #{sections}
49
+ - Assessment: #{assess_structure(paragraphs, sections)}
50
+ ANALYSIS
51
+ end
52
+
53
+ def assess_structure(paragraphs, sections)
54
+ if paragraphs < 3
55
+ "Consider adding more paragraphs for better organization"
56
+ elsif sections < 2
57
+ "Consider adding section headings for better structure"
58
+ else
59
+ "Good structural organization"
60
+ end
61
+ end
62
+
63
+ def analyze_clarity(content)
64
+ return "Unable to analyze clarity" unless content.is_a?(String)
65
+
66
+ sentences = content.split(/[.!?]/).map(&:strip).reject(&:empty?)
67
+ long_sentences = sentences.select { |s| s.split.size > 20 }
68
+ passive_voice = content.scan(/\b(?:is|are|was|were|be|been|being)\s+\w+ed\b/i).size
69
+
70
+ <<~ANALYSIS
71
+ Clarity Analysis:
72
+ - Long sentences: #{long_sentences.size}
73
+ - Passive voice instances: #{passive_voice}
74
+ - Recommendations: #{clarity_recommendations(long_sentences.size, passive_voice)}
75
+ ANALYSIS
76
+ end
77
+
78
+ def clarity_recommendations(long_sentences, passive_voice)
79
+ recommendations = []
80
+ recommendations << "Break down long sentences" if long_sentences > 0
81
+ recommendations << "Use more active voice" if passive_voice > 2
82
+ recommendations.empty? ? "Good clarity" : recommendations.join(", ")
83
+ end
84
+
85
+ def analyze_completeness(content)
86
+ return "Unable to analyze completeness" unless content.is_a?(String)
87
+
88
+ key_sections = {
89
+ introduction: content.downcase.include?('introduction'),
90
+ background: content.downcase.include?('background'),
91
+ methodology: content.downcase.include?('methodology'),
92
+ conclusion: content.downcase.include?('conclusion')
93
+ }
94
+
95
+ <<~ANALYSIS
96
+ Completeness Analysis:
97
+ - Key sections present: #{key_sections.count { |_, present| present }}/#{key_sections.size}
98
+ - Missing sections: #{missing_sections(key_sections)}
99
+ ANALYSIS
100
+ end
101
+
102
+ def missing_sections(sections)
103
+ missing = sections.reject { |_, present| present }.keys
104
+ missing.empty? ? "None" : missing.map(&:to_s).join(", ")
105
+ end
106
+ end
107
+
108
+ class SuggestImprovementsTool < CircuitBreaker::Executors::LLM::Tool
109
+ def initialize
110
+ super(
111
+ name: 'suggest_improvements',
112
+ description: 'Suggest specific improvements for the document',
113
+ parameters: {
114
+ content: { type: 'string', description: 'Document content' },
115
+ feedback_type: { type: 'string', description: 'Type of feedback (structure/clarity/completeness)' }
116
+ }
117
+ )
118
+ end
119
+
120
+ def execute(content:, feedback_type:)
121
+ case feedback_type.downcase
122
+ when 'structure'
123
+ suggest_structure_improvements(content)
124
+ when 'clarity'
125
+ suggest_clarity_improvements(content)
126
+ when 'completeness'
127
+ suggest_completeness_improvements(content)
128
+ else
129
+ "Unknown feedback type: #{feedback_type}"
130
+ end
131
+ rescue => e
132
+ "Error suggesting improvements: #{e.message}"
133
+ end
134
+
135
+ private
136
+
137
+ def suggest_structure_improvements(content)
138
+ return "Unable to analyze structure" unless content.is_a?(String)
139
+
140
+ paragraphs = content.split(/\n\n+/)
141
+ suggestions = []
142
+
143
+ suggestions << structure_organization_suggestions(paragraphs)
144
+ suggestions << structure_formatting_suggestions(content)
145
+ suggestions << structure_transition_suggestions(paragraphs)
146
+
147
+ format_suggestions("Structure Improvements", suggestions.flatten)
148
+ end
149
+
150
+ def structure_organization_suggestions(paragraphs)
151
+ suggestions = []
152
+ suggestions << "Break content into more sections" if paragraphs.size < 3
153
+ suggestions << "Add clear section headings" unless content.match?(/^#|^[A-Z].*:/)
154
+ suggestions
155
+ end
156
+
157
+ def structure_formatting_suggestions(content)
158
+ suggestions = []
159
+ suggestions << "Use bullet points for lists" unless content.match?(/^\s*[-*]/)
160
+ suggestions << "Add emphasis using bold or italics" unless content.match?(/\*\*|\*/)
161
+ suggestions
162
+ end
163
+
164
+ def structure_transition_suggestions(paragraphs)
165
+ return [] if paragraphs.size <= 1
166
+ ["Add transition sentences between paragraphs"]
167
+ end
168
+
169
+ def suggest_clarity_improvements(content)
170
+ return "Unable to analyze clarity" unless content.is_a?(String)
171
+
172
+ sentences = content.split(/[.!?]/).map(&:strip).reject(&:empty?)
173
+ suggestions = []
174
+
175
+ suggestions << clarity_sentence_suggestions(sentences)
176
+ suggestions << clarity_voice_suggestions(content)
177
+ suggestions << clarity_word_choice_suggestions(content)
178
+
179
+ format_suggestions("Clarity Improvements", suggestions.flatten)
180
+ end
181
+
182
+ def clarity_sentence_suggestions(sentences)
183
+ suggestions = []
184
+ if sentences.any? { |s| s.split.size > 20 }
185
+ suggestions << "Break down long sentences into shorter ones"
186
+ suggestions << "Use more punctuation to improve readability"
187
+ end
188
+ suggestions
189
+ end
190
+
191
+ def clarity_voice_suggestions(content)
192
+ suggestions = []
193
+ if content.match?(/\b(?:is|are|was|were)\s+\w+ed\b/i)
194
+ suggestions << "Use active voice instead of passive voice"
195
+ suggestions << "Make sentences more direct"
196
+ end
197
+ suggestions
198
+ end
199
+
200
+ def clarity_word_choice_suggestions(content)
201
+ suggestions = []
202
+ if content.match?(/\b(?:very|really|quite|extremely)\b/i)
203
+ suggestions << "Use stronger, more specific words"
204
+ suggestions << "Remove unnecessary intensifiers"
205
+ end
206
+ suggestions
207
+ end
208
+
209
+ def suggest_completeness_improvements(content)
210
+ return "Unable to analyze completeness" unless content.is_a?(String)
211
+
212
+ suggestions = []
213
+ suggestions << completeness_section_suggestions(content)
214
+ suggestions << completeness_detail_suggestions(content)
215
+ suggestions << completeness_reference_suggestions(content)
216
+
217
+ format_suggestions("Completeness Improvements", suggestions.flatten)
218
+ end
219
+
220
+ def completeness_section_suggestions(content)
221
+ suggestions = []
222
+ %w[introduction background methodology results conclusion].each do |section|
223
+ suggestions << "Add #{section} section" unless content.downcase.include?(section)
224
+ end
225
+ suggestions
226
+ end
227
+
228
+ def completeness_detail_suggestions(content)
229
+ suggestions = []
230
+ suggestions << "Include more specific examples" if content.scan(/(?:for example|such as|like)/).size < 2
231
+ suggestions << "Add supporting data or evidence" if content.scan(/\d+%|\d+\.\d+/).size < 2
232
+ suggestions
233
+ end
234
+
235
+ def completeness_reference_suggestions(content)
236
+ suggestions = []
237
+ suggestions << "Add citations or references" unless content.match?(/\[\d+\]|\(\w+,\s*\d{4}\)/)
238
+ suggestions
239
+ end
240
+
241
+ def format_suggestions(title, suggestions)
242
+ return "No improvements needed" if suggestions.empty?
243
+
244
+ [
245
+ title,
246
+ "=" * title.length,
247
+ "",
248
+ suggestions.map { |s| "- #{s}" }
249
+ ].flatten.join("\n")
250
+ end
251
+ end
252
+
253
+ class SentimentAnalysisTool < CircuitBreaker::Executors::LLM::Tool
254
+ def initialize
255
+ super(
256
+ name: 'analyze_sentiment',
257
+ description: 'Analyze the sentiment and tone of the document',
258
+ parameters: {
259
+ content: { type: 'string', description: 'Document content to analyze' },
260
+ context: { type: 'string', description: 'Document context or type', default: 'general' }
261
+ }
262
+ )
263
+ end
264
+
265
+ def execute(content:, context: 'general')
266
+ return "Unable to analyze sentiment" unless content.is_a?(String)
267
+
268
+ analysis = []
269
+ analysis << analyze_overall_sentiment(content)
270
+ analysis << analyze_emotional_tone(content)
271
+ analysis << analyze_formality(content)
272
+ analysis << context_specific_analysis(content, context)
273
+ analysis.join("\n\n")
274
+ rescue => e
275
+ "Error analyzing sentiment: #{e.message}"
276
+ end
277
+
278
+ private
279
+
280
+ def analyze_overall_sentiment(content)
281
+ # Simple sentiment indicators
282
+ positive_words = %w[good great excellent amazing wonderful positive success successful achieve accomplished beneficial effective]
283
+ negative_words = %w[bad poor terrible horrible negative fail failure difficult problem issue concern]
284
+
285
+ positive_count = count_matches(content, positive_words)
286
+ negative_count = count_matches(content, negative_words)
287
+
288
+ sentiment_score = calculate_sentiment_score(positive_count, negative_count)
289
+
290
+ <<~ANALYSIS
291
+ Overall Sentiment Analysis:
292
+ - Sentiment Score: #{sentiment_score}/10
293
+ - Positive Indicators: #{positive_count}
294
+ - Negative Indicators: #{negative_count}
295
+ - Overall Tone: #{describe_sentiment(sentiment_score)}
296
+ ANALYSIS
297
+ end
298
+
299
+ def analyze_emotional_tone(content)
300
+ emotional_indicators = {
301
+ confidence: %w[confident assured certain definitely clearly],
302
+ uncertainty: %w[maybe perhaps possibly might potentially],
303
+ urgency: %w[urgent immediate critical essential crucial],
304
+ caution: %w[careful cautious warning risk concern]
305
+ }
306
+
307
+ tone_analysis = emotional_indicators.transform_values { |words| count_matches(content, words) }
308
+
309
+ <<~ANALYSIS
310
+ Emotional Tone Analysis:
311
+ - Confidence Level: #{describe_intensity(tone_analysis[:confidence])}
312
+ - Uncertainty Level: #{describe_intensity(tone_analysis[:uncertainty])}
313
+ - Urgency Level: #{describe_intensity(tone_analysis[:urgency])}
314
+ - Caution Level: #{describe_intensity(tone_analysis[:caution])}
315
+ ANALYSIS
316
+ end
317
+
318
+ def analyze_formality(content)
319
+ formal_indicators = %w[furthermore moreover consequently therefore thus accordingly]
320
+ informal_indicators = %w[like well anyway actually basically]
321
+
322
+ formal_count = count_matches(content, formal_indicators)
323
+ informal_count = count_matches(content, informal_indicators)
324
+
325
+ formality_level = calculate_formality_level(formal_count, informal_count)
326
+
327
+ <<~ANALYSIS
328
+ Formality Analysis:
329
+ - Formality Level: #{formality_level}
330
+ - Formal Language Usage: #{describe_intensity(formal_count)}
331
+ - Informal Language Usage: #{describe_intensity(informal_count)}
332
+ #{formality_recommendations(formality_level)}
333
+ ANALYSIS
334
+ end
335
+
336
+ def context_specific_analysis(content, context)
337
+ case context.downcase
338
+ when 'technical'
339
+ analyze_technical_tone(content)
340
+ when 'business'
341
+ analyze_business_tone(content)
342
+ when 'academic'
343
+ analyze_academic_tone(content)
344
+ else
345
+ analyze_general_tone(content)
346
+ end
347
+ end
348
+
349
+ private
350
+
351
+ def count_matches(content, words)
352
+ words.sum { |word| content.downcase.scan(/\b#{word}\b/).count }
353
+ end
354
+
355
+ def calculate_sentiment_score(positive, negative)
356
+ return 5 if positive == 0 && negative == 0
357
+ total = positive + negative
358
+ return 5 if total == 0
359
+ ((positive.to_f / total) * 10).round(1)
360
+ end
361
+
362
+ def describe_sentiment(score)
363
+ case score
364
+ when 0..3 then "Strongly Negative"
365
+ when 3..4 then "Negative"
366
+ when 4..6 then "Neutral"
367
+ when 6..8 then "Positive"
368
+ else "Strongly Positive"
369
+ end
370
+ end
371
+
372
+ def describe_intensity(count)
373
+ case count
374
+ when 0 then "Very Low"
375
+ when 1..2 then "Low"
376
+ when 3..5 then "Moderate"
377
+ when 6..8 then "High"
378
+ else "Very High"
379
+ end
380
+ end
381
+
382
+ def calculate_formality_level(formal, informal)
383
+ total = formal + informal
384
+ return "Neutral" if total == 0
385
+
386
+ ratio = formal.to_f / (formal + informal)
387
+ case ratio
388
+ when 0..0.2 then "Very Informal"
389
+ when 0.2..0.4 then "Informal"
390
+ when 0.4..0.6 then "Neutral"
391
+ when 0.6..0.8 then "Formal"
392
+ else "Very Formal"
393
+ end
394
+ end
395
+
396
+ def formality_recommendations(level)
397
+ case level
398
+ when "Very Informal"
399
+ "Recommendation: Consider using more formal language for professional documents"
400
+ when "Informal"
401
+ "Recommendation: Slightly increase formal language usage"
402
+ when "Very Formal"
403
+ "Recommendation: Consider adding some less formal elements for better engagement"
404
+ else
405
+ "Recommendation: Current formality level is appropriate"
406
+ end
407
+ end
408
+
409
+ def analyze_technical_tone(content)
410
+ technical_terms = %w[implementation algorithm framework architecture database api interface]
411
+ jargon_count = count_matches(content, technical_terms)
412
+
413
+ <<~ANALYSIS
414
+ Technical Context Analysis:
415
+ - Technical Term Usage: #{describe_intensity(jargon_count)}
416
+ - Technical Depth: #{jargon_count > 5 ? "High" : "Moderate"}
417
+ ANALYSIS
418
+ end
419
+
420
+ def analyze_business_tone(content)
421
+ business_terms = %w[roi stakeholder deliverable milestone objective strategic]
422
+ business_focus = count_matches(content, business_terms)
423
+
424
+ <<~ANALYSIS
425
+ Business Context Analysis:
426
+ - Business Term Usage: #{describe_intensity(business_focus)}
427
+ - Business Focus: #{business_focus > 5 ? "Strong" : "Moderate"}
428
+ ANALYSIS
429
+ end
430
+
431
+ def analyze_academic_tone(content)
432
+ academic_terms = %w[research study analysis methodology hypothesis conclusion evidence]
433
+ academic_focus = count_matches(content, academic_terms)
434
+
435
+ <<~ANALYSIS
436
+ Academic Context Analysis:
437
+ - Academic Term Usage: #{describe_intensity(academic_focus)}
438
+ - Academic Rigor: #{academic_focus > 5 ? "High" : "Moderate"}
439
+ ANALYSIS
440
+ end
441
+
442
+ def analyze_general_tone(content)
443
+ <<~ANALYSIS
444
+ General Tone Analysis:
445
+ - Accessibility: #{analyze_accessibility(content)}
446
+ - Engagement Level: #{analyze_engagement(content)}
447
+ ANALYSIS
448
+ end
449
+
450
+ def analyze_accessibility(content)
451
+ complex_words = content.scan(/\b\w{12,}\b/).size
452
+ average_sentence_length = content.split(/[.!?]/).map(&:split).map(&:size).sum.to_f / content.split(/[.!?]/).size
453
+
454
+ if complex_words > 5 || average_sentence_length > 20
455
+ "Complex - Consider simplifying"
456
+ else
457
+ "Good - Easy to understand"
458
+ end
459
+ end
460
+
461
+ def analyze_engagement(content)
462
+ engagement_markers = %w[you your we our they them it his her imagine consider note]
463
+ engagement_level = count_matches(content, engagement_markers)
464
+
465
+ describe_intensity(engagement_level)
466
+ end
467
+ end
468
+
469
+ class << self
470
+ def define(&block)
471
+ new.tap { |assistant| assistant.instance_eval(&block) if block_given? }
472
+ end
473
+ end
474
+
475
+ def initialize(model = 'qwen2.5-coder')
476
+ @executor = CircuitBreaker::Executors::AssistantExecutor.define do
477
+ use_model model
478
+ with_system_prompt "You are a document review assistant. You help analyze documents and provide constructive feedback."
479
+ with_parameters(
480
+ temperature: 0.7,
481
+ top_p: 0.9,
482
+ top_k: 40
483
+ )
484
+ add_tools [
485
+ AnalyzeContentTool.new,
486
+ SuggestImprovementsTool.new,
487
+ SentimentAnalysisTool.new
488
+ ]
489
+ end
490
+ end
491
+
492
+ def analyze_document(token)
493
+ @executor
494
+ .update_context(input: generate_analysis_prompt(token))
495
+ .execute
496
+
497
+ result = @executor.result
498
+ if result && result[:output]
499
+ result[:output][:content]
500
+ else
501
+ "Error analyzing document"
502
+ end
503
+ end
504
+
505
+ private
506
+
507
+ def generate_analysis_prompt(token)
508
+ <<~PROMPT
509
+ Please analyze this document and provide feedback:
510
+
511
+ Title: #{token.title}
512
+ Content: #{token.content}
513
+ Current Word Count: #{token.word_count}
514
+ Priority: #{token.priority}
515
+ Context: #{determine_document_context(token)}
516
+
517
+ Please:
518
+ 1. Analyze the content quality and structure
519
+ 2. Analyze the sentiment and tone
520
+ 3. Suggest specific improvements
521
+ 4. Check if it meets minimum length requirements
522
+ 5. Provide any additional recommendations
523
+
524
+ Use the available tools to perform the analysis.
525
+ PROMPT
526
+ end
527
+
528
+ def determine_document_context(token)
529
+ return 'technical' if token.content.downcase.match?(/\b(?:code|algorithm|implementation|api|database)\b/)
530
+ return 'business' if token.content.downcase.match?(/\b(?:roi|stakeholder|business|market|strategy)\b/)
531
+ return 'academic' if token.content.downcase.match?(/\b(?:research|study|hypothesis|methodology)\b/)
532
+ 'general'
533
+ end
534
+ end
535
+ end
@@ -0,0 +1,60 @@
1
+ require_relative '../../lib/circuit_breaker'
2
+
3
+ module Examples
4
+ module Document
5
+ module Rules
6
+ def self.define
7
+ CircuitBreaker::Rules::DSL.define do
8
+ # Content validation rules
9
+ rule :valid_word_count do |token|
10
+ analysis = context.get_result(:analysis)
11
+ analysis && analysis[:word_count] >= 50
12
+ end
13
+
14
+ rule :valid_clarity do |token|
15
+ clarity = context.get_result(:clarity)
16
+ clarity && clarity[:score] >= 70
17
+ end
18
+
19
+ rule :valid_completeness do |token|
20
+ completeness = context.get_result(:completeness)
21
+ completeness && completeness[:score] >= 80
22
+ end
23
+
24
+ # Review rules
25
+ rule :valid_review_metrics do |token|
26
+ review = context.get_result(:review)
27
+ review && review[:metrics][:word_count] >= 50 &&
28
+ review[:metrics][:clarity_score] >= 70 &&
29
+ review[:metrics][:completeness_score] >= 80
30
+ end
31
+
32
+ rule :is_high_priority do |token|
33
+ token.priority == "high"
34
+ end
35
+
36
+ rule :is_urgent do |token|
37
+ token.priority == "urgent"
38
+ end
39
+
40
+ # Approval rules
41
+ rule :valid_approver do |token|
42
+ final = context.get_result(:final)
43
+ final && final[:status] == "APPROVED"
44
+ end
45
+
46
+ rule :approved_status do |token|
47
+ final = context.get_result(:final)
48
+ final && final[:status] == "APPROVED"
49
+ end
50
+
51
+ # Rejection rules
52
+ rule :has_rejection_reasons do |token|
53
+ rejection = context.get_result(:rejection)
54
+ rejection && !rejection[:reasons].empty?
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,83 @@
1
+ require 'securerandom'
2
+ require_relative '../../lib/circuit_breaker/token'
3
+
4
+ module Examples
5
+ module Document
6
+ class DocumentToken < CircuitBreaker::Token
7
+ # Define valid states
8
+ states :draft, :pending_review, :reviewed, :approved, :rejected
9
+
10
+ # Define document attributes with types
11
+ attribute :title, String
12
+ attribute :content, String
13
+ attribute :author_id, String
14
+ attribute :reviewer_id, String
15
+ attribute :approver_id, String
16
+ attribute :reviewer_comments, String
17
+ attribute :rejection_reason, String
18
+ attribute :tags, Array
19
+ attribute :priority, String, allowed: %w[low medium high urgent]
20
+ attribute :due_date, Time
21
+ attribute :word_count, Integer
22
+ attribute :external_url, String
23
+
24
+ # Define timestamp fields and state messages
25
+ state_configs do
26
+ state :pending_review,
27
+ timestamps: :submitted_at,
28
+ message: ->(t) { "Document submitted for review by #{t.reviewer_id}" }
29
+
30
+ state :reviewed,
31
+ timestamps: :reviewed_at,
32
+ message: ->(t) { "Document reviewed by #{t.reviewer_id} with comments" }
33
+
34
+ state :approved,
35
+ timestamps: :approved_at,
36
+ message: ->(t) { "Document approved by #{t.approver_id}" }
37
+
38
+ state :rejected,
39
+ timestamps: :rejected_at,
40
+ message: ->(t) { "Document rejected with reason: #{t.rejection_reason}" }
41
+
42
+ # Handle shared timestamp
43
+ on_states [:approved, :rejected], timestamps: :completed_at
44
+ end
45
+
46
+ def initialize
47
+ @id = SecureRandom.uuid
48
+ @state = :draft
49
+ @content = <<~CONTENT
50
+ Introduction
51
+ This is a detailed document that shows our workflow system. We aim to create
52
+ a robust and clear process for managing documents effectively.
53
+
54
+ Background
55
+ We need to ensure documents meet quality standards. This means checking the
56
+ word count, clarity of writing, and completeness of content. Each document
57
+ goes through several stages of review.
58
+
59
+ The process uses smart tools to check document quality. These tools look at
60
+ various aspects like word count and readability. They help make sure our
61
+ documents are clear and complete.
62
+
63
+ Conclusion
64
+ By following these rules and steps, we keep our documents clear and useful.
65
+ The workflow helps us maintain high standards and ensures good quality.
66
+ Thank you for reading this example document.
67
+ CONTENT
68
+ @priority = "high"
69
+ @created_at = Time.now
70
+ @updated_at = Time.now
71
+ @history = []
72
+ end
73
+
74
+ def to_json(include_private = false)
75
+ JSON.pretty_generate(to_h(include_private))
76
+ end
77
+
78
+ def to_yaml(include_private = false)
79
+ to_h(include_private).to_yaml
80
+ end
81
+ end
82
+ end
83
+ end