pytyche.experiment.recommendation¶
Recommendation engine: the per-round NextRoundPlan construction.
The engine is a thin composition over the shipped L2 primitives: the
policy tree (already carrying the ε-clipped Thompson allocation_map)
comes from posterior.fit_policy_tree, and graduation evidence from
posterior.recommendation_summary — nothing here reimplements the
allocation or decision rules (design.md §”Recommendation engine”).
Prose templates: every operator-facing phrase the engine emits lives in the “Prose templates” section below — deterministic, no LLM, no timestamps.
Functions
|
The round-0 default plan: Control + Explore at 50/50. |
|
Treatments whose allocation starved below epsilon everywhere. |
|
The next round's |
|
Build the next round's |
Classes
|
Default graduation rule: sustained compound-threshold evidence. |
|
A (treatment, segment) pair that has met the graduation rule. |
|
Decision rule for whether a (treatment, segment) pair is a graduation candidate. |
|
The recommendation engine's proposal for the next round. |
- class pytyche.experiment.recommendation.GraduationRule(*args, **kwargs)[source]¶
Bases:
ProtocolDecision rule for whether a (treatment, segment) pair is a graduation candidate.
historyis oldest-first and INCLUDES the round under evaluation as its LAST element — the engine appends a provisionalExperimentfor the current round before consulting the rule. Default implementation:ExpectedLossRule.
- class pytyche.experiment.recommendation.ExpectedLossRule(expected_loss_max=0.005, p_positive_threshold=0.95, p_better_threshold=0.8, sustained_rounds=2)[source]¶
Bases:
objectDefault graduation rule: sustained compound-threshold evidence.
A (treatment, segment) pair is a candidate when its per-round decision evidence cleared all three thresholds —
expected_loss_comparison < expected_loss_maxANDprobability_positive > p_positive_thresholdANDprobability_better > p_better_threshold— in the LASTsustained_roundsconsecutive rounds of the history (this round and thesustained_rounds - 1before it). The evidence is recomputed per round from each history entry’s stored posterior viaexperiment.posterior.recommendation_summary(treatment, segment=segment)— array math on stored draws, no extra state.Graduation runs on RAW posterior probabilities in v0.2: the calibration artifact’s correction scope is intervals-only, so the probability and expected-loss inputs here come from raw posterior draws even when a calibration artifact is attached. When the calibration track’s p-curve extension lands, graduation becomes calibrated with no API change here.
The defaults are the canonical thresholds; calibrate to your domain.
- Parameters:
expected_loss_max (
float)p_positive_threshold (
float)p_better_threshold (
float)sustained_rounds (
int)
- is_candidate(treatment, segment, history)[source]¶
Whether the thresholds held in the last
sustained_rounds.- Parameters:
treatment (
str)segment (
DiscoveredSegment)history (
list[Experiment])
- Return type:
bool
- consecutive_held(treatment, segment, history)[source]¶
Count of trailing rounds whose evidence cleared the thresholds.
The engine records this on
GraduationCandidate— a pair holding five consecutive rounds reports 5, not the rule’s configured minimum.- Parameters:
treatment (
str)segment (
DiscoveredSegment)history (
list[Experiment])
- Return type:
int
- class pytyche.experiment.recommendation.GraduationCandidate(treatment, segment, sustained_rounds, latest_recommendation)[source]¶
Bases:
objectA (treatment, segment) pair that has met the graduation rule.
Surfaced as structured data only — the library never auto-graduates; the operator (or an automated workflow) decides whether to ship.
- treatment¶
The treatment name.
- segment¶
The discovered segment where graduation fired.
- sustained_rounds¶
Count of consecutive rounds the rule has held.
- latest_recommendation¶
The current round’s per-(treatment, segment) decision evidence.
- Parameters:
treatment (
str)segment (
DiscoveredSegment)sustained_rounds (
int)latest_recommendation (
RecommendationSummary)
- class pytyche.experiment.recommendation.NextRoundPlan(n_visitors, cells, treatments, dropped_treatments, graduation_candidates, prose_summary, tree=None)[source]¶
Bases:
objectThe recommendation engine’s proposal for the next round.
The engine emits ONE proposed cell structure per round; the operator may accept, partially override, or fully replace before shipping.
- n_visitors¶
schedule.next_round_size(rounds_completed);Nonewhen the schedule is exhausted (the final round’s plan has no next round to size).
- cells¶
The recommended cell structure (weights sum to 1.0).
- treatments¶
Treatments active going into the next round.
- dropped_treatments¶
Treatments dropped between this round and the next; disjoint from
treatments.
- graduation_candidates¶
(treatment, segment) pairs that met the graduation rule. Candidates’ treatments stay active — no auto-graduation.
- prose_summary¶
Multi-paragraph PM-readable rationale (template-formatted, deterministic).
- tree¶
The round’s policy-tree fit, from which the Optimized cell was built — segments, stability scores, allocation map.
Noneonly on the round-0 cold-start plan, where no fit exists yet. Consumers needing the round’s tree (the truth comparison, next-fitnum_trees_tausizing) read it here rather than refitting.
- Parameters:
n_visitors (
int|None)cells (
list[Cell])treatments (
list[str])dropped_treatments (
list[str])graduation_candidates (
list[GraduationCandidate])prose_summary (
str)tree (
PolicyTreeResult|None)
- pytyche.experiment.recommendation.cold_start_plan(treatments, n_visitors)[source]¶
The round-0 default plan: Control + Explore at 50/50.
No posterior exists yet to derive a tree from, so there is no Optimized cell — the Control cell measures the baseline cleanly and the Explore cell gives the first fit uniform-random signal.
- Parameters:
treatments (
list[str]) – The experiment’s full treatments universe (control first).n_visitors (
int|None) – The first round’s size per the schedule;Nonewhen the schedule offers no rounds.
- Return type:
- Returns:
The default
NextRoundPlanfor round 0.
- pytyche.experiment.recommendation.next_fit_num_trees_tau(posterior, tree_result, cumulative_n)[source]¶
The next round’s
num_trees_taufrom this round’s evidence.compute_num_trees_tauat its defaults, withd_tauthe unique split-feature count of the fitted policy tree (min 1 — a root-only tree still has one effective dimension) andsigma_tauthe standard deviation of the posterior-mean CATEs.- Parameters:
posterior (
HurdleBCFResult) – This round’s hurdle posterior.tree_result (
PolicyTreeResult) – This round’s policy-tree fit.cumulative_n (
int) – Total visitors across all rounds including the next one being sized.
- Return type:
int- Returns:
Tree count for the next fit’s
GPUBCFConfig.num_trees_tau.
- pytyche.experiment.recommendation.detect_dropped_treatments(allocation_history, treatments, *, epsilon=0.02, sustained_rounds=2)[source]¶
Treatments whose allocation starved below epsilon everywhere.
A treatment is dropped when its allocation is below epsilon in EVERY segment for the last sustained_rounds consecutive rounds. A name absent from a segment’s weights counts as zero allocation.
- Parameters:
allocation_history (
Sequence[dict[int,dict[str,float]]]) – Per-round allocation maps ({leaf_id: {treatment_name: weight}}), oldest first.treatments (
list[str]) – Names to test; the result preserves this order.epsilon (
float) – The starvation threshold (the Thompson clip floor).sustained_rounds (
int) – Consecutive starved rounds required.
- Return type:
list[str]- Returns:
Dropped names in treatments order; empty when the history is shorter than sustained_rounds.
- pytyche.experiment.recommendation.recommend(analysis, *, history, treatments, schedule, min_control_weight=0.05, min_explore_weight=0.05, max_segment_depth=3, min_segment_share=0.1, graduation_rule=None, seed=0)[source]¶
Build the next round’s
NextRoundPlanfrom this round’s analysis.Composes the shipped L2 primitives: the policy tree and its ε-clipped Thompson allocation come from
analysis.posterior.fit_policy_tree(...), graduation evidence fromposterior.recommendation_summary(...). The proposed cells are the Control + Explore + Optimized triple at the floor weights, the graduation rule is consulted per (non-control treatment × segment) pair over[*history, <this round>], and treatments starved below the allocation floor for consecutive rounds are dropped from the active list.- Parameters:
analysis (
AnalysisResult) – This round’s analysis; the posterior rides onanalysis.posterior.history (
list[Experiment]) – Prior rounds’ experiments, oldest first (this round is NOT included; the engine appends its own provisional view).treatments (
list[str]) – The experiment’s full treatments universe (control first).schedule (
Schedule) – Sizes the next round vianext_round_size(len(history) + 1).min_control_weight (
float) – Guaranteed Control-cell share.min_explore_weight (
float) – Guaranteed Explore-cell share.max_segment_depth (
int) – Policy-tree depth bound.min_segment_share (
float) – Minimum per-leaf population share.graduation_rule (
GraduationRule|None) –NoneusesExpectedLossRuleat its defaults.seed (
int) – Bootstrap seed for the policy tree’s stability scores.
- Return type:
- Returns:
The assembled
NextRoundPlan.- Raises:
ValueError – When
min_control_weight + min_explore_weight >= 1.0, or when the posterior carries no observed data.