lex-cognitive-triage 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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/CLAUDE.md +122 -0
- data/Gemfile +13 -0
- data/README.md +74 -0
- data/lex-cognitive-triage.gemspec +32 -0
- data/lib/legion/extensions/cognitive_triage/client.rb +15 -0
- data/lib/legion/extensions/cognitive_triage/helpers/constants.rb +66 -0
- data/lib/legion/extensions/cognitive_triage/helpers/demand.rb +97 -0
- data/lib/legion/extensions/cognitive_triage/helpers/triage_engine.rb +146 -0
- data/lib/legion/extensions/cognitive_triage/runners/cognitive_triage.rb +106 -0
- data/lib/legion/extensions/cognitive_triage/version.rb +9 -0
- data/lib/legion/extensions/cognitive_triage.rb +15 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bf115104e45fd4f342847472fb98867351280610072bb62c78b1ef4bb4e756bd
|
|
4
|
+
data.tar.gz: d474d79895e4100c895667618175e79c7a0588ed73b8b7ccbef3c6a331b26791
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 487a5574ef1b037ae3c10aaf6e38430177033e7f4875d8ccc86a10784fefd245632ae206a6b0e0d01bc7509d6679a2414f2effee11f1b58d7b7ddbd23f86b5c8
|
|
7
|
+
data.tar.gz: dce224d41e81c12b73a93363c8ddc6a1e99aad237515ff2384fb639c30cb8eb3bd549101a1049fa2c4525dc35bd8de82c2ca9a83349cf918557488b92ea3a6e7
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
|
+
|
|
11
|
+
release:
|
|
12
|
+
needs: ci
|
|
13
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
|
+
secrets:
|
|
16
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 3.4
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Style/Documentation:
|
|
7
|
+
Enabled: false
|
|
8
|
+
|
|
9
|
+
Naming/PredicateMethod:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Naming/PredicatePrefix:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Metrics/ClassLength:
|
|
16
|
+
Max: 150
|
|
17
|
+
|
|
18
|
+
Metrics/MethodLength:
|
|
19
|
+
Max: 25
|
|
20
|
+
|
|
21
|
+
Metrics/AbcSize:
|
|
22
|
+
Max: 25
|
|
23
|
+
|
|
24
|
+
Metrics/ParameterLists:
|
|
25
|
+
Max: 8
|
|
26
|
+
MaxOptionalParameters: 8
|
|
27
|
+
|
|
28
|
+
Layout/HashAlignment:
|
|
29
|
+
EnforcedHashRocketStyle: table
|
|
30
|
+
EnforcedColonStyle: table
|
|
31
|
+
|
|
32
|
+
Metrics/BlockLength:
|
|
33
|
+
Exclude:
|
|
34
|
+
- 'spec/**/*'
|
|
35
|
+
|
|
36
|
+
Style/FrozenStringLiteralComment:
|
|
37
|
+
Enabled: true
|
|
38
|
+
|
|
39
|
+
Style/OneClassPerFile:
|
|
40
|
+
Exclude:
|
|
41
|
+
- 'spec/spec_helper.rb'
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# lex-cognitive-triage
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-cognitive-triage`
|
|
6
|
+
- **Version**: 0.1.0
|
|
7
|
+
- **Namespace**: `Legion::Extensions::CognitiveTriage`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Models cognitive demand management as a triage system. Demands are incoming cognitive tasks or stimuli with severity and urgency levels. A priority score is computed and demands are color-coded (red/orange/yellow/green/white). Adding demands drains cognitive capacity; completing or dropping them restores it. When capacity falls below the overload threshold, the system is flagged as overloaded.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Gemspec**: `lex-cognitive-triage.gemspec`
|
|
16
|
+
- **Require**: `lex-cognitive-triage`
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
- **License**: MIT
|
|
19
|
+
- **Homepage**: https://github.com/LegionIO/lex-cognitive-triage
|
|
20
|
+
|
|
21
|
+
## File Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/legion/extensions/cognitive_triage/
|
|
25
|
+
version.rb
|
|
26
|
+
helpers/
|
|
27
|
+
constants.rb # Severity/urgency levels and weights, triage/capacity/queue label tables
|
|
28
|
+
demand.rb # Demand class — one cognitive demand with triage score and lifecycle
|
|
29
|
+
triage_engine.rb # TriageEngine — demand registry with capacity management
|
|
30
|
+
runners/
|
|
31
|
+
cognitive_triage.rb # Runner module — public API
|
|
32
|
+
client.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key Constants
|
|
36
|
+
|
|
37
|
+
| Constant | Value | Meaning |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `MAX_DEMANDS` | 500 | Hard cap; oldest completed (or lowest score) pruned |
|
|
40
|
+
| `MAX_QUEUE_SIZE` | 100 | Reference for queue pressure calculation |
|
|
41
|
+
| `CAPACITY_DEFAULT` | 1.0 | Starting cognitive capacity |
|
|
42
|
+
| `CAPACITY_DRAIN` | 0.05 | Max capacity drain per demand add (multiplied by triage score) |
|
|
43
|
+
| `CAPACITY_RESTORE` | 0.03 | Capacity restored per complete or drop |
|
|
44
|
+
| `OVERLOAD_THRESHOLD` | 0.2 | Capacity <= this = overloaded |
|
|
45
|
+
|
|
46
|
+
Triage score formula: `severity_weight * 0.6 + urgency_weight * 0.4`
|
|
47
|
+
|
|
48
|
+
`SEVERITY_LEVELS`: `[:critical, :major, :moderate, :minor, :trivial]` with weights `[1.0, 0.8, 0.5, 0.3, 0.1]`
|
|
49
|
+
|
|
50
|
+
`URGENCY_LEVELS`: `[:immediate, :urgent, :soon, :deferred, :indefinite]` with weights `[1.0, 0.8, 0.5, 0.3, 0.1]`
|
|
51
|
+
|
|
52
|
+
Triage color labels: `0.8+` = `:red`, `0.6..0.8` = `:orange`, `0.4..0.6` = `:yellow`, `0.2..0.4` = `:green`, `<0.2` = `:white`
|
|
53
|
+
|
|
54
|
+
Capacity labels: `0.8+` = `:fresh`, `0.6..0.8` = `:engaged`, `0.4..0.6` = `:strained`, `0.2..0.4` = `:depleted`, `<0.2` = `:overloaded`
|
|
55
|
+
|
|
56
|
+
Queue labels (by `active / MAX_QUEUE_SIZE`): `0.8+` = `:overwhelmed`, `0.6..0.8` = `:heavy`, `0.4..0.6` = `:moderate`, `0.2..0.4` = `:light`, `<0.2` = `:empty`
|
|
57
|
+
|
|
58
|
+
## Key Classes
|
|
59
|
+
|
|
60
|
+
### `Helpers::Demand`
|
|
61
|
+
|
|
62
|
+
One cognitive demand with a lifecycle.
|
|
63
|
+
|
|
64
|
+
- `triage!` / `defer!` / `process!` / `complete!` / `drop!` — state transitions
|
|
65
|
+
- `active?` — status in `[:pending, :triaged, :processing]`
|
|
66
|
+
- `red?` — triage_score >= 0.8
|
|
67
|
+
- `triage_label` — color label for triage score
|
|
68
|
+
- `triage_score` — computed at init: `severity_weight * 0.6 + urgency_weight * 0.4`; immutable
|
|
69
|
+
- Invalid severity/urgency silently defaults to `:moderate`/`:soon`
|
|
70
|
+
|
|
71
|
+
### `Helpers::TriageEngine`
|
|
72
|
+
|
|
73
|
+
Demand registry with capacity tracking.
|
|
74
|
+
|
|
75
|
+
- `add_demand(description:, domain:, severity:, urgency:)` — prunes if at `MAX_DEMANDS`; drains capacity by `triage_score * CAPACITY_DRAIN`; auto-calls `triage!`
|
|
76
|
+
- `process_demand(demand_id:)` — sets to `:processing`; fails if not active
|
|
77
|
+
- `complete_demand(demand_id:)` — sets to `:completed`; restores `CAPACITY_RESTORE`
|
|
78
|
+
- `defer_demand(demand_id:)` — sets to `:deferred`; restores `CAPACITY_RESTORE * 0.5`
|
|
79
|
+
- `drop_demand(demand_id:)` — sets to `:dropped`; restores `CAPACITY_RESTORE`
|
|
80
|
+
- `next_demand` — active demand with highest triage score
|
|
81
|
+
- `red_demands` — all demands with score >= 0.8
|
|
82
|
+
- `overloaded?` — capacity <= `OVERLOAD_THRESHOLD`
|
|
83
|
+
- `queue_pressure` — `active_count / MAX_QUEUE_SIZE`
|
|
84
|
+
- `triage_report` — full status hash
|
|
85
|
+
- `prune_if_needed` (private) — removes oldest completed demand; if none, removes lowest-score demand
|
|
86
|
+
|
|
87
|
+
## Runners
|
|
88
|
+
|
|
89
|
+
Module: `Legion::Extensions::CognitiveTriage::Runners::CognitiveTriage`
|
|
90
|
+
|
|
91
|
+
| Runner | Key Args | Returns |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `add_demand` | `description:`, `domain:`, `severity:`, `urgency:` | `{ success:, demand:, capacity: }` |
|
|
94
|
+
| `process_demand` | `demand_id:` | `{ success:, demand: }` or error |
|
|
95
|
+
| `complete_demand` | `demand_id:` | `{ success:, demand:, capacity: }` or error |
|
|
96
|
+
| `defer_demand` | `demand_id:` | `{ success:, demand:, capacity: }` or error |
|
|
97
|
+
| `drop_demand` | `demand_id:` | `{ success:, demand:, capacity: }` or error |
|
|
98
|
+
| `next_demand` | — | `{ success:, found:, demand: }` |
|
|
99
|
+
| `active_demands` | — | `{ success:, demands:, count: }` |
|
|
100
|
+
| `red_demands` | — | `{ success:, demands:, count: }` |
|
|
101
|
+
| `demands_by_severity` | `severity:` | `{ success:, demands:, count: }` |
|
|
102
|
+
| `demands_by_domain` | `domain:` | `{ success:, demands:, count: }` |
|
|
103
|
+
| `capacity_status` | — | `{ success:, capacity:, capacity_label:, overloaded:, queue_pressure:, queue_label: }` |
|
|
104
|
+
| `triage_report` | — | `{ success:, report: }` |
|
|
105
|
+
|
|
106
|
+
All runners accept optional `engine:` keyword for test injection.
|
|
107
|
+
|
|
108
|
+
## Integration Points
|
|
109
|
+
|
|
110
|
+
- No actors defined; driven by external task events or `lex-tick` phases
|
|
111
|
+
- `add_demand` is the entry point for all incoming cognitive demands
|
|
112
|
+
- `next_demand` feeds into `lex-tick`'s `action_selection` phase — selects highest-priority demand
|
|
113
|
+
- `overloaded?` can gate new commitments or trigger `lex-consent` escalation
|
|
114
|
+
- All state is in-memory per `TriageEngine` instance
|
|
115
|
+
|
|
116
|
+
## Development Notes
|
|
117
|
+
|
|
118
|
+
- Capacity drain per demand is `triage_score * CAPACITY_DRAIN`, not a flat `CAPACITY_DRAIN` — critical/immediate demands drain 5x more than trivial/indefinite
|
|
119
|
+
- `add_demand` automatically calls `triage!` after creation — demand moves from `:pending` to `:triaged` immediately
|
|
120
|
+
- `prune_if_needed` prefers to evict completed demands; only evicts active if no completed exist
|
|
121
|
+
- Demand IDs are UUID strings, not sequential symbols
|
|
122
|
+
- `demands_by_severity` and `demands_by_domain` return ALL demands (including completed/dropped), not only active ones
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# lex-cognitive-triage
|
|
2
|
+
|
|
3
|
+
A LegionIO cognitive architecture extension that models cognitive demand management as a triage system. Incoming demands are scored by severity and urgency, color-coded by priority, and managed through a lifecycle as capacity allows.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Tracks **demands** — cognitive tasks or stimuli requiring attention. Each demand has:
|
|
8
|
+
|
|
9
|
+
- A severity (`:critical`, `:major`, `:moderate`, `:minor`, `:trivial`)
|
|
10
|
+
- An urgency (`:immediate`, `:urgent`, `:soon`, `:deferred`, `:indefinite`)
|
|
11
|
+
- A triage score: `severity * 0.6 + urgency * 0.4`
|
|
12
|
+
- A color label: `:red` (0.8+), `:orange`, `:yellow`, `:green`, `:white`
|
|
13
|
+
|
|
14
|
+
Adding a demand drains cognitive capacity. Completing or dropping demands restores it. When capacity drops below 0.2, the system is overloaded.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require 'lex-cognitive-triage'
|
|
20
|
+
|
|
21
|
+
client = Legion::Extensions::CognitiveTriage::Client.new
|
|
22
|
+
|
|
23
|
+
# Register an incoming demand
|
|
24
|
+
result = client.add_demand(
|
|
25
|
+
description: 'Ethical conflict detected in action plan',
|
|
26
|
+
domain: :safety,
|
|
27
|
+
severity: :critical,
|
|
28
|
+
urgency: :immediate
|
|
29
|
+
)
|
|
30
|
+
# => { success: true, demand: { triage_score: 1.0, triage_label: :red, status: :triaged, ... }, capacity: 0.95 }
|
|
31
|
+
|
|
32
|
+
demand_id = result[:demand][:id]
|
|
33
|
+
|
|
34
|
+
# What should be worked on next?
|
|
35
|
+
client.next_demand
|
|
36
|
+
# => { success: true, found: true, demand: { triage_score: 1.0, triage_label: :red, ... } }
|
|
37
|
+
|
|
38
|
+
# All red-priority demands
|
|
39
|
+
client.red_demands
|
|
40
|
+
# => { success: true, demands: [...], count: 1 }
|
|
41
|
+
|
|
42
|
+
# Begin processing
|
|
43
|
+
client.process_demand(demand_id: demand_id)
|
|
44
|
+
# => { success: true, demand: { status: :processing, ... } }
|
|
45
|
+
|
|
46
|
+
# Mark complete — restores capacity
|
|
47
|
+
client.complete_demand(demand_id: demand_id)
|
|
48
|
+
# => { success: true, demand: { status: :completed, ... }, capacity: 0.98 }
|
|
49
|
+
|
|
50
|
+
# Defer a lower-priority demand
|
|
51
|
+
d2 = client.add_demand(description: 'Schedule review', severity: :minor, urgency: :deferred)
|
|
52
|
+
client.defer_demand(demand_id: d2[:demand][:id])
|
|
53
|
+
# => { success: true, demand: { status: :deferred, ... }, capacity: 0.965 }
|
|
54
|
+
|
|
55
|
+
# Check capacity state
|
|
56
|
+
client.capacity_status
|
|
57
|
+
# => { success: true, capacity: 0.965, capacity_label: :fresh, overloaded: false, queue_pressure: 0.0, queue_label: :empty }
|
|
58
|
+
|
|
59
|
+
# Full report
|
|
60
|
+
client.triage_report
|
|
61
|
+
# => { success: true, report: { total_demands: 2, active_count: 0, red_count: 0, capacity: 0.965, overloaded: false, ... } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bundle install
|
|
68
|
+
bundle exec rspec
|
|
69
|
+
bundle exec rubocop
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_triage/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-triage'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveTriage::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Emergency cognitive prioritization for LegionIO'
|
|
12
|
+
spec.description = 'Triage system for cognitive overload. Classifies incoming demands by severity ' \
|
|
13
|
+
'and urgency, routes to appropriate processing queues, and manages cognitive ' \
|
|
14
|
+
'capacity under pressure.'
|
|
15
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-triage'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
18
|
+
spec.required_ruby_version = '>= 3.4'
|
|
19
|
+
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-triage'
|
|
22
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-triage/blob/master/README.md'
|
|
23
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-triage/blob/master/CHANGELOG.md'
|
|
24
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-triage/issues'
|
|
25
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
26
|
+
|
|
27
|
+
spec.files = Dir.chdir(__dir__) do
|
|
28
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
29
|
+
end
|
|
30
|
+
spec.require_paths = ['lib']
|
|
31
|
+
spec.add_development_dependency 'legion-gaia'
|
|
32
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTriage
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::CognitiveTriage
|
|
8
|
+
|
|
9
|
+
def initialize(engine: nil)
|
|
10
|
+
@default_engine = engine || Helpers::TriageEngine.new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTriage
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_DEMANDS = 500
|
|
9
|
+
MAX_QUEUE_SIZE = 100
|
|
10
|
+
CAPACITY_DEFAULT = 1.0
|
|
11
|
+
CAPACITY_DRAIN = 0.05
|
|
12
|
+
CAPACITY_RESTORE = 0.03
|
|
13
|
+
OVERLOAD_THRESHOLD = 0.2
|
|
14
|
+
|
|
15
|
+
SEVERITY_LEVELS = %i[critical major moderate minor trivial].freeze
|
|
16
|
+
URGENCY_LEVELS = %i[immediate urgent soon deferred indefinite].freeze
|
|
17
|
+
|
|
18
|
+
SEVERITY_WEIGHTS = {
|
|
19
|
+
critical: 1.0,
|
|
20
|
+
major: 0.8,
|
|
21
|
+
moderate: 0.5,
|
|
22
|
+
minor: 0.3,
|
|
23
|
+
trivial: 0.1
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
URGENCY_WEIGHTS = {
|
|
27
|
+
immediate: 1.0,
|
|
28
|
+
urgent: 0.8,
|
|
29
|
+
soon: 0.5,
|
|
30
|
+
deferred: 0.3,
|
|
31
|
+
indefinite: 0.1
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
TRIAGE_LABELS = {
|
|
35
|
+
(0.8..) => :red,
|
|
36
|
+
(0.6...0.8) => :orange,
|
|
37
|
+
(0.4...0.6) => :yellow,
|
|
38
|
+
(0.2...0.4) => :green,
|
|
39
|
+
(..0.2) => :white
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
CAPACITY_LABELS = {
|
|
43
|
+
(0.8..) => :fresh,
|
|
44
|
+
(0.6...0.8) => :engaged,
|
|
45
|
+
(0.4...0.6) => :strained,
|
|
46
|
+
(0.2...0.4) => :depleted,
|
|
47
|
+
(..0.2) => :overloaded
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
QUEUE_LABELS = {
|
|
51
|
+
(0.8..) => :overwhelmed,
|
|
52
|
+
(0.6...0.8) => :heavy,
|
|
53
|
+
(0.4...0.6) => :moderate,
|
|
54
|
+
(0.2...0.4) => :light,
|
|
55
|
+
(..0.2) => :empty
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
def self.label_for(labels, value)
|
|
59
|
+
match = labels.find { |range, _| range.cover?(value) }
|
|
60
|
+
match&.last
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveTriage
|
|
8
|
+
module Helpers
|
|
9
|
+
class Demand
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :description, :domain, :severity, :urgency,
|
|
13
|
+
:triage_score, :status, :created_at, :triaged_at
|
|
14
|
+
|
|
15
|
+
def initialize(description:, domain: :general, severity: :moderate, urgency: :soon)
|
|
16
|
+
@id = SecureRandom.uuid
|
|
17
|
+
@description = description
|
|
18
|
+
@domain = domain.to_sym
|
|
19
|
+
@severity = validate_level(severity, SEVERITY_LEVELS, :moderate)
|
|
20
|
+
@urgency = validate_level(urgency, URGENCY_LEVELS, :soon)
|
|
21
|
+
@triage_score = compute_triage_score
|
|
22
|
+
@status = :pending
|
|
23
|
+
@created_at = Time.now.utc
|
|
24
|
+
@triaged_at = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def triage!
|
|
28
|
+
@status = :triaged
|
|
29
|
+
@triaged_at = Time.now.utc
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def defer!
|
|
34
|
+
@status = :deferred
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def process!
|
|
39
|
+
@status = :processing
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def complete!
|
|
44
|
+
@status = :completed
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def drop!
|
|
49
|
+
@status = :dropped
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def active?
|
|
54
|
+
%i[pending triaged processing].include?(@status)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def triage_label
|
|
58
|
+
Constants.label_for(TRIAGE_LABELS, @triage_score)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def red?
|
|
62
|
+
@triage_score >= 0.8
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_h
|
|
66
|
+
{
|
|
67
|
+
id: @id,
|
|
68
|
+
description: @description,
|
|
69
|
+
domain: @domain,
|
|
70
|
+
severity: @severity,
|
|
71
|
+
urgency: @urgency,
|
|
72
|
+
triage_score: @triage_score,
|
|
73
|
+
triage_label: triage_label,
|
|
74
|
+
status: @status,
|
|
75
|
+
red: red?,
|
|
76
|
+
created_at: @created_at,
|
|
77
|
+
triaged_at: @triaged_at
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def validate_level(level, valid_levels, default)
|
|
84
|
+
sym = level.to_sym
|
|
85
|
+
valid_levels.include?(sym) ? sym : default
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def compute_triage_score
|
|
89
|
+
sev = SEVERITY_WEIGHTS.fetch(@severity, 0.5)
|
|
90
|
+
urg = URGENCY_WEIGHTS.fetch(@urgency, 0.5)
|
|
91
|
+
((sev * 0.6) + (urg * 0.4)).round(10)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTriage
|
|
6
|
+
module Helpers
|
|
7
|
+
class TriageEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :capacity
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@demands = {}
|
|
14
|
+
@capacity = CAPACITY_DEFAULT
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_demand(description:, domain: :general, severity: :moderate, urgency: :soon)
|
|
18
|
+
prune_if_needed
|
|
19
|
+
demand = Demand.new(
|
|
20
|
+
description: description, domain: domain, severity: severity, urgency: urgency
|
|
21
|
+
)
|
|
22
|
+
@demands[demand.id] = demand
|
|
23
|
+
drain_capacity!(demand.triage_score * CAPACITY_DRAIN)
|
|
24
|
+
demand.triage!
|
|
25
|
+
demand
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def process_demand(demand_id:)
|
|
29
|
+
demand = @demands[demand_id]
|
|
30
|
+
return nil unless demand&.active?
|
|
31
|
+
|
|
32
|
+
demand.process!
|
|
33
|
+
demand
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def complete_demand(demand_id:)
|
|
37
|
+
demand = @demands[demand_id]
|
|
38
|
+
return nil unless demand
|
|
39
|
+
|
|
40
|
+
demand.complete!
|
|
41
|
+
restore_capacity!(CAPACITY_RESTORE)
|
|
42
|
+
demand
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def defer_demand(demand_id:)
|
|
46
|
+
demand = @demands[demand_id]
|
|
47
|
+
return nil unless demand&.active?
|
|
48
|
+
|
|
49
|
+
demand.defer!
|
|
50
|
+
restore_capacity!(CAPACITY_RESTORE * 0.5)
|
|
51
|
+
demand
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def drop_demand(demand_id:)
|
|
55
|
+
demand = @demands[demand_id]
|
|
56
|
+
return nil unless demand&.active?
|
|
57
|
+
|
|
58
|
+
demand.drop!
|
|
59
|
+
restore_capacity!(CAPACITY_RESTORE)
|
|
60
|
+
demand
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def next_demand
|
|
64
|
+
active_demands.max_by(&:triage_score)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def active_demands = @demands.values.select(&:active?)
|
|
68
|
+
def red_demands = @demands.values.select(&:red?)
|
|
69
|
+
def completed_demands = @demands.values.select { |d| d.status == :completed }
|
|
70
|
+
def dropped_demands = @demands.values.select { |d| d.status == :dropped }
|
|
71
|
+
def deferred_demands = @demands.values.select { |d| d.status == :deferred }
|
|
72
|
+
|
|
73
|
+
def demands_by_severity(severity:)
|
|
74
|
+
@demands.values.select { |d| d.severity == severity.to_sym }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def demands_by_domain(domain:)
|
|
78
|
+
@demands.values.select { |d| d.domain == domain.to_sym }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def overloaded?
|
|
82
|
+
@capacity <= OVERLOAD_THRESHOLD
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def queue_pressure
|
|
86
|
+
return 0.0 if @demands.empty?
|
|
87
|
+
|
|
88
|
+
(active_demands.size.to_f / MAX_QUEUE_SIZE).clamp(0.0, 1.0).round(10)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def capacity_label = Constants.label_for(CAPACITY_LABELS, @capacity)
|
|
92
|
+
def queue_label = Constants.label_for(QUEUE_LABELS, queue_pressure)
|
|
93
|
+
|
|
94
|
+
def restore_capacity!(amount = CAPACITY_RESTORE)
|
|
95
|
+
@capacity = (@capacity + amount).clamp(0.0, 1.0).round(10)
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def triage_report
|
|
100
|
+
{
|
|
101
|
+
total_demands: @demands.size,
|
|
102
|
+
active_count: active_demands.size,
|
|
103
|
+
red_count: red_demands.size,
|
|
104
|
+
completed: completed_demands.size,
|
|
105
|
+
dropped: dropped_demands.size,
|
|
106
|
+
deferred: deferred_demands.size,
|
|
107
|
+
capacity: @capacity,
|
|
108
|
+
capacity_label: capacity_label,
|
|
109
|
+
overloaded: overloaded?,
|
|
110
|
+
queue_pressure: queue_pressure,
|
|
111
|
+
queue_label: queue_label,
|
|
112
|
+
next_demand: next_demand&.to_h
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def to_h
|
|
117
|
+
{
|
|
118
|
+
total_demands: @demands.size,
|
|
119
|
+
active: active_demands.size,
|
|
120
|
+
capacity: @capacity,
|
|
121
|
+
overloaded: overloaded?
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def drain_capacity!(amount)
|
|
128
|
+
@capacity = (@capacity - amount).clamp(0.0, 1.0).round(10)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def prune_if_needed
|
|
132
|
+
return if @demands.size < MAX_DEMANDS
|
|
133
|
+
|
|
134
|
+
oldest_completed = completed_demands.min_by(&:created_at)
|
|
135
|
+
if oldest_completed
|
|
136
|
+
@demands.delete(oldest_completed.id)
|
|
137
|
+
else
|
|
138
|
+
lowest = @demands.values.min_by(&:triage_score)
|
|
139
|
+
@demands.delete(lowest.id) if lowest
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTriage
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveTriage
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
9
|
+
|
|
10
|
+
def add_demand(description:, domain: :general, severity: :moderate, urgency: :soon, engine: nil, **)
|
|
11
|
+
eng = engine || default_engine
|
|
12
|
+
demand = eng.add_demand(
|
|
13
|
+
description: description, domain: domain, severity: severity, urgency: urgency
|
|
14
|
+
)
|
|
15
|
+
{ success: true, demand: demand.to_h, capacity: eng.capacity }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_demand(demand_id:, engine: nil, **)
|
|
19
|
+
eng = engine || default_engine
|
|
20
|
+
demand = eng.process_demand(demand_id: demand_id)
|
|
21
|
+
return { success: false, error: 'demand not found or not active' } unless demand
|
|
22
|
+
|
|
23
|
+
{ success: true, demand: demand.to_h }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def complete_demand(demand_id:, engine: nil, **)
|
|
27
|
+
eng = engine || default_engine
|
|
28
|
+
demand = eng.complete_demand(demand_id: demand_id)
|
|
29
|
+
return { success: false, error: 'demand not found' } unless demand
|
|
30
|
+
|
|
31
|
+
{ success: true, demand: demand.to_h, capacity: eng.capacity }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def defer_demand(demand_id:, engine: nil, **)
|
|
35
|
+
eng = engine || default_engine
|
|
36
|
+
demand = eng.defer_demand(demand_id: demand_id)
|
|
37
|
+
return { success: false, error: 'demand not found or not active' } unless demand
|
|
38
|
+
|
|
39
|
+
{ success: true, demand: demand.to_h, capacity: eng.capacity }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def drop_demand(demand_id:, engine: nil, **)
|
|
43
|
+
eng = engine || default_engine
|
|
44
|
+
demand = eng.drop_demand(demand_id: demand_id)
|
|
45
|
+
return { success: false, error: 'demand not found or not active' } unless demand
|
|
46
|
+
|
|
47
|
+
{ success: true, demand: demand.to_h, capacity: eng.capacity }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def next_demand(engine: nil, **)
|
|
51
|
+
eng = engine || default_engine
|
|
52
|
+
demand = eng.next_demand
|
|
53
|
+
return { success: true, found: false } unless demand
|
|
54
|
+
|
|
55
|
+
{ success: true, found: true, demand: demand.to_h }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def active_demands(engine: nil, **)
|
|
59
|
+
eng = engine || default_engine
|
|
60
|
+
{ success: true, demands: eng.active_demands.map(&:to_h), count: eng.active_demands.size }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def red_demands(engine: nil, **)
|
|
64
|
+
eng = engine || default_engine
|
|
65
|
+
{ success: true, demands: eng.red_demands.map(&:to_h), count: eng.red_demands.size }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def demands_by_severity(severity:, engine: nil, **)
|
|
69
|
+
eng = engine || default_engine
|
|
70
|
+
demands = eng.demands_by_severity(severity: severity)
|
|
71
|
+
{ success: true, demands: demands.map(&:to_h), count: demands.size }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def demands_by_domain(domain:, engine: nil, **)
|
|
75
|
+
eng = engine || default_engine
|
|
76
|
+
demands = eng.demands_by_domain(domain: domain)
|
|
77
|
+
{ success: true, demands: demands.map(&:to_h), count: demands.size }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def capacity_status(engine: nil, **)
|
|
81
|
+
eng = engine || default_engine
|
|
82
|
+
{
|
|
83
|
+
success: true,
|
|
84
|
+
capacity: eng.capacity,
|
|
85
|
+
capacity_label: eng.capacity_label,
|
|
86
|
+
overloaded: eng.overloaded?,
|
|
87
|
+
queue_pressure: eng.queue_pressure,
|
|
88
|
+
queue_label: eng.queue_label
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def triage_report(engine: nil, **)
|
|
93
|
+
eng = engine || default_engine
|
|
94
|
+
{ success: true, report: eng.triage_report }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def default_engine
|
|
100
|
+
@default_engine ||= Helpers::TriageEngine.new
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'cognitive_triage/version'
|
|
4
|
+
require_relative 'cognitive_triage/helpers/constants'
|
|
5
|
+
require_relative 'cognitive_triage/helpers/demand'
|
|
6
|
+
require_relative 'cognitive_triage/helpers/triage_engine'
|
|
7
|
+
require_relative 'cognitive_triage/runners/cognitive_triage'
|
|
8
|
+
require_relative 'cognitive_triage/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module CognitiveTriage
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-triage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Triage system for cognitive overload. Classifies incoming demands by
|
|
27
|
+
severity and urgency, routes to appropriate processing queues, and manages cognitive
|
|
28
|
+
capacity under pressure.
|
|
29
|
+
email:
|
|
30
|
+
- matthewdiverson@gmail.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- ".github/workflows/ci.yml"
|
|
36
|
+
- ".gitignore"
|
|
37
|
+
- ".rspec"
|
|
38
|
+
- ".rubocop.yml"
|
|
39
|
+
- CLAUDE.md
|
|
40
|
+
- Gemfile
|
|
41
|
+
- README.md
|
|
42
|
+
- lex-cognitive-triage.gemspec
|
|
43
|
+
- lib/legion/extensions/cognitive_triage.rb
|
|
44
|
+
- lib/legion/extensions/cognitive_triage/client.rb
|
|
45
|
+
- lib/legion/extensions/cognitive_triage/helpers/constants.rb
|
|
46
|
+
- lib/legion/extensions/cognitive_triage/helpers/demand.rb
|
|
47
|
+
- lib/legion/extensions/cognitive_triage/helpers/triage_engine.rb
|
|
48
|
+
- lib/legion/extensions/cognitive_triage/runners/cognitive_triage.rb
|
|
49
|
+
- lib/legion/extensions/cognitive_triage/version.rb
|
|
50
|
+
homepage: https://github.com/LegionIO/lex-cognitive-triage
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata:
|
|
54
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-triage
|
|
55
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-triage
|
|
56
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-triage/blob/master/README.md
|
|
57
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-triage/blob/master/CHANGELOG.md
|
|
58
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-triage/issues
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.4'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.6.9
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: Emergency cognitive prioritization for LegionIO
|
|
77
|
+
test_files: []
|