Expansion Domain API Reference¶
Auto-generated from Google-style docstrings in src/domain/expansion/.
Value Objects¶
src.domain.expansion.value_objects ¶
Value objects for the Expansion domain.
UpgradePropensity: P(upgrade to next tier in 90 days), analogous to ChurnProbability. TargetTier: Encapsulates tier-ladder logic and ARR uplift multipliers.
Classes¶
UpgradePropensity
dataclass
¶
Calibrated P(upgrade to next plan tier within 90 days).
Business Context: Analogous to ChurnProbability in the prediction domain. The 0.5 threshold separates accounts to actively pursue vs. nurture. Tier boundaries mirror ChurnProbability for consistent CS tooling language.
Source code in src/domain/expansion/value_objects.py
TargetTier
dataclass
¶
Encapsulates the tier ladder and ARR uplift multipliers.
Business Context: Determines what the next upgrade step is and how much net-new ARR that upgrade is worth (probability-weighted). Used by ExpansionResult.expected_arr_uplift to produce a defensible dollar figure for VP/Sales reviews — not a raw ACV, but a probability-weighted delta.
Tier ladder
STARTER → GROWTH → ENTERPRISE → CUSTOM (seat/add-on expansion) → None
Source code in src/domain/expansion/value_objects.py
Attributes¶
property
¶The next plan tier in the upgrade sequence, or None at the ceiling.
property
¶MRR multiplier representing the target tier's typical ACV vs current ACV.
Business Context: Multipliers derived from median ACV jumps observed between tiers. Enterprise → CUSTOM is 1.2× because growth at that tier comes from seat additions and add-on modules (Dash, Sign, AI), not a full tier change. FREE uses 0.0 as a sentinel — the guard clause in calculate_expected_uplift handles the FREE → STARTER conversion using the Starter floor ARR instead.
Functions¶
Probability-weighted net annual revenue opportunity from this upgrade.
Formula (paid tiers): (MRR × 12) × (multiplier - 1) × propensity Formula (FREE tier): 500 × 12 × propensity (Starter floor ARR)
Business Context: FREE tier customers have zero MRR so the standard (MRR × 12 × ...) formula would always yield $0. Instead, the Starter floor ARR ($500/mo = $6k/yr) is used as the conversion value. This surfaces free-to-paid conversion as a real revenue opportunity in Sales prioritisation. The $500/mo floor matches the Starter tier minimum (per tier ladder).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current_mrr
|
float
|
Customer's current Monthly Recurring Revenue (USD). Ignored for FREE tier — Starter floor is used instead. |
required |
propensity
|
float
|
UpgradePropensity.value — calibrated probability in [0, 1]. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Expected net ARR uplift in USD, rounded to 2 decimal places. |
float
|
Returns 0.0 if there is no higher tier, multiplier is zero, or |
float
|
propensity is zero. |
Source code in src/domain/expansion/value_objects.py
Entities¶
src.domain.expansion.entities ¶
ExpansionResult entity — output of the Expansion domain services.
Symmetric mirror of PredictionResult in the prediction domain. Immutable (frozen=True) because results are computed once at inference time.
Classes¶
ExpansionResult
dataclass
¶
The complete output of an expansion propensity prediction for one customer.
Business Context: Pairs with PredictionResult to form the full NRR lifecycle view — Retain (churn) + Expand (upgrade). The expected_arr_uplift property produces a probability-weighted dollar figure for VP/Sales prioritisation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
customer_id
|
str
|
The customer this prediction belongs to. |
required |
current_mrr
|
float
|
Customer's current MRR at prediction time (USD). |
required |
propensity
|
UpgradePropensity
|
Calibrated P(upgrade to next tier within 90 days). |
required |
target
|
TargetTier
|
TargetTier encapsulating next step and uplift multiplier. |
required |
top_features
|
list[ShapFeature]
|
Top SHAP drivers sorted by |shap_impact| descending. |
list()
|
model_version
|
str
|
Semantic version of the expansion model artifact. |
'1.0.0'
|
predicted_at
|
datetime
|
UTC timestamp of when the prediction was generated. |
(lambda: now(UTC))()
|
Source code in src/domain/expansion/entities.py
18 19 20 21 22 23 24 25 26 27 28 29 30 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 69 70 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 | |
Attributes¶
property
¶Probability-weighted net annual revenue opportunity from this upgrade.
Business Context: Delegates to TargetTier.calculate_expected_uplift() to avoid duplicating the (MRR × 12 × (multiplier - 1) × propensity) formula. This property is the primary input for Sales prioritisation: sort the expansion list by expected_arr_uplift DESC to find accounts with the highest ROI on an upgrade conversation.
Returns:
| Type | Description |
|---|---|
float
|
Expected net ARR uplift in USD. |
property
¶True if this account warrants Senior AE (or CSM) attention.
Business Context: A $50 ARR uplift at 85% propensity is not worth a Senior AE's time. This property separates 'interesting signal' from 'actionable Sales motion' — resource allocation logic baked into the domain, not the dashboard filters. Threshold: > $10k expected uplift AND propensity tier is High or Critical.
FREE-tier override: free-to-paid max uplift is $6k (below the $10k threshold), so free-tier customers at CRITICAL propensity (≥0.75) always return True — the conversion event is high-priority regardless.
Returns:
| Type | Description |
|---|---|
bool
|
True if the account should be escalated for active outreach. |
Functions¶
Deterministic GTM playbook routing based on propensity tier.
Business Context: When both churn probability and expansion propensity are available, the conflict matrix resolves the correct CS motion. High Churn + High Expansion = 'Flight Risk' — upselling a churning customer accelerates churn rather than preventing it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
churn_probability
|
float | None
|
Optional churn probability from PredictionResult. When provided, enables conflict-matrix resolution. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
Human-readable action string for CS/Sales tooling. |
Source code in src/domain/expansion/entities.py
Produces a verified, grounded-facts dict for the LLM PromptBuilder.
Business Context: Only surfaces confirmed model outputs to the LLM. This is the 'clean hands' hallucination guardrail — the model can only fabricate facts we explicitly passed to it, not invent new signals.
Returns:
| Type | Description |
|---|---|
dict[str, object]
|
Dict of scalar values safe to inject into a prompt [CONTEXT] block. |
Source code in src/domain/expansion/entities.py
Summary Result Entity¶
src.domain.expansion.summary_entities ¶
ExpansionSummaryResult entity — output of GenerateExpansionSummaryUseCase.
Immutable dataclass that carries the LLM-generated AE brief, optional email draft, guardrail validation outcome, and full provenance for audit logging.
Classes¶
ExpansionSummaryResult
dataclass
¶
Complete output of the expansion narrative generation pipeline.
Business Context: Closes the "last mile" between a high-propensity score in DuckDB and an actionable, personalised pitch in the AE's hands. The correlation_id links each brief to its downstream outcome in expansion_outreach_log, enabling the data team to measure whether high fact_confidence briefs close at higher rates (V2 fine-tuning flywheel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
customer_id
|
str
|
UUID of the account this brief is about. |
required |
propensity_summary
|
str
|
Plain-English propensity tier + score sentence. |
required |
key_narrative_drivers
|
list[str]
|
Top 3 signals in business language (from SHAP). |
required |
ae_tactical_brief
|
str
|
LLM-generated brief with guardrail watermark appended. |
required |
email_draft
|
str | None
|
Optional 3-sentence email with CTA (AE audience only). |
required |
guardrail_status
|
Literal['PASSED', 'FLAGGED', 'REJECTED']
|
'PASSED' / 'FLAGGED' (1 issue) / 'REJECTED' (2+ issues). |
required |
fact_confidence
|
float
|
1.0 − (0.25 × n_flags), floored at 0.0. |
required |
generated_at
|
datetime
|
UTC timestamp when the summary was created. |
required |
model_used
|
str
|
LLM model identifier (e.g. 'llama-3.1-8b-instant'). |
required |
llm_provider
|
str
|
Inference provider — 'groq' or 'ollama'. |
required |
propensity_score
|
float
|
Raw calibrated probability in [0, 1]. |
required |
propensity_tier
|
str
|
Human-readable tier — 'low' / 'medium' / 'high' / 'critical'. |
required |
target_tier
|
str | None
|
Next upgrade target (e.g. 'enterprise'), or None at ceiling. |
required |
expected_arr_uplift
|
float
|
Probability-weighted net ARR opportunity (USD). |
required |
correlation_id
|
str
|
UUID hex linking this brief to GTM outreach log for V2. |
required |
Source code in src/domain/expansion/summary_entities.py
Domain Service¶
src.domain.expansion.expansion_service ¶
ExpansionModelService — domain service for upgrade propensity prediction.
Symmetric mirror of ChurnModelService in the prediction domain. Ports and protocols follow the same pattern: domain has no direct knowledge of infrastructure (no file I/O, no DuckDB, no pickle files here).
Classes¶
ExpansionFeatureVector ¶
Bases: Protocol
Protocol for expansion feature extraction — implemented in infrastructure layer.
Queries the mart_customer_expansion_features dbt mart for the 20-feature vector (15 churn features + 5 expansion signals).
Source code in src/domain/expansion/expansion_service.py
Functions¶
Extract the 20-feature expansion vector for a customer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
customer
|
Customer
|
Active Customer entity with plan_tier and MRR. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float | str]
|
Flat dict of feature_name → value (numeric as float, categorical as str). |
dict[str, float | str]
|
All feature engineering lives in mart_customer_expansion_features. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the customer is not found in the mart or has already upgraded. |
Source code in src/domain/expansion/expansion_service.py
ExpansionModelPort ¶
Bases: ABC
Abstract port for the underlying expansion ML model.
Concrete implementations in src/infrastructure/ml/ load the trained XGBoost expansion model artifact.
Source code in src/domain/expansion/expansion_service.py
ExpansionModelService ¶
Orchestrates feature extraction → model inference → result construction.
Business Context: Mirrors ChurnModelService exactly. Feature extraction, model inference, and SHAP computation are all delegated to injected dependencies — this service only owns the assembly logic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ExpansionModelPort
|
Concrete expansion ML model (injected from infrastructure layer). |
required |
feature_extractor
|
ExpansionFeatureVector
|
Queries the dbt expansion mart for the feature vector. |
required |
Source code in src/domain/expansion/expansion_service.py
Functions¶
Generate a full ExpansionResult for a customer.
Business Context: Expansion depends on usage signals and GTM intent, not compliance risk — so no RiskScore is needed here. The domain service is intentionally leaner than ChurnModelService.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
customer
|
Customer
|
Active Customer entity that has not yet upgraded. |
required |
Returns:
| Type | Description |
|---|---|
ExpansionResult
|
ExpansionResult with calibrated upgrade propensity, target tier, |
ExpansionResult
|
SHAP explanations, and a deterministic GTM action recommendation. |