Dynamic tracker. Update this file as phases complete. Keep it current.
v0.5.0 in progress — Milestones 7–11 complete, v4 dataset shipped. Full simulation engine + render/bundle + exposure filtering + CLI commands + validation harness implemented. v4 engine changes + build pipeline merged (PR #21). v4 dataset generated and validated. PR-agent refresh fallback wiring fixed for bot-authored reviews. 609 tests passing.
The primary focus is producing a v4 lead scoring dataset that fixes the issues found in v1–v3 datasets. This requires targeted engine changes + a build pipeline, followed by dataset release.
See docs/v4/design.md for full details.
-
docs/v4/design.md— consolidated requirements, contract, engine changes, implementation plan -
docs/v4/validation_spec.md— automated validation checks -
docs/v4/planning_pr_review.md— self-review and treatment plan -
scripts/spike_category_signal.py— spike experiment validating category signal approach - Updated
CLAUDE.md,AGENTS.md,.agent-plan.md
Engine changes:
- Add
category_latent_correlationstodifficulty_profiles.yaml(intro profile, scale 1.8) - Apply correlations in
simulation/population.pyvia_apply_category_latent_correlations() - Wire correlations through
api/generator.py→build_population() - Add
snapshot_dayparameter torender/snapshots.pywith windowed aggregation - Add new features:
touches_week_1,days_since_first_touch,expected_acv,total_touches_all - Add
opportunity_createdfeature (tracks ANY opp, not just open ones) - Add
FeatureSpecentries toschema/features.py - 19 new tests (16 windowed snapshot + 3 category-latent correlations)
Build pipeline:
-
scripts/build_v4_snapshot.py— day-10 snapshot + leakage trap + structured missingness + subsampling -
scripts/validate_v4_dataset.py— full validation perdocs/v4/validation_spec.md - End-to-end: generate bundle → build CSV → validate → all 7 mandatory checks pass
- LR AUC 0.652 (without trap); 0.034 boost with trap
- Generate
lead_scoring_intro_v4.csv(in datasets-private repo) - Write
RELEASE_v4.mdwith column dictionary, validation results, pedagogical notes - Update dataset repo README (v4 now recommended, v3 superseded)
- Tune snapshot day (14 → 10) for leakage trap boost ≥ 0.03 after stacking fix
- Add StandardScaler to validation script LR for convergence
- Update
.agent-plan.mdto reflect completion
| Item | Status | Rationale |
|---|---|---|
M12: CLI --json flag |
Deferred | No consumer needs it yet; add post-v4 |
M12: CLI --strict flag |
Deferred | Per-check control is better than global flag |
| M12: CLI help text polish | Deferred | Low priority vs dataset |
| M14: Sample bundle commit | Absorbed into v4-M2 | v4 dataset IS the sample |
| M14: Notebook 1 (inspecting world) | Deferred | Do after v4 ships |
| M14: Notebook 2 (lead scoring baseline) | Deferred | v4 validation script covers this |
| M14: Notebook 3 (public vs instructor) | Discarded | No current audience |
| M14: Notebook 4 (recipe customization) | Discarded | Premature |
| M15: Docs polish + v1.0 RC | Deferred | Do after v4 ships |
- Second vertical
- LTV labels as first-class task outputs
- Continuous-time / richer event engine
- Plugin architecture
- External-API enrichment
- Web UI or dashboard
- Engine fix:
is_sql=False→ never converts (deterministic invariant)
- v4 design (requirements, contract, engine changes, plan):
docs/v4/design.md - v4 validation spec:
docs/v4/validation_spec.md - v4 self-review:
docs/v4/planning_pr_review.md - Spike experiment:
scripts/spike_category_signal.py - Existing roadmap:
docs/leadforge_implementation_plan.md - CLI commands:
leadforge/cli/commands/ - Validation modules:
leadforge/validation/ - Snapshot builder:
leadforge/render/snapshots.py - Mechanism policy:
leadforge/mechanisms/policies.py - Difficulty profiles:
leadforge/recipes/b2b_saas_procurement_v1/difficulty_profiles.yaml
validation/bundle_checks.py: orchestrator — artifact, FK, leakage checks + wires realism/difficultyvalidation/invariants.py: determinism (same seed → identical hashes), exposure monotonicity (student ⊂ instructor)validation/realism.py: conversion rate bounds, non-empty core tables, feature value ranges (non-negative counts, valid booleans), stage distribution diversityvalidation/difficulty.py: known-difficulty validation, ordering check (no-op until engine modulates by difficulty)validation/drift.py: cross-seed stability — conversion rate spread, degenerate seed detection- All wired into
validate_bundle()viainclude_realismflag - 18 new validation tests; total 581 passing
cli/commands/generate.py: fully wired — parses all flags, callsGenerator.from_recipe().generate(), writes bundle via.save()cli/commands/inspect.py: readsmanifest.jsonand prints summary (recipe, seed, mode, tables with row counts, task splits, metadata presence)cli/commands/validate.py: checks manifest presence, required files, table row counts, SHA-256 hashes, task split integrity, FK constraints, leakage (unexpected columns)- 22 CLI tests (smoke, generate integration, inspect output, validate pass/fail/corrupt/missing); total 562 passing
exposure/filters.py:BundleFilterfrozen dataclass;FILTERSdict keyed byExposureMode;get_filter()acceptsstr | ExposureModeexposure/metadata.py:write_metadata_dir()— writesmetadata/withgraph.json,graph.graphml,world_spec.json,latent_registry.json,mechanism_summary.jsonexposure/modes.py:apply_exposure(bundle, root, mode)— dispatch; removes stalemetadata/forstudent_public- Wired into
api/bundle.pybetween dataset card and manifest steps - 22 exposure tests; total 547 passing
render/relational.py:to_dataframes()— 9-table dict of typed DataFrames from SimulationResult + PopulationResultrender/snapshots.py:build_snapshot()— 30-column leakage-free lead snapshot with touch/session/activity aggregates, account/contact field joinsrender/tasks.py:write_task_splits()— deterministic 70/15/15 train/valid/test Parquet split +task_manifest.jsonrender/manifests.py:build_manifest()/write_manifest()— manifest.json with provenance, row counts, SHA-256 hashesapi/bundle.py:write_bundle()— orchestrates all render steps; writes full bundle to diskcore/models.py:WorldBundle.save(path)— delegates towrite_bundle()via lazy importapi/generator.py:Generator.generate()— fully implemented end-to-end flow- 31 new render tests; total 521 passing
simulation/state.py:LeadSimState— per-lead mutable state (stage, dwell, converted, churned, sql_day)simulation/engine.py:simulate_world()— 90-day daily-step loop;SimulationResultoutput type- Churn: daily 0.4% probability →
closed_lost - Stage advance:
HazardTransitiondrives mql → … → negotiation - Conversion:
ConversionHazardfires fromnegotiation→closed_won - Touch emission:
RecencyDecayIntensityper day; session (30% of touch-days) and sales-activity (20% at sql+) emission - Post-sim:
OpportunityRowfor sql+ leads;CustomerRow+SubscriptionRowforclosed_wonleads
- Churn: daily 0.4% probability →
- 45 new tests; total 490 passing
mechanisms/base.py:MechanismABC,MechanismContext,MechanismSummary,MechanismAssignmentmechanisms/static.py:CategoricalDraw,BoundedNumericDraw,MixtureDrawmechanisms/influence.py:AdditiveInfluence,LogisticInfluence,SaturatingInfluence,ThresholdInfluence,InteractionTermmechanisms/scores.py:LatentScore— logistic score from weighted latent combinationmechanisms/hazards.py:ConversionHazard— daily conversion probability from latent scoremechanisms/transitions.py:StageSequence,HazardTransition— funnel stage advancementmechanisms/counts.py:PoissonIntensity,RecencyDecayIntensity— touch/session countsmechanisms/categorical.py:CategoricalInfluence,CHANNEL_QUALITY_SCORESmechanisms/measurement.py:NoisyProxy,NoisyCategorization,ProxyCompressionmechanisms/policies.py:assign_mechanisms()— motif-family-awareMechanismAssignmentfactory- 74 tests; total 437 passing
leadforge/simulation/population.py:build_population()— accounts (3 latent traits), contacts (4 latent traits, conditional on account), leads (1 latent trait, FK-consistent),LatentStateandPopulationResultoutput types- Motif-family latent biases (
_MOTIF_LATENT_BIAS) linking world structure to population tests/simulation/test_population.py: 26 tests covering counts, determinism, FK integrity, latent value ranges, trait completeness, motif bias properties, and observable field validity- Total: 358 tests passing
leadforge/structure/node_types.py:NodeTypeenum (9 categories);ROOT_ELIGIBLE,REQUIRES_PARENT,LEAF_ONLYconstraint setsleadforge/structure/graph.py:WorldGraphwrappingnetworkx.DiGraphwith DAG validation, node-type legality, nondegeneracy, and outcome-reachability checks; JSON + GraphML exportleadforge/structure/motifs.py:MotifFamilyfrozen dataclass; all 5 v1 families (fit_dominant, intent_dominant, sales_execution_sensitive, demo_trial_mediated, buying_committee_friction);get_motif_family()lookupleadforge/structure/rewiring.py:rewire()— optional-node dropping, edge-weight jitter, optional latent-confounder injection; fully deterministic given seedleadforge/structure/sampler.py:sample_hidden_graph(seed, motif_family_name=None)— selects motif, applies rewiring, validates, retries up to 20 timespyproject.toml: addednetworkx>=3.2+numpy>=1.26; mypy override for networkx- 132 new tests; total 327 passing
leadforge/core/ids.py:make_id(prefix, n)+ID_PREFIXESregistryleadforge/schema/entities.py: typed row dataclasses for all 9 tables (accounts, contacts, leads, touches, sessions, sales_activities, opportunities, customers, subscriptions) withDTYPE_MAP,to_dict(),empty_dataframe(), Parquet round-trip viaschema/tables.pyleadforge/schema/relationships.py:FKConstraint,ALL_CONSTRAINTS(10 FK edges),validate_fk()helper raisingFKViolationErrorleadforge/schema/features.py:FeatureSpecfrozen dataclass +LEAD_SNAPSHOT_FEATURES(29 features, one target)leadforge/schema/dictionaries.py:feature_dictionary_df()+write_feature_dictionary()leadforge/schema/tasks.py:SplitSpec,TaskManifest,CONVERTED_WITHIN_90_DAYSconstantpyproject.toml: added pandas≥2.0 + pyarrow≥14.0 as core deps; mypy overrides for both- 82 new tests; total 192 passing
leadforge/narrative/spec.py: frozen dataclassesNarrativeSpec,CompanySpec,ProductSpec,MarketSpec,GtmMotionSpec,PersonaSpec,FunnelStageSpec— all with validatedfrom_dict()leadforge/narrative/dataset_card.py:render_dataset_card(world_spec)— Markdown cardleadforge/core/models.py:WorldSpecgetsnarrative: NarrativeSpec | Nonefieldleadforge/api/generator.py:world_specproperty;from_recipe()resolves narrative intoWorldSpec- 51 new tests (spec validation, dataset card, Generator integration); total 110 passing
leadforge/core/rng.py:RNGRootwith SHA-256-derived named substreamsleadforge/core/hashing.py:hash_config()— stable SHA-256 digest ofGenerationConfigleadforge/core/serialization.py:load_yaml,load_json,dump_jsonleadforge/core/models.py:GenerationConfigwith__post_init__validation +package_versionleadforge/api/recipes.py: typedRecipedataclass,from_dict,resolve_config(full precedence: explicit kwargs > override dict > recipe defaults > package defaults)leadforge/api/generator.py:Generator.from_recipe()fully implemented (skeleton — no simulation);generate()stubs to v0.3.0leadforge/recipes/b2b_saas_procurement_v1/narrative.yaml: company, product, market, GTM, personas, funnel stagesleadforge/recipes/b2b_saas_procurement_v1/difficulty_profiles.yaml: intro / intermediate / advanced signal-noise profiles- 39 new tests (rng, hashing, recipes, generator); total 59 passing
pyproject.toml,README.md,LICENSE,.pre-commit-config.yaml- Full package skeleton with
__init__.pystubs for all submodules leadforge/core/:enums.py,exceptions.py,models.py,rng.py(stub),ids.py(stub)leadforge/cli/:main.py+ four commands (list-recipesimplemented, others stubbed)leadforge/recipes/: registry +b2b_saas_procurement_v1/recipe.yaml.github/workflows/ci.yml: lint, typecheck, test matrix (3.11 + 3.12) with coverage upload- 20 tests passing; ruff + mypy clean