106 lines
3.0 KiB
Python
106 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from idea39_vizforge_interactive_economic.artifacts import export_artifact, generate_investor_artifact
|
|
from idea39_vizforge_interactive_economic.dsl import dump_business_model, load_business_model, parse_business_model
|
|
from idea39_vizforge_interactive_economic.models import BusinessModel
|
|
from idea39_vizforge_interactive_economic.simulation import compare_scenarios, sensitivity_analysis, simulate_business_model
|
|
|
|
|
|
def sample_yaml() -> str:
|
|
return """
|
|
name: Acme AI
|
|
horizon_months: 12
|
|
base_currency: USD
|
|
starting_cash: 250000
|
|
macro:
|
|
gdp_growth: 0.02
|
|
inflation: 0.03
|
|
interest_rate: 0.05
|
|
consumer_confidence: 102
|
|
funding_environment: 0.68
|
|
regime_switch_probability: 0.05
|
|
fx_rates:
|
|
USD: 1.0
|
|
revenue_streams:
|
|
- name: core
|
|
starting_customers: 100
|
|
price_per_customer: 120
|
|
monthly_growth_rate: 0.06
|
|
monthly_churn: 0.04
|
|
gross_margin: 0.82
|
|
acquisition_rate: 15
|
|
macro_sensitivity: 0.4
|
|
costs:
|
|
- name: cloud
|
|
monthly_amount: 10000
|
|
inflation_sensitivity: 0.5
|
|
macro_sensitivity: 0.1
|
|
hires:
|
|
- month: 2
|
|
role: engineer
|
|
annual_salary: 180000
|
|
count: 1
|
|
capex:
|
|
- month: 3
|
|
amount: 25000
|
|
description: hardware
|
|
financing:
|
|
- month: 0
|
|
instrument: equity
|
|
amount: 150000
|
|
valuation: 3000000
|
|
- month: 6
|
|
instrument: option_pool
|
|
option_pool_percent: 0.08
|
|
scenarios:
|
|
- name: recession
|
|
probability: 0.2
|
|
demand_multiplier: 0.82
|
|
cac_multiplier: 1.25
|
|
churn_delta: 0.03
|
|
cost_inflation_delta: 0.02
|
|
funding_environment_delta: -0.2
|
|
interest_rate_delta: 0.02
|
|
duration_months: 4
|
|
regime_bias: -0.2
|
|
""".strip()
|
|
|
|
|
|
def test_parse_roundtrip_and_digest() -> None:
|
|
model, digest = parse_business_model(sample_yaml())
|
|
assert model.name == "Acme AI"
|
|
assert len(digest) == 64
|
|
dumped = dump_business_model(model)
|
|
loaded = load_business_model(dumped)
|
|
assert loaded.name == model.name
|
|
|
|
|
|
def test_simulation_and_scenario_comparison() -> None:
|
|
model = load_business_model(sample_yaml())
|
|
base = simulate_business_model(model, n_sims=64, seed=11)
|
|
assert base.revenue_paths.shape == (64, 12)
|
|
assert base.cash_paths.shape == (64, 13)
|
|
assert base.summary()["runway_p50"] >= 0
|
|
|
|
recession = model.scenarios[0]
|
|
results = compare_scenarios(model, [recession], n_sims=32, seed=12)
|
|
assert results[0].scenario_name == "recession"
|
|
assert results[0].runway_months.shape == (32,)
|
|
|
|
|
|
def test_sensitivity_and_artifacts(tmp_path: Path) -> None:
|
|
model = load_business_model(sample_yaml())
|
|
rows = sensitivity_analysis(model, "macro.inflation", [0.02, 0.05], n_sims=24, seed=3)
|
|
assert len(rows) == 2
|
|
assert rows[0]["value"] == 0.02
|
|
|
|
bundle = generate_investor_artifact(model, n_sims=32, seed=4)
|
|
assert "VizForge Investor Scenario Brief" in bundle.markdown
|
|
assert "cash_runway.svg" in bundle.charts
|
|
|
|
export_artifact(bundle, tmp_path)
|
|
assert (tmp_path / "report.md").exists()
|
|
assert (tmp_path / "cash_runway.svg").exists()
|