Skip to main content

Lightweight explainable scoring and ranking engine for Python backends.

Project description

DecisionKit

CI PyPI version Python versions License: MIT

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.0) — 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; min criteria inverted
TOPSIS topsis Vector normalization + closeness to ideal

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-django integration package
  • Optional CLI for batch ranking

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

decisionkit-0.3.0.post1.tar.gz (30.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

decisionkit-0.3.0.post1-py3-none-any.whl (26.0 kB view details)

Uploaded Python 3

File details

Details for the file decisionkit-0.3.0.post1.tar.gz.

File metadata

  • Download URL: decisionkit-0.3.0.post1.tar.gz
  • Upload date:
  • Size: 30.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for decisionkit-0.3.0.post1.tar.gz
Algorithm Hash digest
SHA256 7d7e8a6b8a985705d4fdd50ca9299d908876725a1a05e6dd302a0623a0bbc941
MD5 dc756152d27d9c776037f9d1108f7020
BLAKE2b-256 754d79e7c7cefa5eae22f7952ebd266513d53a092c2c2680be22d0e29c666405

See more details on using hashes here.

File details

Details for the file decisionkit-0.3.0.post1-py3-none-any.whl.

File metadata

File hashes

Hashes for decisionkit-0.3.0.post1-py3-none-any.whl
Algorithm Hash digest
SHA256 0111e8d9afb8e7fa920ae807126aa36f43a79a02bc513334ebd56387425d02db
MD5 d94a1552f6c96bf270f0a689fefb495a
BLAKE2b-256 4402a1feda110842b0dec1ee515bf0e6d5a35f768df13b7187003bc5f03e4074

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page