swarph-triage
Generalizable ranked-queue triage primitive — fingerprint, priority, state machine, regression detector. Backend-agnostic via SQLAlchemy Core (sqlite + postgres).
Status: ✅ 0.1.0 — functional. Public API, priority formula, state machine, regression detector, CLI, and FastAPI router are implemented and tested (sqlite + postgres via SQLAlchemy Core).
Extracted from a production error-triage system (scan logs → fingerprint → prioritize → work the backlog down) and generalized: the same ranked-dedup-queue pattern fits any high-volume stream of observations that should collapse into prioritized, dispositionable rows.
The pattern
A ranked queue where:
- Many concrete observations collapse to one logical row via a
fingerprint_fn. (Canonical case — error triage: 47 near-identical error log lines → 1 fingerprint withcount=47. The pattern generalizes to any stream where many raw events map to one actionable unit.) - Items rank by
severity × log(1+freq) × decay(age) × actionability— log on freq stops whales drowning fresh items, exp-decay on age makes hot items rise. All coefficients live in a calibration table (config-driven, not hardcoded). - A small explicit state machine (
new → triaged → approved → patched, with branches towontfixandneeds_review) — every transition logged tostate_log, no implicit "kinda done." - A regression detector — if a fingerprint with
status='patched'gets a new occurrence withinregression_grace_hours, resurrect tonewwithregression=1. Accepted dispositions don't silently mask returning problems. - Cooldown semantics — a
cooldown_untiltimestamp onlet_cooldispositions. The priority calc ramps back from zero as cooldown expires, so a deliberately-deferred item doesn't immediately re-surface.
Public API surface (planned)
from swarph_triage import open as open_triage
q = open_triage(
"postgresql://user:pass@host:5433/mydb", # or sqlite:///path.db
config={"decay_half_life_hours": 72.0},
proposer_fn=my_proposer, # optional, domain-specific
)
# ingest one observation
fp_id = q.ingest(
fingerprint="NullPointerError|auth.py|login",
severity="high",
actionability=0.7,
context={"module": "auth", "first_seen": "..."},
)
# disposition
q.transition(fp_id, to_status="approved", actor="oncall", note="fix queued")
# top-N for the UI
for row in q.list(limit=20):
print(row["fingerprint"], row["priority_score"], row["status"])
CLI:
swarph-triage list
swarph-triage show <id>
swarph-triage approve <id>
swarph-triage wontfix <id> "reason"
swarph-triage stats
swarph-triage backlog # writes markdown snapshot
swarph-triage history <id>
FastAPI routes (optional install: pip install swarph-triage[fastapi]):
from fastapi import FastAPI
from swarph_triage.fastapi import build_router
app = FastAPI()
app.include_router(build_router(q), prefix="/triage")
# → /list, /stats, /show/{id}, /{id}/{approve|wontfix|escalate|reopen}, /events (SSE)
Configuration
Everything tunable lives in swarph_triage.config.DEFAULT_CONFIG — override per-consumer at open():
DEFAULT_CONFIG = {
"decay_half_life_hours": 6.0, # 6h for hourly, 72h for daily
"severity_weights": {"critical": 1.0, "high": 0.7, "medium": 0.5, "low": 0.3},
"freq_curve": "log", # "log" | "linear" | "sqrt"
"freq_log_base": 10,
"actionability_floor": 0.1,
"regression_grace_hours": 24,
"cooldown_default_days": 14,
"priority_min": 0.0,
"priority_max": 100.0,
}
Layout
swarph_triage/
__init__.py — public API surface
config.py — DEFAULT_CONFIG + load/merge helpers
schema.py — SQLAlchemy Core table definitions (3 tables, 6 indexes)
state_machine.py — Status enum + valid transition matrix + side effects
priority.py — score formula + decay + recompute_all
regression.py — patched-then-reappearance detector
queue.py — TriageQueue main class (ingest, transition, list, show)
cli.py — argparse-driven CLI
fastapi.py — APIRouter factory (optional extra)
License
MIT. Pierre Samson + Claude, co-authored — matching the phawkes / fisherrao / tailcor lineage.
Release files for swarph-triage 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| swarph_triage-0.1.0.tar.gz | 25.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| swarph_triage-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.9 kB
Release files / swarph_triage-0.1.0.tar.gz
| Download URL | swarph_triage-0.1.0.tar.gz |
|---|---|
| Size | 25.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a0c8a500045c12fa8e01cf27179dd681281a987b57f9ab2fb448dcdfb8a1cb5
|
|
BLAKE2b-256 checksum How to use checksums |
0aa27d449e36c591e843d0287cdaaaa4a265f10db6d1b691d7656ce90f7f570c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|
Release files / swarph_triage-0.1.0-py3-none-any.whl
| Download URL | swarph_triage-0.1.0-py3-none-any.whl |
|---|---|
| Size | 19.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dba86d50b04570f6564d8a0577abc92b1a1ae40bd70d75965319e0a38c0a90c0
|
|
BLAKE2b-256 checksum How to use checksums |
b91c6716d9e2f1d27f90b304c8af9c6e6f0d4bfa66464d326dd4140b31fdb99a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|