codex_services.booking.slot_master.scorer
scorer
codex_services.booking.slot_master.scorer
Evaluation and ranking of booking engine solutions.
Pure Python — no Django dependencies. Applied AFTER ChainFinder.find() to sort solutions by quality.
Does not affect the search algorithm — only the output order. BookingChainSolution.score is set here.
Quick start
from codex_services.booking.slot_master import ChainFinder from codex_services.booking.slot_master.scorer import BookingScorer, ScoringWeights
result = ChainFinder().find(request, availability)
scorer = BookingScorer( weights=ScoringWeights(preferred_resource_bonus=15.0), preferred_resource_ids=["3", "7"], ) ranked = scorer.score(result) best = ranked.best # solution with the highest score
Classes
ScoringWeights
dataclass
Weights for solution evaluation criteria. Configurable per project.
All weights are additive: total score = sum of applicable bonuses. Higher score = more preferred solution.
Fields
preferred_resource_bonus (float): Bonus for each service with a preferred resource. Passed via BookingScorer(preferred_resource_ids=[...]). Example: client always visits Anya -> preferred_resource_ids=["3"].
same_resource_bonus (float): Bonus if one resource performs multiple services. Reduces the number of times the client must switch resources.
min_idle_bonus_per_hour (float): Bonus for each hour of minimized idle time between services. Encourages compact chains.
early_slot_penalty_per_hour (float): Penalty for each hour from the start of the workday to the first service. Encourages earlier slots (less index = better). Negative value is not needed here — subtracted automatically.
Example (encourage early times AND preferred resource): ScoringWeights( preferred_resource_bonus=20.0, early_slot_penalty_per_hour=1.0, )
Source code in src/codex_services/booking/slot_master/scorer.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
BookingScorer
Evaluates engine solutions and returns an EngineResult with populated scores.
Usage
scorer = BookingScorer( weights=ScoringWeights(preferred_resource_bonus=15.0), preferred_resource_ids=["3", "7"], ) ranked = scorer.score(result)
Best solution by score (not just the earliest):
print(ranked.best.score) print(ranked.best.to_display())
All solutions sorted by score (highest -> first):
for solution in ranked.solutions: print(solution.score, solution.starts_at)
Source code in src/codex_services/booking/slot_master/scorer.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
Functions
__init__(weights=None, preferred_resource_ids=None)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ScoringWeights | None
|
Criteria weights. None = ScoringWeights() with defaults. |
None
|
preferred_resource_ids
|
list[str] | None
|
List of IDs for preferred resources. Must be strings (str(resource.pk)). On match -> preferred_resource_bonus applied. |
None
|
Source code in src/codex_services/booking/slot_master/scorer.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
score(result)
Populates a score for each solution and returns a re-sorted EngineResult.
Sorting: descending by score (best = first = result.best). Solutions with the same score are sorted by starts_at (earlier = better).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
EngineResult
|
EngineResult from ChainFinder.find(). |
required |
Returns:
| Type | Description |
|---|---|
EngineResult
|
New EngineResult (using frozen=True -> model_copy) with populated scores |
EngineResult
|
and solutions sorted by score DESC. |
EngineResult
|
If result is empty, it is returned unchanged. |
Source code in src/codex_services/booking/slot_master/scorer.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |