Skip to content

refactor(api): resolve_config carries lifecycle config fields [LTV-Po.2a]#131

Open
shaypal5 wants to merge 1 commit into
mainfrom
feat/lifecycle-recipe-e2e
Open

refactor(api): resolve_config carries lifecycle config fields [LTV-Po.2a]#131
shaypal5 wants to merge 1 commit into
mainfrom
feat/lifecycle-recipe-e2e

Conversation

@shaypal5

Copy link
Copy Markdown
Contributor

Summary

First half of the split LTV-Po.2 (the config-plumbing enabler; the recipe YAMLs + end-to-end round-trip are Po.2b). Recipe.resolve_config and Generator.from_recipe/generate were lead-scoring-shaped — they only threaded n_accounts/n_contacts/n_leads to GenerationConfig — so a scheme: lifecycle recipe couldn't size its customer cohort (n_customers always took the package default).

Change

  • resolve_config now resolves n_customers (from default_population, an explicit kwarg, or the override dict) and early_tenure_weeks / observation_date (from override), and passes all three to GenerationConfig.
  • forward_windows_days is deliberately not resolvable — the lifecycle scheme locks it to its exported constant and rejects overrides (LTV-Pn.4c).
  • Generator.from_recipe and generate gain an n_customers parameter.

Lead-scoring is unchanged

A recipe without these keys leaves the lifecycle fields at their GenerationConfig defaults — exactly what the previous construction produced. Verified byte-identical via full-bundle SHA-256 of a lead-scoring bundle (both exposure modes) against main.

Tests (4 new; full suite 1885 passed / 51 skipped)

n_customers flows from default_population; kwarg + override precedence; lifecycle fields via override; a lead-scoring-style recipe keeps the lifecycle defaults.

Next: Po.2b — the b2b_saas_ltv_v1 recipe YAMLs (narrative.yaml with ≥2 industries + ≥2 geographies per the Po.1 constraint, difficulty_profiles.yaml), registry registration, difficulty_params resolution in build_world, and the Generator.from_recipe("b2b_saas_ltv_v1").generate() round-trip — which completes M6.

🤖 Generated with Claude Code

….2a]

First half of the split LTV-Po.2 (the config-plumbing enabler; the recipe
assets + e2e are Po.2b).  Recipe.resolve_config and
Generator.from_recipe/generate were lead-scoring-shaped — they only threaded
n_accounts/n_contacts/n_leads to GenerationConfig — so a scheme: lifecycle
recipe could not size its customer cohort (n_customers always took the
package default).

- resolve_config now resolves n_customers (from default_population, an explicit
  kwarg, or the override dict) and early_tenure_weeks / observation_date (from
  override) and passes all three to GenerationConfig.  forward_windows_days is
  deliberately NOT resolvable — the lifecycle scheme locks it to its exported
  constant and rejects overrides (LTV-Pn.4c).
- Generator.from_recipe and generate gain an n_customers parameter.

Lead-scoring config resolution is unchanged: a recipe without these keys leaves
the lifecycle fields at their GenerationConfig defaults, which is exactly what
the previous construction produced.  Verified byte-identical via full-bundle
SHA-256 of a lead-scoring bundle (both exposure modes) against main.

Tests: n_customers flows from default_population; kwarg + override precedence;
lifecycle fields via override; lead-scoring-style recipe keeps the lifecycle
defaults.  Full suite 1885 passed / 51 skipped; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 18, 2026 05:19
@shaypal5 shaypal5 added this to the dataset: leadforge-ltv-v1 milestone Jun 18, 2026
@shaypal5 shaypal5 added type: refactor Code change with no behavior difference layer: api api/ public Python surface dataset: leadforge-ltv-v1 Issue/PR scoped to the b2b_saas_ltv_v1 LTV dataset workstream labels Jun 18, 2026
@github-actions

Copy link
Copy Markdown

pr-agent-context report:

No unresolved review comments, failing checks, or actionable patch coverage gaps were found on PR #131 in repository https://github.com/leadforge-dev/leadforge. Treat this PR as all clear unless new signals appear.

Run metadata:

Tool ref: v4
Tool version: 4.0.21
Trigger: pull request opened
Workflow run: 27738478391 attempt 1
Comment timestamp: 2026-06-18T05:19:20.884099+00:00
PR head commit: cdcbca336623e5cfd4b4a576faf7bc7fa8b529cd

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the public API config plumbing so Recipe.resolve_config and Generator can carry lifecycle-specific sizing/config fields (notably n_customers) through to GenerationConfig, enabling lifecycle recipes to control cohort size instead of always using package defaults.

Changes:

  • Extend Recipe.resolve_config to resolve n_customers, early_tenure_weeks, and observation_date into GenerationConfig.
  • Add n_customers support to Generator.from_recipe(...) and per-call overrides in Generator.generate(...).
  • Add lifecycle-focused resolve_config tests and update LTV roadmap/agent plan notes to reflect the Po.2a split.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/api/test_resolve_config_lifecycle.py Adds tests covering lifecycle-field resolution via resolve_config.
leadforge/api/recipes.py Threads lifecycle config fields (n_customers, early_tenure_weeks, observation_date) through config resolution.
leadforge/api/generator.py Adds n_customers parameter passthrough/override support in from_recipe and generate.
docs/ltv/roadmap.md Documents Po.2 split and records Po.2a scope/constraints.
.agent-plan.md Updates workstream plan text to reflect the Po.2a/Po.2b split.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 51 to 55
n_accounts: int | None = None,
n_contacts: int | None = None,
n_leads: int | None = None,
n_customers: int | None = None,
horizon_days: int | None = None,
Comment on lines 129 to 133
n_accounts: int | None = None,
n_contacts: int | None = None,
n_leads: int | None = None,
n_customers: int | None = None,
difficulty: str | DifficultyProfile = _MISSING, # type: ignore[assignment]
Comment on lines +46 to +55
def test_defaults_when_recipe_omits_lifecycle_fields() -> None:
# A lead-scoring-style recipe (no n_customers) → lifecycle fields keep their
# GenerationConfig defaults; this is why lead-scoring resolution is unchanged.
cfg = _recipe(n_accounts=10, n_contacts=30, n_leads=30).resolve_config(
seed=1, difficulty="intro"
)
assert cfg.n_customers == 1500 # GenerationConfig default
assert cfg.forward_windows_days == (90, 365, 730)
assert cfg.early_tenure_weeks == 4
assert cfg.observation_date is None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dataset: leadforge-ltv-v1 Issue/PR scoped to the b2b_saas_ltv_v1 LTV dataset workstream layer: api api/ public Python surface type: refactor Code change with no behavior difference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants