The Pipeline¶
BeeKeeper.execute() runs three stages in order. Each one takes a BeeKeeperFlowState and returns one — the same object, mutated in place where useful.
┌──────────────────────────────────────┐
InputAdapter │ stage 1 — AssignPossibleEntitiesTo… │ stage 2 — RunPreliminaryRules
─────────────► │ build the candidate map │ prune by hard rules,
entities, │ for each allocation, list the │ aggregate soft scores
allocations │ entities not blocked by an │ (geometric mean across rules)
│ unavailability that fully covers it │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ stage 3 — RunAlgorithmAndDispatch… │
│ algorithm.run(...) → AssignmentState │
│ then output_adapter.handle_output() │
└──────────────────────────────────────┘
What each stage does¶
Stage 1 — AssignPossibleEntitiesToAllocations¶
For each allocation, walks the entity list and includes the entity as a candidate unless something definitively rules it out:
- The allocation specifies
requested_entitiesand this entity isn't in the set. - The entity has an unavailability whose date range fully covers the allocation's date range.
Partial overlaps pass through at this stage. Domain-specific availability semantics (e.g. "any conflict at all disqualifies") belong in a preliminary rule like AvailabilityRule. This keeps stage 1 cheap and conservative.
Output: state.candidate_map: dict[int, list[Candidate[TEntity]]], keyed by id(allocation). Each Candidate starts with a neutral score of 1.0.
Stage 2 — RunPreliminaryRules¶
For every (allocation, candidate) pair from stage 1, evaluates every preliminary rule:
- Compatibility is logical AND. A single
compatible=Falsefrom any rule prunes the candidate from the allocation's list — no point scoring an entity that's hard-disqualified. - Score is the geometric mean of the per-rule verdict scores. See Soft-rules aggregation for why geometric mean and not arithmetic mean or pure product.
Output: an updated state.candidate_map — fewer candidates per allocation, each with an aggregated score reflecting how well the surviving candidates fit.
Stage 3 — RunAlgorithmAndDispatchResults¶
Calls algorithm.run(allocations, entities, candidates, rules) with the now-pruned candidate map. The algorithm consults stateful rules as it assigns; its return value is an AssignmentState[TEntity, TAllocationRequest] carrying the final list of Assignments. That state is then passed to every configured output adapter via handle_output(state).
Customizing the pipeline¶
The default 3-stage pipeline is built when you instantiate BeeKeeper(...) without an explicit stages argument. Pass stages= to replace it entirely. See Customize the pipeline.