darkplex-core/cortex/triage.py
Claudia 43d033e242 feat: initial cortex package — 8 intelligence modules, CLI, Docker
Modules: triage, health_scanner, feedback_loop, memory_hygiene,
         roadmap, validate_output, enhanced_search, auto_handoff
         + composite_scorer, intent_classifier

CLI: 'cortex <module> <command>' unified entry point
Tests: 157/169 passing (12 assertion mismatches from rename)
Docker: python:3.11-slim based
2026-02-09 11:18:20 +01:00

214 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Priority Triage System — score and rank tasks by urgency, importance, effort, readiness."""
import json
import re
import sys
from dataclasses import dataclass, asdict
# --- Keyword banks (DE + EN) ---
URGENCY_KEYWORDS = {
# word -> weight (0.0-1.0 contribution)
"jetzt": 0.9, "sofort": 0.9, "asap": 0.8, "urgent": 0.8, "dringend": 0.8,
"notfall": 1.0, "emergency": 1.0, "down": 0.9, "broken": 0.7, "kaputt": 0.7,
"fehler": 0.6, "error": 0.6, "critical": 0.9, "alarm": 0.8, "outage": 0.9,
"crash": 0.8, "timeout": 0.5, "offline": 0.7, "immediately": 0.8,
"deadline": 0.7, "frist": 0.7, "heute": 0.5, "today": 0.5,
"blocker": 0.8, "p0": 1.0, "p1": 0.8, "sev1": 0.9, "sev2": 0.7,
"morgen": 0.6, "tomorrow": 0.6, "nächste woche": 0.5, "next week": 0.5,
"fällig": 0.7, "overdue": 0.8, "überfällig": 0.8,
"beschwer": 0.6, "complaint": 0.6, "problem": 0.4,
}
IMPORTANCE_KEYWORDS = {
"production": 0.9, "prod": 0.9, "security": 0.9, "sicherheit": 0.9,
"geld": 0.8, "money": 0.8, "finance": 0.8, "finanzen": 0.8,
"kunde": 0.7, "customer": 0.7, "kunden": 0.7, "bafin": 1.0,
"regulatory": 0.9, "compliance": 0.9, "gateway": 0.7, "dns": 0.7,
"albert": 0.6, "chef": 0.6, "boss": 0.6,
"database": 0.6, "datenbank": 0.6, "backup": 0.7,
"ssl": 0.7, "certificate": 0.7, "zertifikat": 0.7,
"auth": 0.7, "login": 0.6, "password": 0.7, "passwort": 0.7,
"data loss": 1.0, "datenverlust": 1.0,
# Security keywords
"breach": 0.95, "compromised": 0.9, "hacked": 0.95, "vulnerability": 0.8,
"exploit": 0.9, "unauthorized": 0.85, "ssh keys": 0.85,
# Compliance keywords
"audit": 0.8, "iso": 0.7, "compliance": 0.9, "regulatory": 0.9,
"certification": 0.7, "zertifizierung": 0.7,
# Business keywords
"revenue": 0.8, "umsatz": 0.8, "zahlung": 0.7, "payment": 0.7,
"beschwer": 0.7,
}
LOW_EFFORT_KEYWORDS = {
"quick": 0.8, "schnell": 0.8, "einfach": 0.8, "simple": 0.8,
"typo": 0.9, "tippfehler": 0.9, "config": 0.6, "toggle": 0.9,
"restart": 0.7, "neustart": 0.7, "one-liner": 0.9, "fix": 0.5,
}
HIGH_EFFORT_KEYWORDS = {
"komplett": 0.8, "refactor": 0.9, "migration": 0.9, "redesign": 0.9,
"rewrite": 0.9, "umschreiben": 0.9, "architecture": 0.8, "architektur": 0.8,
"von grund auf": 0.9, "from scratch": 0.9, "multi-step": 0.6,
"complex": 0.7, "komplex": 0.7, "projekt": 0.5, "project": 0.5,
}
NOT_READY_KEYWORDS = {
"blocked": 0.9, "blockiert": 0.9, "warte auf": 0.8, "waiting for": 0.8,
"depends on": 0.8, "abhängig von": 0.8, "brauche erst": 0.8, "need first": 0.8,
"pending": 0.6, "ausstehend": 0.6, "not yet": 0.5, "noch nicht": 0.5,
}
def _scan_keywords(text: str, keywords: dict[str, float]) -> float:
"""Scan text for keywords, return max-based score with diminishing boost."""
t = text.lower()
hits = []
for kw, weight in keywords.items():
if kw in t:
hits.append(weight)
if not hits:
return 0.0
hits.sort(reverse=True)
score = hits[0]
for h in hits[1:]:
score = min(1.0, score + h * 0.15) # diminishing returns
return round(min(1.0, score), 3)
@dataclass
class TriageScore:
text: str
urgency: float
importance: float
effort: float # 0=trivial, 1=massive
readiness: float # 1=ready, 0=blocked
priority: float
def to_dict(self):
d = asdict(self)
d["priority"] = round(d["priority"], 3)
return d
def score_task(text: str) -> TriageScore:
"""Score a single task/message."""
urgency = _scan_keywords(text, URGENCY_KEYWORDS)
importance = _scan_keywords(text, IMPORTANCE_KEYWORDS)
low_effort = _scan_keywords(text, LOW_EFFORT_KEYWORDS)
high_effort = _scan_keywords(text, HIGH_EFFORT_KEYWORDS)
# effort: 0=trivial, 1=massive. Default 0.5 (medium)
if low_effort > high_effort:
effort = max(0.1, 0.5 - low_effort * 0.4)
elif high_effort > low_effort:
effort = min(1.0, 0.5 + high_effort * 0.4)
else:
effort = 0.5
not_ready = _scan_keywords(text, NOT_READY_KEYWORDS)
readiness = round(1.0 - not_ready, 3)
# priority_score = (urgency * 0.4) + (importance * 0.35) + (1/effort_norm * 0.15) + (ready * 0.1)
# 1/effort: invert so low effort = high score. Scale effort 0.1-1.0 -> 1.0-10.0 inverted to 0.1-1.0
effort_inv = round(1.0 - effort, 3) # simple inversion: low effort -> high score
priority = (urgency * 0.4) + (importance * 0.35) + (effort_inv * 0.15) + (readiness * 0.1)
return TriageScore(
text=text,
urgency=round(urgency, 3),
importance=round(importance, 3),
effort=round(effort, 3),
readiness=readiness,
priority=round(priority, 3),
)
def rank_tasks(texts: list[str]) -> list[TriageScore]:
"""Score and rank multiple tasks by priority (highest first)."""
scores = [score_task(t) for t in texts]
scores.sort(key=lambda s: s.priority, reverse=True)
return scores
def analyze_message(text: str) -> dict:
"""Deep analysis of a message — return signals found."""
score = score_task(text)
t = text.lower()
signals = {"urgency": [], "importance": [], "effort": [], "readiness": []}
for kw in URGENCY_KEYWORDS:
if kw in t:
signals["urgency"].append(kw)
for kw in IMPORTANCE_KEYWORDS:
if kw in t:
signals["importance"].append(kw)
for kw in LOW_EFFORT_KEYWORDS:
if kw in t:
signals["effort"].append(f"{kw} (low)")
for kw in HIGH_EFFORT_KEYWORDS:
if kw in t:
signals["effort"].append(f"{kw} (high)")
for kw in NOT_READY_KEYWORDS:
if kw in t:
signals["readiness"].append(f"{kw} (blocker)")
# Classify
p = score.priority
if p >= 0.8:
classification = "🔴 CRITICAL — handle immediately"
elif p >= 0.6:
classification = "🟠 HIGH — handle soon"
elif p >= 0.4:
classification = "🟡 MEDIUM — schedule"
elif p >= 0.2:
classification = "🔵 LOW — backlog"
else:
classification = "⚪ MINIMAL — ignore"
return {
"score": score.to_dict(),
"signals": signals,
"classification": classification,
}
def main():
if len(sys.argv) < 2:
print("Usage: python3 triage.py <score|rank|analyze> <text...>")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "score":
if len(sys.argv) < 3:
print("Usage: python3 triage.py score \"task description\"")
sys.exit(1)
result = score_task(sys.argv[2])
print(json.dumps(result.to_dict(), indent=2, ensure_ascii=False))
elif cmd == "rank":
if len(sys.argv) < 3:
print("Usage: python3 triage.py rank \"task1\" \"task2\" ...")
sys.exit(1)
tasks = sys.argv[2:]
ranked = rank_tasks(tasks)
output = [{"rank": i + 1, **s.to_dict()} for i, s in enumerate(ranked)]
print(json.dumps(output, indent=2, ensure_ascii=False))
elif cmd == "analyze":
if len(sys.argv) < 3:
print("Usage: python3 triage.py analyze \"message\"")
sys.exit(1)
result = analyze_message(sys.argv[2])
print(json.dumps(result, indent=2, ensure_ascii=False))
else:
print(f"Unknown command: {cmd}")
sys.exit(1)
if __name__ == "__main__":
main()