DecisionKit
DecisionKit is a lightweight explainable scoring and ranking engine for Python backends.
Describe criteria, weights, constraints, penalties, and bonuses — in code or config — then get ranked alternatives with scores, human-readable explanations, and audit-ready output.
No machine learning. No database. No web UI. Core has zero runtime dependencies.
Project status
Beta (v0.3.1) — useful for real backend workflows; API aims to stay stable across minor releases. See CHANGELOG.md.
Installation
pip install decisionkit
Optional YAML support:
pip install "decisionkit[yaml]"
From source:
pip install -e ".[dev]"
Quick start
from decisionkit import DecisionModel, Criterion
model = DecisionModel(
criteria=[
Criterion("relevance", weight=0.5, direction="max"),
Criterion("rating", weight=0.3, direction="max"),
Criterion("workload", weight=0.2, direction="min"),
],
method="weighted_sum",
explain=True,
)
result = model.rank([
{"id": "reviewer_1", "relevance": 0.92, "rating": 4.8, "workload": 7},
{"id": "reviewer_2", "relevance": 0.81, "rating": 4.5, "workload": 2},
])
print(result.best.id)
print(result.explain())
Config-driven usage
from decisionkit import DecisionModel
config = {
"method": "weighted_sum",
"explain": True,
"criteria": [
{"name": "relevance", "weight": 0.5, "direction": "max"},
{"name": "rating", "weight": 0.3, "direction": "max"},
{"name": "workload", "weight": 0.2, "direction": "min"},
],
"constraints": [
{
"name": "available_only",
"field": "available",
"operator": "eq",
"value": True,
"reason": "Reviewer must be available",
}
],
"penalties": [
{
"name": "high_workload_penalty",
"field": "workload",
"operator": "gt",
"value": 5,
"amount": 0.1,
}
],
"bonuses": [
{
"name": "exact_topic_bonus",
"field": "topic_match",
"operator": "gte",
"value": 0.9,
"amount": 0.05,
}
],
}
model = DecisionModel.from_dict(config)
result = model.rank(
[
{
"id": "reviewer_1",
"relevance": 0.92,
"rating": 4.8,
"workload": 7,
"available": True,
"topic_match": 0.95,
},
{
"id": "reviewer_2",
"relevance": 0.81,
"rating": 4.5,
"workload": 2,
"available": True,
"topic_match": 0.8,
},
],
context={"max_workload": 5},
)
print(result.best.id)
print(result.to_audit_dict(include_hash=True)["audit_hash"])
JSON and optional YAML helpers:
model = DecisionModel.from_json(model.to_json())
# pip install "decisionkit[yaml]"
# model = DecisionModel.from_yaml(model.to_yaml())
Rules: constraints, penalties, bonuses
Operators: eq, ne, gt, gte, lt, lte, in, not_in, contains, between.
Context-aware rule:
{
"name": "within_capacity",
"field": "workload",
"operator": "lte",
"context": "max_workload",
}
Compound rule (all = AND, any = OR):
{
"name": "eligible",
"all": [
{"field": "available", "operator": "eq", "value": True},
{"field": "workload", "operator": "lte", "context": "max_workload"},
],
}
Audit export and hash
audit = result.to_audit_dict(
decision_id="req-123",
metadata={"tenant": "acme"},
timestamp="2026-07-10T10:00:00Z",
include_hash=True,
)
digest = result.audit_hash() # sha256 of canonical decision payload
The digest covers method, model, context, inputs, exclusions, ranking, and best alternative. It excludes decision_id, metadata, and timestamp by default. Details: docs/audit.md.
FastAPI integration
from fastapi import FastAPI, HTTPException
from decisionkit import DecisionModel, ValidationError
app = FastAPI()
@app.post("/rank")
def rank(payload: dict):
try:
model = DecisionModel.from_dict(payload["model"])
result = model.rank(
payload["alternatives"],
context=payload.get("context", {}),
)
return result.to_audit_dict(
decision_id=payload.get("decision_id"),
include_hash=True,
)
except ValidationError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
See examples/fastapi_example.py.
Django service-layer integration
Keep DecisionKit in a service module; store config in settings/admin/DB:
from decisionkit import DecisionModel
def rank_reviewers(candidates: list[dict], model_config: dict) -> dict:
model = DecisionModel.from_dict(model_config)
result = model.rank(candidates, context={"max_workload": 5})
return result.to_audit_dict(decision_id="editorial-assign", include_hash=True)
See examples/django_service_example.py.
How DecisionKit compares
| Approach | Fit |
|---|---|
Ad-hoc if / spreadsheet scoring |
Fast to start, hard to explain, test, or reuse |
| Generic rule engines | Great for branching logic; weaker at weighted ranking + score breakdowns |
| Academic MCDA toolkits | Broad method coverage; often awkward for backend APIs and audits |
| DecisionKit | Backend-friendly weighted ranking with rules, explanations, and audit hashes |
DecisionKit is not trying to replace every MCDA method. It focuses on explainable scoring you can ship in Django/FastAPI services.
Supported methods
| Method | Key | Notes |
|---|---|---|
| Weighted sum | weighted_sum |
Min-max normalization relative to the evaluated alternatives; min criteria inverted |
| TOPSIS | topsis |
Vector normalization + closeness to ideal |
Scores and “strong/weak” explanation labels are relative to the current alternative set, not absolute universal grades.
Examples
python examples/reviewer_selection.py
python examples/vendor_ranking.py
python examples/task_prioritization.py
python examples/risk_scoring.py
python examples/django_service_example.py
More: docs/examples.md
Documentation
Development
pip install -e ".[dev]"
python -m ruff check src tests examples
python -m mypy src
python -m pytest --cov=decisionkit --cov-report=term-missing
CI runs on Python 3.11 and 3.12 (see .github/workflows/ci.yml).
Release steps: RELEASE.md
Roadmap
v0.4.0
- AHP method (optional, still backend-friendly)
- Nested boolean rule trees (beyond flat
all/any) - Signed / hashed decision record helpers for long-term storage
Later
- ELECTRE / PROMETHEE (only if API stays simple)
decisionkit-djangointegration package- Optional CLI for batch ranking
License
MIT — see LICENSE.
Metadata
Release files for decisionkit 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| decisionkit-0.3.1.tar.gz | 31.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| decisionkit-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.9 kB
Release files / decisionkit-0.3.1.tar.gz
| Download URL | decisionkit-0.3.1.tar.gz |
|---|---|
| Size | 31.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1080c1c30e53910a411b90b608b988fe2d886065dc8df1586ae39d04fa7d5764
|
|
BLAKE2b-256 checksum How to use checksums |
617c2a55c1c83c1e0e229087713f0e7fadac66dea1c3d622c7bd5074ee87d21c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / decisionkit-0.3.1-py3-none-any.whl
| Download URL | decisionkit-0.3.1-py3-none-any.whl |
|---|---|
| Size | 26.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3530718999d9f89e43ee85ea4d0572c2498b6103b17be64fe69538ea759f19ad
|
|
BLAKE2b-256 checksum How to use checksums |
62ffb2bd8970d1e3e6f7c3ded4df0bd55cd372521479d844e77d4b265b254b77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|