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.
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| swarph_triage-0.1.0.tar.gz | 25.6 kB | Without provenance | Details for swarph_triage-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| swarph_triage-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for swarph_triage-0.1.0-py3-none-any.whl |
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
|
Security for swarph-triage 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.