Skip to content

Asset Types API

Asset-specific valuation models for IP, brands, technology, customer relationships, and human capital.

IP Valuation

ip_valuation

Intellectual property valuation methods.

Implements valuation for patents, copyrights, and trade secrets using risk-adjusted income approaches.

Classes

CopyrightInputs

Bases: BaseModel

Inputs for copyright valuation.

OptionPricingInputs

Bases: BaseModel

Inputs for real options patent valuation.

PatentInputs

Bases: BaseModel

Inputs for patent valuation.

PatentPortfolioInputs

Bases: BaseModel

Inputs for patent portfolio valuation.

TradeSecretInputs

Bases: BaseModel

Inputs for trade secret valuation.

Functions

copyright_valuation(projected_revenue: float, useful_life: int, discount_rate: float, royalty_rate: float) -> dict

Calculate PV of expected copyright royalty/licensing income.

Uses the relief-from-royalty approach to value copyrights based on projected revenue streams over the asset's useful life.

Parameters:

Name Type Description Default
projected_revenue float

Total projected revenue over useful life.

required
useful_life int

Useful life in years.

required
discount_rate float

Discount rate (decimal).

required
royalty_rate float

Applicable royalty rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/ip_valuation.py
def copyright_valuation(
    projected_revenue: float,
    useful_life: int,
    discount_rate: float,
    royalty_rate: float,
) -> dict:
    """Calculate PV of expected copyright royalty/licensing income.

    Uses the relief-from-royalty approach to value copyrights based on
    projected revenue streams over the asset's useful life.

    Args:
        projected_revenue: Total projected revenue over useful life.
        useful_life: Useful life in years.
        discount_rate: Discount rate (decimal).
        royalty_rate: Applicable royalty rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = CopyrightInputs(
        projected_revenue=projected_revenue,
        useful_life=useful_life,
        discount_rate=discount_rate,
        royalty_rate=royalty_rate,
    )

    annual_revenue = inputs.projected_revenue / inputs.useful_life
    annual_royalty = annual_revenue * inputs.royalty_rate

    value = present_value_of_annuity(
        annual_royalty, inputs.discount_rate, inputs.useful_life
    )

    steps = [
        f"Total projected revenue: {inputs.projected_revenue:,.0f}",
        f"Useful life: {inputs.useful_life} years",
        f"Annual revenue: {annual_revenue:,.0f}",
        f"Royalty rate: {inputs.royalty_rate:.2%}",
        f"Annual royalty income: {annual_royalty:,.0f}",
        f"PV of royalty stream: {value:,.0f}",
    ]

    return {
        "value": value,
        "method": "Relief-from-Royalty (Copyright)",
        "formula_reference": "PV = sum(R_t * r / (1+d)^t)",
        "steps": steps,
        "assumptions": {
            "projected_revenue": inputs.projected_revenue,
            "useful_life": inputs.useful_life,
            "discount_rate": inputs.discount_rate,
            "royalty_rate": inputs.royalty_rate,
        },
    }

option_pricing_patent(exercise_cost: float, expected_value: float, volatility: float, time_to_expiry: float, risk_free_rate: float) -> dict

Value a patent using Black-Scholes real options approximation.

Treats a patent as a call option: the right (but not obligation) to commercialize at cost K. This captures the value of managerial flexibility to wait, expand, or abandon the project.

Formula (Black-Scholes call option): d1 = [ln(S/K) + (r + σ²/2)T] / (σ√T) d2 = d1 - σ√T C = S·N(d1) - K·e^(-rT)·N(d2)

Where

S = expected value of commercialized patent K = exercise cost (commercialization cost) σ = volatility of expected value T = time to patent expiry r = risk-free rate N() = cumulative standard normal distribution

Parameters:

Name Type Description Default
exercise_cost float

Cost to commercialize the patent (strike price K).

required
expected_value float

Expected value if commercialized (underlying S).

required
volatility float

Volatility of expected value (0-2, decimal).

required
time_to_expiry float

Time remaining until patent expires (years).

required
risk_free_rate float

Risk-free interest rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Example

result = option_pricing_patent( ... exercise_cost=5_000_000, ... expected_value=10_000_000, ... volatility=0.40, ... time_to_expiry=10, ... risk_free_rate=0.03, ... ) result["value"] > 5_000_000 # option value > intrinsic True

Reference

Trigeorgis, L. (1996). Real Options: Managerial Flexibility and Strategy. MIT Press. Chapter 5.

Source code in src/asset_types/ip_valuation.py
def option_pricing_patent(
    exercise_cost: float,
    expected_value: float,
    volatility: float,
    time_to_expiry: float,
    risk_free_rate: float,
) -> dict:
    """Value a patent using Black-Scholes real options approximation.

    Treats a patent as a call option: the right (but not obligation) to
    commercialize at cost K. This captures the value of managerial flexibility
    to wait, expand, or abandon the project.

    Formula (Black-Scholes call option):
        d1 = [ln(S/K) + (r + σ²/2)T] / (σ√T)
        d2 = d1 - σ√T
        C = S·N(d1) - K·e^(-rT)·N(d2)

    Where:
        S = expected value of commercialized patent
        K = exercise cost (commercialization cost)
        σ = volatility of expected value
        T = time to patent expiry
        r = risk-free rate
        N() = cumulative standard normal distribution

    Args:
        exercise_cost: Cost to commercialize the patent (strike price K).
        expected_value: Expected value if commercialized (underlying S).
        volatility: Volatility of expected value (0-2, decimal).
        time_to_expiry: Time remaining until patent expires (years).
        risk_free_rate: Risk-free interest rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.

    Example:
        >>> result = option_pricing_patent(
        ...     exercise_cost=5_000_000,
        ...     expected_value=10_000_000,
        ...     volatility=0.40,
        ...     time_to_expiry=10,
        ...     risk_free_rate=0.03,
        ... )
        >>> result["value"] > 5_000_000  # option value > intrinsic
        True

    Reference:
        Trigeorgis, L. (1996). Real Options: Managerial Flexibility and Strategy.
        MIT Press. Chapter 5.
    """
    inputs = OptionPricingInputs(
        exercise_cost=exercise_cost,
        expected_value=expected_value,
        volatility=volatility,
        time_to_expiry=time_to_expiry,
        risk_free_rate=risk_free_rate,
    )

    S = inputs.expected_value  # noqa: N806 (standard Black-Scholes notation)
    K = inputs.exercise_cost  # noqa: N806 (standard Black-Scholes notation)
    sigma = inputs.volatility
    T = inputs.time_to_expiry  # noqa: N806 (standard Black-Scholes notation)
    r = inputs.risk_free_rate

    steps: list[str] = []

    # Black-Scholes calculation
    sqrt_t = math.sqrt(T)
    d1 = (math.log(S / K) + (r + sigma ** 2 / 2) * T) / (sigma * sqrt_t)
    d2 = d1 - sigma * sqrt_t

    # Approximation of cumulative normal distribution (Abramowitz & Stegun)
    def norm_cdf(x: float) -> float:
        a1 = 0.254829592
        a2 = -0.284496736
        a3 = 1.421413741
        a4 = -1.453152027
        a5 = 1.061405429
        p = 0.3275911
        sign = 1 if x >= 0 else -1
        x = abs(x)
        t = 1.0 / (1.0 + p * x)
        y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * math.exp(-x * x / 2)
        return 0.5 * (1.0 + sign * y)

    n_d1 = norm_cdf(d1)
    n_d2 = norm_cdf(d2)

    option_value = S * n_d1 - K * math.exp(-r * T) * n_d2
    intrinsic_value = max(S - K, 0)
    time_value = option_value - intrinsic_value

    steps.append(f"Expected value (S): {S:,.0f}")
    steps.append(f"Exercise cost (K): {K:,.0f}")
    steps.append(f"Volatility (σ): {sigma:.2%}")
    steps.append(f"Time to expiry (T): {T:.1f} years")
    steps.append(f"Risk-free rate (r): {r:.2%}")
    steps.append(f"d1: {d1:.4f}")
    steps.append(f"d2: {d2:.4f}")
    steps.append(f"N(d1): {n_d1:.4f}")
    steps.append(f"N(d2): {n_d2:.4f}")
    steps.append(f"Intrinsic value (S-K): {intrinsic_value:,.0f}")
    steps.append(f"Time value: {time_value:,.0f}")
    steps.append(f"Option value: {option_value:,.0f}")

    return {
        "value": max(option_value, 0),
        "method": "Real Options (Black-Scholes)",
        "formula_reference": "C = S·N(d1) - K·e^(-rT)·N(d2)",
        "steps": steps,
        "assumptions": {
            "expected_value": S,
            "exercise_cost": K,
            "volatility": sigma,
            "time_to_expiry": T,
            "risk_free_rate": r,
            "d1": round(d1, 4),
            "d2": round(d2, 4),
            "intrinsic_value": intrinsic_value,
            "time_value": round(time_value, 2),
        },
    }

patent_portfolio_valuation(patents: Sequence[dict], diversification_factor: float = 0.1) -> dict

Calculate total patent portfolio value with diversification adjustment.

Sums individual patent values and applies a diversification discount/premium based on portfolio concentration across technology categories. A more diversified portfolio receives a smaller adjustment.

Formula

Portfolio Value = sum(individual_values) x (1 - diversification_factor x HHI)

where HHI (Herfindahl-Hirschman Index) measures concentration.

Parameters:

Name Type Description Default
patents Sequence[dict]

List of dicts, each with 'value' (float) and optionally 'category' (str) and 'remaining_life' (int).

required
diversification_factor float

Adjustment factor (0-1), default 0.1.

0.1

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If patents list is empty or contains invalid data.

Example

patents = [ ... {"value": 1000000, "category": "pharma"}, ... {"value": 500000, "category": "tech"}, ... {"value": 750000, "category": "pharma"}, ... ] result = patent_portfolio_valuation(patents) result["value"] < 2250000 # diversification adjustment True

Source code in src/asset_types/ip_valuation.py
def patent_portfolio_valuation(
    patents: Sequence[dict],
    diversification_factor: float = 0.1,
) -> dict:
    """Calculate total patent portfolio value with diversification adjustment.

    Sums individual patent values and applies a diversification discount/premium
    based on portfolio concentration across technology categories. A more
    diversified portfolio receives a smaller adjustment.

    Formula:
        Portfolio Value = sum(individual_values) x (1 - diversification_factor x HHI)
    where HHI (Herfindahl-Hirschman Index) measures concentration.

    Args:
        patents: List of dicts, each with 'value' (float) and optionally
            'category' (str) and 'remaining_life' (int).
        diversification_factor: Adjustment factor (0-1), default 0.1.

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If patents list is empty or contains invalid data.

    Example:
        >>> patents = [
        ...     {"value": 1000000, "category": "pharma"},
        ...     {"value": 500000, "category": "tech"},
        ...     {"value": 750000, "category": "pharma"},
        ... ]
        >>> result = patent_portfolio_valuation(patents)
        >>> result["value"] < 2250000  # diversification adjustment
        True
    """
    patent_list = list(patents)
    if not patent_list:
        raise ValueError("Patents list cannot be empty")

    inputs = PatentPortfolioInputs(
        patents=patent_list, diversification_factor=diversification_factor
    )

    steps: list[str] = []

    # Step 1: Sum individual values
    total_raw = sum(p["value"] for p in inputs.patents)
    steps.append(f"Number of patents: {len(inputs.patents)}")
    steps.append(f"Sum of individual values: {total_raw:,.0f}")

    # Step 2: Calculate HHI for diversification adjustment
    categories: dict[str, float] = {}
    for p in inputs.patents:
        cat = p.get("category", "uncategorized")
        categories[cat] = categories.get(cat, 0) + p["value"]

    hhi = sum((v / total_raw) ** 2 for v in categories.values()) if total_raw > 0 else 0.0

    steps.append(f"Categories: {len(categories)}")
    for cat, val in categories.items():
        steps.append(f"  {cat}: {val:,.0f} ({val / total_raw:.1%})")
    steps.append(f"HHI (concentration): {hhi:.4f}")

    # Step 3: Apply diversification adjustment
    adjustment = 1 - inputs.diversification_factor * hhi
    portfolio_value = total_raw * adjustment
    steps.append(
        f"Diversification adjustment: {adjustment:.4f} "
        f"(factor={inputs.diversification_factor}, HHI={hhi:.4f})"
    )
    steps.append(f"Portfolio value: {portfolio_value:,.0f}")

    return {
        "value": portfolio_value,
        "method": "Patent Portfolio with Diversification Adjustment",
        "formula_reference": "V = sum(Vi) x (1 - DF x HHI)",
        "steps": steps,
        "assumptions": {
            "num_patents": len(inputs.patents),
            "total_raw_value": total_raw,
            "hhi": hhi,
            "diversification_factor": inputs.diversification_factor,
            "num_categories": len(categories),
        },
    }

patent_valuation(remaining_life: int, cash_flow_projections: Sequence[float], probability_of_success: float, discount_rate: float, comparable_license_rates: Sequence[float] | None = None) -> dict

Calculate risk-adjusted patent value with probability weighting.

Values a patent by discounting projected cash flows and applying probability of commercial success. Comparable license rates provide a cross-check via the relief-from-royalty approach.

Parameters:

Name Type Description Default
remaining_life int

Remaining patent life in years.

required
cash_flow_projections Sequence[float]

Projected annual cash flows from the patent.

required
probability_of_success float

Probability of commercial success (0-1).

required
discount_rate float

Discount rate (decimal).

required
comparable_license_rates Sequence[float] | None

Optional comparable license royalty rates.

None

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/ip_valuation.py
def patent_valuation(
    remaining_life: int,
    cash_flow_projections: Sequence[float],
    probability_of_success: float,
    discount_rate: float,
    comparable_license_rates: Sequence[float] | None = None,
) -> dict:
    """Calculate risk-adjusted patent value with probability weighting.

    Values a patent by discounting projected cash flows and applying
    probability of commercial success. Comparable license rates provide
    a cross-check via the relief-from-royalty approach.

    Args:
        remaining_life: Remaining patent life in years.
        cash_flow_projections: Projected annual cash flows from the patent.
        probability_of_success: Probability of commercial success (0-1).
        discount_rate: Discount rate (decimal).
        comparable_license_rates: Optional comparable license royalty rates.

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = PatentInputs(
        remaining_life=remaining_life,
        cash_flow_projections=list(cash_flow_projections),
        probability_of_success=probability_of_success,
        discount_rate=discount_rate,
        comparable_license_rates=(
            list(comparable_license_rates) if comparable_license_rates else None
        ),
    )

    steps: list[str] = []
    assumptions: dict[str, float | str] = {}

    # Step 1: Calculate PV of cash flows
    pv_cash_flows = 0.0
    for t, cf in enumerate(inputs.cash_flow_projections, start=1):
        if t > inputs.remaining_life:
            break
        pv = present_value(cf, inputs.discount_rate, t)
        pv_cash_flows += pv
        steps.append(f"Year {t} CF: {cf:,.0f} -> PV: {pv:,.0f}")

    # Step 2: Apply probability of success
    risk_adjusted_pv = risk_adjusted_value(
        pv_cash_flows, inputs.probability_of_success
    )
    steps.append(
        f"Gross PV: {pv_cash_flows:,.0f} "
        f"x POS ({inputs.probability_of_success:.0%}) "
        f"= {risk_adjusted_pv:,.0f}"
    )

    # Step 3: Cross-check with comparable license rates if provided
    comparable_value: float | None = None
    if inputs.comparable_license_rates and len(inputs.comparable_license_rates) > 0:
        avg_rate = sum(inputs.comparable_license_rates) / len(
            inputs.comparable_license_rates
        )
        comparable_value = risk_adjusted_pv * (avg_rate / 0.05)  # normalize
        steps.append(
            f"Comparable avg rate: {avg_rate:.2%}, "
            f"cross-check value: {comparable_value:,.0f}"
        )

    final_value = risk_adjusted_pv

    assumptions = {
        "remaining_life": inputs.remaining_life,
        "number_of_projections": len(inputs.cash_flow_projections),
        "probability_of_success": inputs.probability_of_success,
        "discount_rate": inputs.discount_rate,
    }
    if comparable_license_rates:
        assumptions["comparable_license_rates"] = str(
            comparable_license_rates
        )

    return {
        "value": final_value,
        "method": "Risk-Adjusted Income Approach",
        "formula_reference": "PV = sum(CF_t / (1+r)^t) x POS",
        "steps": steps,
        "assumptions": assumptions,
    }

trade_secret_valuation(development_cost: float, economic_life: int, competitive_advantage_period: int, discount_rate: float, secrecy_probability: float) -> dict

Value a trade secret incorporating secrecy risk over time.

Combines cost approach (development cost) with income approach (competitive advantage period), adjusted for the probability of maintaining secrecy over time.

Parameters:

Name Type Description Default
development_cost float

Cost to develop the trade secret.

required
economic_life int

Expected economic life in years.

required
competitive_advantage_period int

Period of competitive advantage in years.

required
discount_rate float

Discount rate (decimal).

required
secrecy_probability float

Probability of maintaining secrecy (0-1).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/ip_valuation.py
def trade_secret_valuation(
    development_cost: float,
    economic_life: int,
    competitive_advantage_period: int,
    discount_rate: float,
    secrecy_probability: float,
) -> dict:
    """Value a trade secret incorporating secrecy risk over time.

    Combines cost approach (development cost) with income approach
    (competitive advantage period), adjusted for the probability of
    maintaining secrecy over time.

    Args:
        development_cost: Cost to develop the trade secret.
        economic_life: Expected economic life in years.
        competitive_advantage_period: Period of competitive advantage in years.
        discount_rate: Discount rate (decimal).
        secrecy_probability: Probability of maintaining secrecy (0-1).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = TradeSecretInputs(
        development_cost=development_cost,
        economic_life=economic_life,
        competitive_advantage_period=competitive_advantage_period,
        discount_rate=discount_rate,
        secrecy_probability=secrecy_probability,
    )

    steps: list[str] = []

    # Step 1: Cost approach floor
    cost_floor = inputs.development_cost
    steps.append(f"Development cost (floor): {cost_floor:,.0f}")

    # Step 2: Income approach - PV of competitive advantage
    annual_benefit = inputs.development_cost / inputs.competitive_advantage_period
    income_value = present_value_of_annuity(
        annual_benefit,
        inputs.discount_rate,
        inputs.competitive_advantage_period,
    )
    steps.append(
        f"Annual benefit: {annual_benefit:,.0f}, "
        f"Income PV: {income_value:,.0f}"
    )

    # Step 3: Apply secrecy risk (probability decays over economic life)
    cumulative_secrecy = inputs.secrecy_probability ** inputs.economic_life
    risk_adjusted_income = income_value * cumulative_secrecy
    steps.append(
        f"Cumulative secrecy probability "
        f"({inputs.secrecy_probability:.0%}^{inputs.economic_life}): "
        f"{cumulative_secrecy:.4f}"
    )
    steps.append(f"Risk-adjusted income value: {risk_adjusted_income:,.0f}")

    # Step 4: Take higher of cost floor and risk-adjusted income
    value = max(cost_floor, risk_adjusted_income)
    steps.append(
        f"Final value (max of cost floor {cost_floor:,.0f} "
        f"and risk-adjusted income {risk_adjusted_income:,.0f}): "
        f"{value:,.0f}"
    )

    return {
        "value": value,
        "method": "Cost-Income Hybrid with Secrecy Risk",
        "formula_reference": "V = max(Cost, PV(Benefit) x P(secrecy)^t)",
        "steps": steps,
        "assumptions": {
            "development_cost": inputs.development_cost,
            "economic_life": inputs.economic_life,
            "competitive_advantage_period": inputs.competitive_advantage_period,
            "discount_rate": inputs.discount_rate,
            "secrecy_probability": inputs.secrecy_probability,
        },
    }

Brand Valuation

brand_valuation

Brand and trademark valuation methods.

Implements brand value using Relief-from-Royalty and Excess Earnings methods with brand strength adjustments.

Classes

BrandRoyaltyInputs

Bases: BaseModel

Inputs for brand royalty rate from comparables.

BrandStrengthInputs

Bases: BaseModel

Inputs for brand strength index calculation.

InterbrandInputs

Bases: BaseModel

Inputs for Interbrand brand valuation.

TrademarkInputs

Bases: BaseModel

Inputs for trademark/brand valuation.

Functions

brand_royalty_rate_from_comparables(comparable_rates: Sequence[float], brand_strength_adjustment: float = 0.0) -> dict

Derive brand royalty rate from comparable brand licensing agreements.

Calculates a base rate from comparable transactions (median), then adjusts for the subject brand's relative strength. A stronger brand commands a higher rate; a weaker brand commands a lower rate.

Formula

Base Rate = Median(comparable_rates) Adjusted Rate = Base Rate x (1 + brand_strength_adjustment)

Parameters:

Name Type Description Default
comparable_rates Sequence[float]

List of comparable brand royalty rates (as decimals).

required
brand_strength_adjustment float

Adjustment factor (-0.5 to +0.5). Positive for stronger brands, negative for weaker.

0.0

Returns:

Type Description
dict

Dict with value (royalty rate), method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If comparable_rates is empty or adjustment is out of range.

Example

rates = [0.03, 0.04, 0.05, 0.06, 0.04] result = brand_royalty_rate_from_comparables(rates, 0.10) result["value"] # 0.04 * 1.10 = 0.044 0.044

Source code in src/asset_types/brand_valuation.py
def brand_royalty_rate_from_comparables(
    comparable_rates: Sequence[float],
    brand_strength_adjustment: float = 0.0,
) -> dict:
    """Derive brand royalty rate from comparable brand licensing agreements.

    Calculates a base rate from comparable transactions (median), then
    adjusts for the subject brand's relative strength. A stronger brand
    commands a higher rate; a weaker brand commands a lower rate.

    Formula:
        Base Rate = Median(comparable_rates)
        Adjusted Rate = Base Rate x (1 + brand_strength_adjustment)

    Args:
        comparable_rates: List of comparable brand royalty rates (as decimals).
        brand_strength_adjustment: Adjustment factor (-0.5 to +0.5).
            Positive for stronger brands, negative for weaker.

    Returns:
        Dict with value (royalty rate), method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If comparable_rates is empty or adjustment is out of range.

    Example:
        >>> rates = [0.03, 0.04, 0.05, 0.06, 0.04]
        >>> result = brand_royalty_rate_from_comparables(rates, 0.10)
        >>> result["value"]  # 0.04 * 1.10 = 0.044
        0.044
    """
    rates = list(comparable_rates)
    if not rates:
        raise ValueError("Comparable rates list cannot be empty")

    inputs = BrandRoyaltyInputs(
        comparable_rates=rates,
        brand_strength_adjustment=brand_strength_adjustment,
    )

    steps: list[str] = []

    # Calculate statistics
    sorted_rates = sorted(rates)
    n = len(sorted_rates)
    median_rate = sorted_rates[n // 2] if n % 2 == 1 else (sorted_rates[n // 2 - 1] + sorted_rates[n // 2]) / 2
    mean_rate = sum(rates) / n
    min_rate = min(rates)
    max_rate = max(rates)

    steps.append(f"Number of comparables: {n}")
    steps.append(f"Rate range: {min_rate:.2%} - {max_rate:.2%}")
    steps.append(f"Mean rate: {mean_rate:.4f}")
    steps.append(f"Median rate: {median_rate:.4f}")

    # Apply brand strength adjustment
    adjusted_rate = median_rate * (1 + inputs.brand_strength_adjustment)
    adjusted_rate = max(0.0, adjusted_rate)  # floor at zero

    steps.append(f"Brand strength adjustment: {inputs.brand_strength_adjustment:+.0%}")
    steps.append(f"Adjusted royalty rate: {adjusted_rate:.4f} ({adjusted_rate:.2%})")

    return {
        "value": adjusted_rate,
        "method": "Comparable Brand Royalty Rate",
        "formula_reference": "Rate = Median(comparables) x (1 + adjustment)",
        "steps": steps,
        "assumptions": {
            "num_comparables": n,
            "min_rate": min_rate,
            "max_rate": max_rate,
            "mean_rate": round(mean_rate, 4),
            "median_rate": median_rate,
            "brand_strength_adjustment": inputs.brand_strength_adjustment,
        },
    }

brand_strength_index(revenue_stability: float, market_share: float, geographic_reach: float, customer_loyalty: float, investment_level: float) -> dict

Calculate composite brand strength score on a 0-100 scale.

Combines five dimensions of brand strength using weighted scoring: - Revenue stability (25%): Consistency and predictability of brand revenue - Market share (25%): Relative position in the market - Geographic reach (20%): Breadth of market coverage - Customer loyalty (20%): Retention and advocacy metrics - Investment level (10%): Ongoing brand investment and support

Formula

BSI = (RS x 0.25 + MS x 0.25 + GR x 0.20 + CL x 0.20 + IL x 0.10) x 100

Parameters:

Name Type Description Default
revenue_stability float

Revenue stability score (0-1).

required
market_share float

Market share score (0-1).

required
geographic_reach float

Geographic reach score (0-1).

required
customer_loyalty float

Customer loyalty score (0-1).

required
investment_level float

Brand investment level score (0-1).

required

Returns:

Type Description
dict

Dict with value (0-100), method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is outside [0, 1].

Example

result = brand_strength_index(0.8, 0.6, 0.7, 0.9, 0.5) result["value"] 72.0

Source code in src/asset_types/brand_valuation.py
def brand_strength_index(
    revenue_stability: float,
    market_share: float,
    geographic_reach: float,
    customer_loyalty: float,
    investment_level: float,
) -> dict:
    """Calculate composite brand strength score on a 0-100 scale.

    Combines five dimensions of brand strength using weighted scoring:
    - Revenue stability (25%): Consistency and predictability of brand revenue
    - Market share (25%): Relative position in the market
    - Geographic reach (20%): Breadth of market coverage
    - Customer loyalty (20%): Retention and advocacy metrics
    - Investment level (10%): Ongoing brand investment and support

    Formula:
        BSI = (RS x 0.25 + MS x 0.25 + GR x 0.20 + CL x 0.20 + IL x 0.10) x 100

    Args:
        revenue_stability: Revenue stability score (0-1).
        market_share: Market share score (0-1).
        geographic_reach: Geographic reach score (0-1).
        customer_loyalty: Customer loyalty score (0-1).
        investment_level: Brand investment level score (0-1).

    Returns:
        Dict with value (0-100), method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is outside [0, 1].

    Example:
        >>> result = brand_strength_index(0.8, 0.6, 0.7, 0.9, 0.5)
        >>> result["value"]
        72.0
    """
    inputs = BrandStrengthInputs(
        revenue_stability=revenue_stability,
        market_share=market_share,
        geographic_reach=geographic_reach,
        customer_loyalty=customer_loyalty,
        investment_level=investment_level,
    )

    weights = {
        "revenue_stability": 0.25,
        "market_share": 0.25,
        "geographic_reach": 0.20,
        "customer_loyalty": 0.20,
        "investment_level": 0.10,
    }

    scores = {
        "revenue_stability": inputs.revenue_stability,
        "market_share": inputs.market_share,
        "geographic_reach": inputs.geographic_reach,
        "customer_loyalty": inputs.customer_loyalty,
        "investment_level": inputs.investment_level,
    }

    steps: list[str] = []
    weighted_sum = 0.0
    for name, weight in weights.items():
        contribution = scores[name] * weight
        weighted_sum += contribution
        steps.append(f"{name}: {scores[name]:.2f} x {weight:.0%} = {contribution:.4f}")

    bsi_score = weighted_sum * 100

    # Rating classification
    if bsi_score >= 80:
        rating = "Excellent"
    elif bsi_score >= 60:
        rating = "Strong"
    elif bsi_score >= 40:
        rating = "Moderate"
    elif bsi_score >= 20:
        rating = "Weak"
    else:
        rating = "Very Weak"

    steps.append(f"Weighted sum: {weighted_sum:.4f}")
    steps.append(f"Brand Strength Index: {bsi_score:.1f}/100 ({rating})")

    return {
        "value": bsi_score,
        "method": "Composite Brand Strength Index",
        "formula_reference": "BSI = sum(Factor_i x Weight_i) x 100",
        "steps": steps,
        "assumptions": {
            "weights": weights,
            "scores": scores,
            "rating": rating,
        },
    }

interbrand_brand_valuation(brand_earnings: float, role_of_brand_index: float, brand_strength_score: float, discount_rate: float) -> dict

Value a brand using the Interbrand methodology.

The Interbrand method calculates brand value as

Brand Value = Branded Earnings x Brand Multiple

Where

Branded Earnings = Brand Earnings x Role of Brand Index Brand Multiple = derived from Brand Strength Score via discount rate

The brand strength score (0-100) maps to a discount rate via the brand-specific discount rate curve. Stronger brands have lower discount rates, resulting in higher multiples.

Formula

Branded Earnings = Earnings x ROBI Brand Multiple = 1 / (discount_rate - g) [Gordon Growth approximation] Brand Value = Branded Earnings x Brand Multiple

Parameters:

Name Type Description Default
brand_earnings float

After-tax operating profit attributable to the brand.

required
role_of_brand_index float

Proportion of purchase decision driven by brand (0-1).

required
brand_strength_score float

Brand strength score from 0-100.

required
discount_rate float

Brand-specific discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Example

result = interbrand_brand_valuation( ... brand_earnings=50_000_000, ... role_of_brand_index=0.60, ... brand_strength_score=75, ... discount_rate=0.08, ... ) result["value"] > 0 True

Reference

Interbrand. "Best Global Brands Methodology." https://interbrand.com/best-brands/

Source code in src/asset_types/brand_valuation.py
def interbrand_brand_valuation(
    brand_earnings: float,
    role_of_brand_index: float,
    brand_strength_score: float,
    discount_rate: float,
) -> dict:
    """Value a brand using the Interbrand methodology.

    The Interbrand method calculates brand value as:
        Brand Value = Branded Earnings x Brand Multiple

    Where:
        Branded Earnings = Brand Earnings x Role of Brand Index
        Brand Multiple = derived from Brand Strength Score via discount rate

    The brand strength score (0-100) maps to a discount rate via the
    brand-specific discount rate curve. Stronger brands have lower discount
    rates, resulting in higher multiples.

    Formula:
        Branded Earnings = Earnings x ROBI
        Brand Multiple = 1 / (discount_rate - g)  [Gordon Growth approximation]
        Brand Value = Branded Earnings x Brand Multiple

    Args:
        brand_earnings: After-tax operating profit attributable to the brand.
        role_of_brand_index: Proportion of purchase decision driven by brand (0-1).
        brand_strength_score: Brand strength score from 0-100.
        discount_rate: Brand-specific discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.

    Example:
        >>> result = interbrand_brand_valuation(
        ...     brand_earnings=50_000_000,
        ...     role_of_brand_index=0.60,
        ...     brand_strength_score=75,
        ...     discount_rate=0.08,
        ... )
        >>> result["value"] > 0
        True

    Reference:
        Interbrand. "Best Global Brands Methodology."
        https://interbrand.com/best-brands/
    """
    inputs = InterbrandInputs(
        brand_earnings=brand_earnings,
        role_of_brand_index=role_of_brand_index,
        brand_strength_score=brand_strength_score,
        discount_rate=discount_rate,
    )

    steps: list[str] = []

    # Step 1: Calculate branded earnings
    branded_earnings = inputs.brand_earnings * inputs.role_of_brand_index
    steps.append(f"Brand earnings: {inputs.brand_earnings:,.0f}")
    steps.append(f"Role of Brand Index: {inputs.role_of_brand_index:.2%}")
    steps.append(f"Branded earnings: {branded_earnings:,.0f}")

    # Step 2: Map brand strength to brand multiple
    brand_multiple = inputs.brand_strength_score / (inputs.discount_rate * 100)
    steps.append(f"Brand strength score: {inputs.brand_strength_score:.0f}/100")
    steps.append(f"Discount rate: {inputs.discount_rate:.2%}")
    steps.append(f"Brand multiple: {brand_multiple:.2f}x")

    # Step 3: Calculate brand value
    brand_value = branded_earnings * brand_multiple
    steps.append(f"Brand value: {brand_value:,.0f}")

    return {
        "value": brand_value,
        "method": "Interbrand Brand Valuation",
        "formula_reference": "BV = (Earnings x ROBI) x (BS / (r x 100))",
        "steps": steps,
        "assumptions": {
            "brand_earnings": inputs.brand_earnings,
            "role_of_brand_index": inputs.role_of_brand_index,
            "brand_strength_score": inputs.brand_strength_score,
            "discount_rate": inputs.discount_rate,
            "brand_multiple": round(brand_multiple, 2),
        },
    }

trademark_valuation(revenue: float, profit_margin: float, brand_strength_index: float, discount_rate: float, useful_life: int, method: str = 'relief_from_royalty') -> dict

Calculate brand value using RFR or excess earnings method.

Brand strength index adjusts the royalty rate in the relief-from-royalty method, or the excess earnings in the excess earnings method.

Parameters:

Name Type Description Default
revenue float

Annual revenue attributable to the brand.

required
profit_margin float

Profit margin (decimal).

required
brand_strength_index float

Brand strength index (0-1, higher = stronger).

required
discount_rate float

Discount rate (decimal).

required
useful_life int

Useful life in years.

required
method str

Valuation method ("relief_from_royalty" or "excess_earnings").

'relief_from_royalty'

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid or method is unknown.

Source code in src/asset_types/brand_valuation.py
def trademark_valuation(
    revenue: float,
    profit_margin: float,
    brand_strength_index: float,
    discount_rate: float,
    useful_life: int,
    method: str = "relief_from_royalty",
) -> dict:
    """Calculate brand value using RFR or excess earnings method.

    Brand strength index adjusts the royalty rate in the relief-from-royalty
    method, or the excess earnings in the excess earnings method.

    Args:
        revenue: Annual revenue attributable to the brand.
        profit_margin: Profit margin (decimal).
        brand_strength_index: Brand strength index (0-1, higher = stronger).
        discount_rate: Discount rate (decimal).
        useful_life: Useful life in years.
        method: Valuation method ("relief_from_royalty" or "excess_earnings").

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid or method is unknown.
    """
    inputs = TrademarkInputs(
        revenue=revenue,
        profit_margin=profit_margin,
        brand_strength_index=brand_strength_index,
        discount_rate=discount_rate,
        useful_life=useful_life,
        method=method,
    )

    if inputs.method not in ("relief_from_royalty", "excess_earnings"):
        raise ValueError(
            f"Unknown method: {inputs.method}. "
            "Use 'relief_from_royalty' or 'excess_earnings'."
        )

    steps: list[str] = []

    # Brand strength adjusts the base royalty rate
    base_royalty_rate = 0.05  # 5% industry baseline
    adjusted_royalty_rate = base_royalty_rate * inputs.brand_strength_index

    if inputs.method == "relief_from_royalty":
        # RFR: PV of avoided royalty payments
        annual_royalty = inputs.revenue * adjusted_royalty_rate
        after_tax_royalty = annual_royalty * 0.75  # assume 25% tax rate

        value = present_value_of_annuity(
            after_tax_royalty, inputs.discount_rate, inputs.useful_life
        )

        steps = [
            f"Revenue base: {inputs.revenue:,.0f}",
            f"Base royalty rate: {base_royalty_rate:.2%}",
            f"Brand strength index: {inputs.brand_strength_index:.2f}",
            f"Adjusted royalty rate: {adjusted_royalty_rate:.2%}",
            f"Annual royalty: {annual_royalty:,.0f}",
            f"After-tax royalty (25% tax): {after_tax_royalty:,.0f}",
            f"PV over {inputs.useful_life} years at "
            f"{inputs.discount_rate:.2%}: {value:,.0f}",
        ]

    else:  # excess_earnings
        # Excess earnings: brand contributes to profit above normal return
        profit = inputs.revenue * inputs.profit_margin
        brand_contribution = profit * inputs.brand_strength_index

        value = present_value_of_annuity(
            brand_contribution, inputs.discount_rate, inputs.useful_life
        )

        steps = [
            f"Revenue: {inputs.revenue:,.0f}",
            f"Profit margin: {inputs.profit_margin:.2%}",
            f"Profit: {profit:,.0f}",
            f"Brand strength index: {inputs.brand_strength_index:.2f}",
            f"Brand contribution to profit: {brand_contribution:,.0f}",
            f"PV over {inputs.useful_life} years at "
            f"{inputs.discount_rate:.2%}: {value:,.0f}",
        ]

    return {
        "value": value,
        "method": (
            "Relief-from-Royalty (Brand)"
            if inputs.method == "relief_from_royalty"
            else "Excess Earnings (Brand)"
        ),
        "formula_reference": (
            "RFR = sum(Revenue x Royalty x (1-T) / (1+r)^t)"
            if inputs.method == "relief_from_royalty"
            else "EE = sum(Profit x BSI / (1+r)^t)"
        ),
        "steps": steps,
        "assumptions": {
            "revenue": inputs.revenue,
            "profit_margin": inputs.profit_margin,
            "brand_strength_index": inputs.brand_strength_index,
            "discount_rate": inputs.discount_rate,
            "useful_life": inputs.useful_life,
            "method": inputs.method,
            "tax_rate": 0.25,
        },
    }

Technology Valuation

technology_valuation

Technology asset valuation methods.

Implements valuation for developed technology, software, data assets, and platforms with network effects.

Classes

AlgorithmValuationInputs

Bases: BaseModel

Inputs for ML algorithm valuation.

ApiValuationInputs

Bases: BaseModel

Inputs for API valuation.

DataAssetInputs

Bases: BaseModel

Inputs for data asset valuation.

DevelopedTechnologyInputs

Bases: BaseModel

Inputs for developed technology valuation.

PlatformInputs

Bases: BaseModel

Inputs for platform valuation.

SoftwareInputs

Bases: BaseModel

Inputs for software valuation.

TechObsolescenceInputs

Bases: BaseModel

Inputs for technology obsolescence curve.

Functions

algorithm_valuation(computational_savings: float, deployment_scale: float, competitive_advantage_years: int, discount_rate: float) -> dict

Value an ML algorithm based on computational savings and competitive advantage.

Values the algorithm by the cost savings it generates at scale, projected over the period of competitive advantage.

Formula

Annual Benefit = Computational Savings x Deployment Scale Value = sum(Annual Benefit / (1 + r)^t) for t = 1 to advantage_years

Parameters:

Name Type Description Default
computational_savings float

Annual computational cost savings from the algorithm.

required
deployment_scale float

Scale factor representing breadth of deployment (>0).

required
competitive_advantage_years int

Expected years of competitive advantage.

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Example

result = algorithm_valuation( ... computational_savings=500_000, ... deployment_scale=3.0, ... competitive_advantage_years=5, ... discount_rate=0.12, ... ) result["value"] > 0 True

Source code in src/asset_types/technology_valuation.py
def algorithm_valuation(
    computational_savings: float,
    deployment_scale: float,
    competitive_advantage_years: int,
    discount_rate: float,
) -> dict:
    """Value an ML algorithm based on computational savings and competitive advantage.

    Values the algorithm by the cost savings it generates at scale,
    projected over the period of competitive advantage.

    Formula:
        Annual Benefit = Computational Savings x Deployment Scale
        Value = sum(Annual Benefit / (1 + r)^t) for t = 1 to advantage_years

    Args:
        computational_savings: Annual computational cost savings from the algorithm.
        deployment_scale: Scale factor representing breadth of deployment (>0).
        competitive_advantage_years: Expected years of competitive advantage.
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.

    Example:
        >>> result = algorithm_valuation(
        ...     computational_savings=500_000,
        ...     deployment_scale=3.0,
        ...     competitive_advantage_years=5,
        ...     discount_rate=0.12,
        ... )
        >>> result["value"] > 0
        True
    """
    inputs = AlgorithmValuationInputs(
        computational_savings=computational_savings,
        deployment_scale=deployment_scale,
        competitive_advantage_years=competitive_advantage_years,
        discount_rate=discount_rate,
    )

    steps: list[str] = []

    annual_benefit = inputs.computational_savings * inputs.deployment_scale

    steps.append(f"Computational savings: {inputs.computational_savings:,.0f}")
    steps.append(f"Deployment scale: {inputs.deployment_scale:.1f}x")
    steps.append(f"Annual benefit: {annual_benefit:,.0f}")
    steps.append(f"Competitive advantage: {inputs.competitive_advantage_years} years")
    steps.append(f"Discount rate: {inputs.discount_rate:.2%}")

    total_pv = 0.0
    for t in range(1, inputs.competitive_advantage_years + 1):
        pv = present_value(annual_benefit, inputs.discount_rate, t)
        total_pv += pv
        steps.append(f"Year {t}: PV={pv:,.0f}")

    steps.append(f"Total algorithm value: {total_pv:,.0f}")

    return {
        "value": total_pv,
        "method": "ML Algorithm Income Approach",
        "formula_reference": "V = sum(Savings x Scale / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "computational_savings": inputs.computational_savings,
            "deployment_scale": inputs.deployment_scale,
            "competitive_advantage_years": inputs.competitive_advantage_years,
            "discount_rate": inputs.discount_rate,
            "annual_benefit": annual_benefit,
        },
    }

api_valuation(api_calls_per_month: float, revenue_per_call: float, growth_rate: float, useful_life: int, discount_rate: float) -> dict

Value an API as an intangible asset based on call volume and revenue.

Projects annual revenue from API usage with growth, then discounts to present value over the API's useful life.

Formula

Annual Revenue(t) = calls_per_month x 12 x revenue_per_call x (1 + g)^(t-1) Value = sum(Annual Revenue(t) / (1 + r)^t)

Parameters:

Name Type Description Default
api_calls_per_month float

Current monthly API call volume.

required
revenue_per_call float

Revenue generated per API call.

required
growth_rate float

Annual growth rate of API usage (decimal).

required
useful_life int

Expected useful life of the API (years).

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Example

result = api_valuation( ... api_calls_per_month=1_000_000, ... revenue_per_call=0.001, ... growth_rate=0.15, ... useful_life=5, ... discount_rate=0.10, ... ) result["value"] > 0 True

Source code in src/asset_types/technology_valuation.py
def api_valuation(
    api_calls_per_month: float,
    revenue_per_call: float,
    growth_rate: float,
    useful_life: int,
    discount_rate: float,
) -> dict:
    """Value an API as an intangible asset based on call volume and revenue.

    Projects annual revenue from API usage with growth, then discounts
    to present value over the API's useful life.

    Formula:
        Annual Revenue(t) = calls_per_month x 12 x revenue_per_call x (1 + g)^(t-1)
        Value = sum(Annual Revenue(t) / (1 + r)^t)

    Args:
        api_calls_per_month: Current monthly API call volume.
        revenue_per_call: Revenue generated per API call.
        growth_rate: Annual growth rate of API usage (decimal).
        useful_life: Expected useful life of the API (years).
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.

    Example:
        >>> result = api_valuation(
        ...     api_calls_per_month=1_000_000,
        ...     revenue_per_call=0.001,
        ...     growth_rate=0.15,
        ...     useful_life=5,
        ...     discount_rate=0.10,
        ... )
        >>> result["value"] > 0
        True
    """
    inputs = ApiValuationInputs(
        api_calls_per_month=api_calls_per_month,
        revenue_per_call=revenue_per_call,
        growth_rate=growth_rate,
        useful_life=useful_life,
        discount_rate=discount_rate,
    )

    steps: list[str] = []
    annual_calls = inputs.api_calls_per_month * 12
    annual_revenue_year1 = annual_calls * inputs.revenue_per_call

    steps.append(f"Monthly API calls: {inputs.api_calls_per_month:,.0f}")
    steps.append(f"Annual calls: {annual_calls:,.0f}")
    steps.append(f"Revenue per call: ${inputs.revenue_per_call:.4f}")
    steps.append(f"Year 1 annual revenue: {annual_revenue_year1:,.0f}")
    steps.append(f"Growth rate: {inputs.growth_rate:.2%}")
    steps.append(f"Useful life: {inputs.useful_life} years")

    total_pv = 0.0
    for t in range(1, inputs.useful_life + 1):
        revenue = annual_revenue_year1 * (1 + inputs.growth_rate) ** (t - 1)
        pv = present_value(revenue, inputs.discount_rate, t)
        total_pv += pv
        steps.append(
            f"Year {t}: revenue={revenue:,.0f}, PV={pv:,.0f}"
        )

    steps.append(f"Total PV: {total_pv:,.0f}")

    return {
        "value": total_pv,
        "method": "API Income Approach",
        "formula_reference": "V = sum(Calls x 12 x RPC x (1+g)^(t-1) / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "api_calls_per_month": inputs.api_calls_per_month,
            "revenue_per_call": inputs.revenue_per_call,
            "growth_rate": inputs.growth_rate,
            "useful_life": inputs.useful_life,
            "discount_rate": inputs.discount_rate,
            "year1_revenue": annual_revenue_year1,
        },
    }

data_asset_valuation(acquisition_cost: float, quality_score: float, revenue_contribution: float, useful_life: int, discount_rate: float) -> dict

Value a data asset with quality-adjusted revenue contribution.

Quality score (0-1) adjusts the revenue contribution to reflect data completeness, accuracy, and usability.

Parameters:

Name Type Description Default
acquisition_cost float

Cost to acquire the data.

required
quality_score float

Data quality score (0-1).

required
revenue_contribution float

Annual revenue contribution.

required
useful_life int

Useful life in years.

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/technology_valuation.py
def data_asset_valuation(
    acquisition_cost: float,
    quality_score: float,
    revenue_contribution: float,
    useful_life: int,
    discount_rate: float,
) -> dict:
    """Value a data asset with quality-adjusted revenue contribution.

    Quality score (0-1) adjusts the revenue contribution to reflect
    data completeness, accuracy, and usability.

    Args:
        acquisition_cost: Cost to acquire the data.
        quality_score: Data quality score (0-1).
        revenue_contribution: Annual revenue contribution.
        useful_life: Useful life in years.
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = DataAssetInputs(
        acquisition_cost=acquisition_cost,
        quality_score=quality_score,
        revenue_contribution=revenue_contribution,
        useful_life=useful_life,
        discount_rate=discount_rate,
    )

    quality_adjusted_revenue = inputs.revenue_contribution * inputs.quality_score

    steps = [
        f"Acquisition cost: {inputs.acquisition_cost:,.0f}",
        f"Quality score: {inputs.quality_score:.2f}",
        f"Raw revenue contribution: {inputs.revenue_contribution:,.0f}",
        f"Quality-adjusted revenue: {quality_adjusted_revenue:,.0f}",
    ]

    income_value = present_value_of_annuity(
        quality_adjusted_revenue, inputs.discount_rate, inputs.useful_life
    )
    steps.append(f"Income approach PV: {income_value:,.0f}")

    value = max(inputs.acquisition_cost, income_value)
    steps.append(
        f"Final value (max of cost {inputs.acquisition_cost:,.0f} "
        f"and income {income_value:,.0f}): {value:,.0f}"
    )

    return {
        "value": value,
        "method": "Cost-Income Hybrid with Quality Adjustment",
        "formula_reference": "V = max(Cost, sum(Rev x Quality / (1+r)^t))",
        "steps": steps,
        "assumptions": {
            "acquisition_cost": inputs.acquisition_cost,
            "quality_score": inputs.quality_score,
            "revenue_contribution": inputs.revenue_contribution,
            "useful_life": inputs.useful_life,
            "discount_rate": inputs.discount_rate,
        },
    }

developed_technology_valuation(rd_costs: float, life_cycle_stage: str, competitive_advantage: int, discount_rate: float, cash_flow_projections: list[float]) -> dict

Value developed technology with life-cycle risk adjustment.

Combines cost approach (R&D costs as floor) with income approach, where the life cycle stage adjusts the discount rate for risk.

Parameters:

Name Type Description Default
rd_costs float

R&D development costs.

required
life_cycle_stage str

One of "emerging", "growth", "mature", "decline".

required
competitive_advantage int

Competitive advantage period in years.

required
discount_rate float

Base discount rate (decimal).

required
cash_flow_projections list[float]

Projected annual cash flows.

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/technology_valuation.py
def developed_technology_valuation(
    rd_costs: float,
    life_cycle_stage: str,
    competitive_advantage: int,
    discount_rate: float,
    cash_flow_projections: list[float],
) -> dict:
    """Value developed technology with life-cycle risk adjustment.

    Combines cost approach (R&D costs as floor) with income approach,
    where the life cycle stage adjusts the discount rate for risk.

    Args:
        rd_costs: R&D development costs.
        life_cycle_stage: One of "emerging", "growth", "mature", "decline".
        competitive_advantage: Competitive advantage period in years.
        discount_rate: Base discount rate (decimal).
        cash_flow_projections: Projected annual cash flows.

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = DevelopedTechnologyInputs(
        rd_costs=rd_costs,
        life_cycle_stage=life_cycle_stage,
        competitive_advantage=competitive_advantage,
        discount_rate=discount_rate,
        cash_flow_projections=cash_flow_projections,
    )

    # Risk-adjusted discount rate based on life cycle stage
    risk_premium = LIFE_CYCLE_RISK[inputs.life_cycle_stage]
    adjusted_discount_rate = inputs.discount_rate + risk_premium

    steps: list[str] = [
        f"R&D costs (floor): {inputs.rd_costs:,.0f}",
        f"Life cycle stage: {inputs.life_cycle_stage}",
        f"Risk premium: {risk_premium:.2%}",
        f"Adjusted discount rate: {adjusted_discount_rate:.2%}",
    ]

    # PV of cash flows over competitive advantage period
    pv_cash_flows = 0.0
    for t, cf in enumerate(inputs.cash_flow_projections, start=1):
        if t > inputs.competitive_advantage:
            break
        pv = present_value(cf, adjusted_discount_rate, t)
        pv_cash_flows += pv
        steps.append(f"Year {t} CF: {cf:,.0f} -> PV: {pv:,.0f}")

    # Value is max of cost floor and income approach
    value = max(inputs.rd_costs, pv_cash_flows)
    steps.append(
        f"Final value (max of cost {inputs.rd_costs:,.0f} "
        f"and income {pv_cash_flows:,.0f}): {value:,.0f}"
    )

    return {
        "value": value,
        "method": "Cost-Income Hybrid with Life Cycle Risk",
        "formula_reference": "V = max(Cost, sum(CF_t / (1+r+risk)^t))",
        "steps": steps,
        "assumptions": {
            "rd_costs": inputs.rd_costs,
            "life_cycle_stage": inputs.life_cycle_stage,
            "risk_premium": risk_premium,
            "competitive_advantage": inputs.competitive_advantage,
            "base_discount_rate": inputs.discount_rate,
            "adjusted_discount_rate": adjusted_discount_rate,
        },
    }

platform_valuation(network_size: int, network_effects_coefficient: float, revenue_per_user: float, growth_rate: float, discount_rate: float) -> dict

Value a platform incorporating network effects in revenue projection.

Network effects amplify revenue as the user base grows. The coefficient determines the strength of the network effect on per-user revenue.

Parameters:

Name Type Description Default
network_size int

Current network size (number of users).

required
network_effects_coefficient float

Network effects coefficient (0-1).

required
revenue_per_user float

Base revenue per user.

required
growth_rate float

Network growth rate (decimal).

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/technology_valuation.py
def platform_valuation(
    network_size: int,
    network_effects_coefficient: float,
    revenue_per_user: float,
    growth_rate: float,
    discount_rate: float,
) -> dict:
    """Value a platform incorporating network effects in revenue projection.

    Network effects amplify revenue as the user base grows. The coefficient
    determines the strength of the network effect on per-user revenue.

    Args:
        network_size: Current network size (number of users).
        network_effects_coefficient: Network effects coefficient (0-1).
        revenue_per_user: Base revenue per user.
        growth_rate: Network growth rate (decimal).
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = PlatformInputs(
        network_size=network_size,
        network_effects_coefficient=network_effects_coefficient,
        revenue_per_user=revenue_per_user,
        growth_rate=growth_rate,
        discount_rate=discount_rate,
    )

    steps: list[str] = [
        f"Network size: {inputs.network_size:,}",
        f"Network effects coefficient: {inputs.network_effects_coefficient:.2f}",
        f"Base revenue per user: {inputs.revenue_per_user:,.2f}",
        f"Growth rate: {inputs.growth_rate:.2%}",
    ]

    # Project 5 years of cash flows with network effects
    projection_years = 5
    total_pv = 0.0
    current_size = float(inputs.network_size)

    for t in range(1, projection_years + 1):
        # Network grows
        current_size = current_size * (1 + inputs.growth_rate)
        # Network effects boost per-user revenue
        network_boost = 1 + inputs.network_effects_coefficient * (t / projection_years)
        effective_rpu = inputs.revenue_per_user * network_boost
        annual_revenue = current_size * effective_rpu

        pv = present_value(annual_revenue, inputs.discount_rate, t)
        total_pv += pv
        steps.append(
            f"Year {t}: users={current_size:,.0f}, "
            f"RPU={effective_rpu:,.2f}, "
            f"revenue={annual_revenue:,.0f}, PV={pv:,.0f}"
        )

    return {
        "value": total_pv,
        "method": "Network Effects Income Approach",
        "formula_reference": "V = sum(N_t x RPU_t x (1 + NE x t/T) / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "network_size": inputs.network_size,
            "network_effects_coefficient": inputs.network_effects_coefficient,
            "revenue_per_user": inputs.revenue_per_user,
            "growth_rate": inputs.growth_rate,
            "discount_rate": inputs.discount_rate,
            "projection_years": projection_years,
        },
    }

software_valuation(development_cost: float, maintenance_cost: float, user_base: int, revenue_model: dict, useful_life: int, discount_rate: float) -> dict

Value software using cost and income approaches.

Combines replacement cost with PV of net cash flows from the user base.

Parameters:

Name Type Description Default
development_cost float

Cost to develop the software.

required
maintenance_cost float

Annual maintenance cost.

required
user_base int

Current number of users.

required
revenue_model dict

Dict with "type" and "revenue_per_user".

required
useful_life int

Useful life in years.

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/technology_valuation.py
def software_valuation(
    development_cost: float,
    maintenance_cost: float,
    user_base: int,
    revenue_model: dict,
    useful_life: int,
    discount_rate: float,
) -> dict:
    """Value software using cost and income approaches.

    Combines replacement cost with PV of net cash flows from the user base.

    Args:
        development_cost: Cost to develop the software.
        maintenance_cost: Annual maintenance cost.
        user_base: Current number of users.
        revenue_model: Dict with "type" and "revenue_per_user".
        useful_life: Useful life in years.
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = SoftwareInputs(
        development_cost=development_cost,
        maintenance_cost=maintenance_cost,
        user_base=user_base,
        revenue_model=revenue_model,
        useful_life=useful_life,
        discount_rate=discount_rate,
    )

    annual_revenue = inputs.user_base * inputs.revenue_model["revenue_per_user"]
    net_cash_flow = annual_revenue - inputs.maintenance_cost

    steps = [
        f"Development cost: {inputs.development_cost:,.0f}",
        f"User base: {inputs.user_base:,}",
        f"Revenue model: {inputs.revenue_model['type']}",
        f"Revenue per user: {inputs.revenue_model['revenue_per_user']:,.2f}",
        f"Annual revenue: {annual_revenue:,.0f}",
        f"Annual maintenance: {inputs.maintenance_cost:,.0f}",
        f"Net annual cash flow: {net_cash_flow:,.0f}",
    ]

    if net_cash_flow > 0:
        income_value = present_value_of_annuity(
            net_cash_flow, inputs.discount_rate, inputs.useful_life
        )
    else:
        income_value = 0

    steps.append(f"Income approach PV: {income_value:,.0f}")

    # Value is max of cost and income
    value = max(inputs.development_cost, income_value)
    steps.append(
        f"Final value (max of cost {inputs.development_cost:,.0f} "
        f"and income {income_value:,.0f}): {value:,.0f}"
    )

    return {
        "value": value,
        "method": "Cost-Income Hybrid (Software)",
        "formula_reference": "V = max(Cost, sum((Rev - Maint) / (1+r)^t))",
        "steps": steps,
        "assumptions": {
            "development_cost": inputs.development_cost,
            "maintenance_cost": inputs.maintenance_cost,
            "user_base": inputs.user_base,
            "revenue_model_type": inputs.revenue_model["type"],
            "revenue_per_user": inputs.revenue_model["revenue_per_user"],
            "useful_life": inputs.useful_life,
            "discount_rate": inputs.discount_rate,
        },
    }

technology_obsolescence_curve(initial_value: float, obsolescence_rate: float, periods: int) -> dict

Calculate technology value decay over time due to obsolescence.

Models the decline in technology value as newer alternatives emerge. Uses exponential decay: V(t) = V0 x (1 - obsolescence_rate)^t

Parameters:

Name Type Description Default
initial_value float

Initial technology value at t=0.

required
obsolescence_rate float

Annual rate of value decay (0-1, decimal).

required
periods int

Number of periods to project.

required

Returns:

Type Description
dict

Dict with value (remaining value at end), method, formula_reference,

dict

steps (value at each period), and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Example

result = technology_obsolescence_curve(1_000_000, 0.20, 5) result["value"] # ~327,680 327680.0

Source code in src/asset_types/technology_valuation.py
def technology_obsolescence_curve(
    initial_value: float,
    obsolescence_rate: float,
    periods: int,
) -> dict:
    """Calculate technology value decay over time due to obsolescence.

    Models the decline in technology value as newer alternatives emerge.
    Uses exponential decay: V(t) = V0 x (1 - obsolescence_rate)^t

    Args:
        initial_value: Initial technology value at t=0.
        obsolescence_rate: Annual rate of value decay (0-1, decimal).
        periods: Number of periods to project.

    Returns:
        Dict with value (remaining value at end), method, formula_reference,
        steps (value at each period), and assumptions.

    Raises:
        ValueError: If any input is invalid.

    Example:
        >>> result = technology_obsolescence_curve(1_000_000, 0.20, 5)
        >>> result["value"]  # ~327,680
        327680.0
    """
    inputs = TechObsolescenceInputs(
        initial_value=initial_value,
        obsolescence_rate=obsolescence_rate,
        periods=periods,
    )

    steps: list[str] = []
    values: list[float] = []
    current_value = inputs.initial_value

    steps.append(f"Initial value: {inputs.initial_value:,.0f}")
    steps.append(f"Annual obsolescence rate: {inputs.obsolescence_rate:.2%}")
    steps.append(f"Projection periods: {inputs.periods}")

    for t in range(1, inputs.periods + 1):
        current_value = current_value * (1 - inputs.obsolescence_rate)
        values.append(current_value)
        pct_remaining = current_value / inputs.initial_value
        steps.append(
            f"Period {t}: value = {current_value:,.0f} "
            f"({pct_remaining:.1%} of original)"
        )

    return {
        "value": current_value,
        "method": "Technology Obsolescence Curve",
        "formula_reference": "V(t) = V0 x (1 - r)^t",
        "steps": steps,
        "assumptions": {
            "initial_value": inputs.initial_value,
            "obsolescence_rate": inputs.obsolescence_rate,
            "periods": inputs.periods,
            "value_at_each_period": [round(v, 2) for v in values],
            "total_value_lost": round(inputs.initial_value - current_value, 2),
        },
    }

Customer Valuation

customer_valuation

Customer-related asset valuation methods.

Implements valuation for customer relationships, distribution networks, and non-compete agreements.

Classes

BacklogValuationInputs

Bases: BaseModel

Inputs for order backlog valuation.

CLVInputs

Bases: BaseModel

Inputs for customer lifetime value calculation.

ChurnImpactInputs

Bases: BaseModel

Inputs for churn impact analysis.

CustomerRelationshipInputs

Bases: BaseModel

Inputs for customer relationship valuation.

DistributionNetworkInputs

Bases: BaseModel

Inputs for distribution network valuation.

NonCompeteInputs

Bases: BaseModel

Inputs for non-compete agreement valuation.

Functions

backlog_valuation(contract_backlog: list[dict], probability_of_completion: float, discount_rate: float) -> dict

Value order backlog as risk-adjusted present value of contracted revenue.

Each contract in the backlog is discounted to present value and adjusted for the probability of successful completion.

Formula

Value = sum(contract_value x P(completion) / (1 + r)^period)

Parameters:

Name Type Description Default
contract_backlog list[dict]

List of dicts with 'value' (float) and optional 'period' (int, default 1) for each contract.

required
probability_of_completion float

Overall probability contracts will be fulfilled (0-1).

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If backlog is empty or inputs are invalid.

Example

backlog = [ ... {"value": 500_000, "period": 1}, ... {"value": 300_000, "period": 2}, ... ] result = backlog_valuation(backlog, 0.90, 0.10) result["value"] > 0 True

Source code in src/asset_types/customer_valuation.py
def backlog_valuation(
    contract_backlog: list[dict],
    probability_of_completion: float,
    discount_rate: float,
) -> dict:
    """Value order backlog as risk-adjusted present value of contracted revenue.

    Each contract in the backlog is discounted to present value and adjusted
    for the probability of successful completion.

    Formula:
        Value = sum(contract_value x P(completion) / (1 + r)^period)

    Args:
        contract_backlog: List of dicts with 'value' (float) and optional
            'period' (int, default 1) for each contract.
        probability_of_completion: Overall probability contracts will be
            fulfilled (0-1).
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If backlog is empty or inputs are invalid.

    Example:
        >>> backlog = [
        ...     {"value": 500_000, "period": 1},
        ...     {"value": 300_000, "period": 2},
        ... ]
        >>> result = backlog_valuation(backlog, 0.90, 0.10)
        >>> result["value"] > 0
        True
    """
    if not contract_backlog:
        raise ValueError("Contract backlog cannot be empty")

    inputs = BacklogValuationInputs(
        contract_backlog=contract_backlog,
        probability_of_completion=probability_of_completion,
        discount_rate=discount_rate,
    )

    steps: list[str] = []
    total_pv = 0.0
    total_nominal = 0.0

    for i, contract in enumerate(inputs.contract_backlog, start=1):
        value = contract["value"]
        period = contract.get("period", 1)
        total_nominal += value

        expected_value = value * inputs.probability_of_completion
        pv = present_value(expected_value, inputs.discount_rate, period)
        total_pv += pv

        steps.append(
            f"Contract {i}: value={value:,.0f}, period={period}, "
            f"expected={expected_value:,.0f}, PV={pv:,.0f}"
        )

    steps.append(f"Total nominal backlog: {total_nominal:,.0f}")
    steps.append(f"Risk-adjusted PV: {total_pv:,.0f}")

    return {
        "value": total_pv,
        "method": "Order Backlog Risk-Adjusted PV",
        "formula_reference": "V = sum(Value x P(complete) / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "num_contracts": len(inputs.contract_backlog),
            "total_nominal_value": total_nominal,
            "probability_of_completion": inputs.probability_of_completion,
            "discount_rate": inputs.discount_rate,
        },
    }

churn_impact_analysis(current_customers: int, churn_rate_before: float, churn_rate_after: float, revenue_per_customer: float, discount_rate: float) -> dict

Analyze the value impact of a change in customer churn rate.

Compares the present value of the customer base under two churn scenarios over a 5-year projection period.

Formula

Customers(t) = Initial x (1 - churn_rate)^t Revenue(t) = Customers(t) x revenue_per_customer PV = sum(Revenue(t) / (1 + r)^t) Impact = PV(before) - PV(after)

Parameters:

Name Type Description Default
current_customers int

Current number of customers.

required
churn_rate_before float

Annual churn rate before the change (0-1).

required
churn_rate_after float

Annual churn rate after the change (0-1).

required
revenue_per_customer float

Annual revenue per customer.

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value (impact = PV_before - PV_after), method,

dict

formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If churn rates are >= 1 or other inputs are invalid.

Example

result = churn_impact_analysis( ... current_customers=1000, ... churn_rate_before=0.20, ... churn_rate_after=0.15, ... revenue_per_customer=5000, ... discount_rate=0.10, ... ) result["value"] > 0 # reducing churn creates value True

Source code in src/asset_types/customer_valuation.py
def churn_impact_analysis(
    current_customers: int,
    churn_rate_before: float,
    churn_rate_after: float,
    revenue_per_customer: float,
    discount_rate: float,
) -> dict:
    """Analyze the value impact of a change in customer churn rate.

    Compares the present value of the customer base under two churn scenarios
    over a 5-year projection period.

    Formula:
        Customers(t) = Initial x (1 - churn_rate)^t
        Revenue(t) = Customers(t) x revenue_per_customer
        PV = sum(Revenue(t) / (1 + r)^t)
        Impact = PV(before) - PV(after)

    Args:
        current_customers: Current number of customers.
        churn_rate_before: Annual churn rate before the change (0-1).
        churn_rate_after: Annual churn rate after the change (0-1).
        revenue_per_customer: Annual revenue per customer.
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value (impact = PV_before - PV_after), method,
        formula_reference, steps, and assumptions.

    Raises:
        ValueError: If churn rates are >= 1 or other inputs are invalid.

    Example:
        >>> result = churn_impact_analysis(
        ...     current_customers=1000,
        ...     churn_rate_before=0.20,
        ...     churn_rate_after=0.15,
        ...     revenue_per_customer=5000,
        ...     discount_rate=0.10,
        ... )
        >>> result["value"] > 0  # reducing churn creates value
        True
    """
    inputs = ChurnImpactInputs(
        current_customers=current_customers,
        churn_rate_before=churn_rate_before,
        churn_rate_after=churn_rate_after,
        revenue_per_customer=revenue_per_customer,
        discount_rate=discount_rate,
    )

    steps: list[str] = []
    projection_years = 5

    def calc_pv(churn_rate: float) -> tuple[float, list[tuple[int, int, float, float]]]:
        total = 0.0
        yearly_details: list[tuple[int, int, float, float]] = []
        customers = float(inputs.current_customers)
        for t in range(1, projection_years + 1):
            customers = customers * (1 - churn_rate)
            revenue = customers * inputs.revenue_per_customer
            pv = present_value(revenue, inputs.discount_rate, t)
            total += pv
            yearly_details.append((t, int(customers), revenue, pv))
        return total, yearly_details

    pv_before, details_before = calc_pv(inputs.churn_rate_before)
    pv_after, details_after = calc_pv(inputs.churn_rate_after)
    impact = pv_after - pv_before

    steps.append(f"Current customers: {inputs.current_customers:,}")
    steps.append(f"Revenue per customer: {inputs.revenue_per_customer:,.0f}")
    steps.append(f"Projection period: {projection_years} years")
    steps.append("")
    steps.append("Scenario 1 (Before):")
    steps.append(f"  Churn rate: {inputs.churn_rate_before:.2%}")
    for t, cust, rev, pv in details_before:
        steps.append(f"  Year {t}: {cust:,} customers, revenue={rev:,.0f}, PV={pv:,.0f}")
    steps.append(f"  Total PV: {pv_before:,.0f}")
    steps.append("")
    steps.append("Scenario 2 (After):")
    steps.append(f"  Churn rate: {inputs.churn_rate_after:.2%}")
    for t, cust, rev, pv in details_after:
        steps.append(f"  Year {t}: {cust:,} customers, revenue={rev:,.0f}, PV={pv:,.0f}")
    steps.append(f"  Total PV: {pv_after:,.0f}")
    steps.append("")
    steps.append(f"Value impact (PV_after - PV_before): {impact:,.0f}")

    return {
        "value": impact,
        "method": "Churn Impact Analysis",
        "formula_reference": "Impact = PV(churn_after) - PV(churn_before)",
        "steps": steps,
        "assumptions": {
            "current_customers": inputs.current_customers,
            "churn_rate_before": inputs.churn_rate_before,
            "churn_rate_after": inputs.churn_rate_after,
            "revenue_per_customer": inputs.revenue_per_customer,
            "discount_rate": inputs.discount_rate,
            "projection_years": projection_years,
            "pv_before": round(pv_before, 2),
            "pv_after": round(pv_after, 2),
        },
    }

customer_lifetime_value(revenue_per_period: float, retention_rate: float, discount_rate: float, margin: float) -> dict

Calculate customer lifetime value using the infinite horizon CLV formula.

Formula

CLV = margin x revenue_per_period x retention_rate / (1 + discount_rate - retention_rate)

This is the closed-form solution for an infinite-horizon CLV with constant retention and discount rates.

Parameters:

Name Type Description Default
revenue_per_period float

Revenue generated per customer per period.

required
retention_rate float

Probability a customer remains active (0-1, must be < 1).

required
discount_rate float

Discount rate per period (decimal).

required
margin float

Profit margin on revenue (0-1, decimal).

required

Returns:

Type Description
dict

Dict with value (CLV per customer), method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If retention_rate >= 1 or other inputs are invalid.

Example

result = customer_lifetime_value(100, 0.80, 0.10, 0.30) result["value"] # 0.30 * 100 * 0.80 / (1 + 0.10 - 0.80) = 80.0 80.0

Reference

Gupta, S. & Lehmann, D. (2005). Managing Customers as Investments. Wharton School Publishing.

Source code in src/asset_types/customer_valuation.py
def customer_lifetime_value(
    revenue_per_period: float,
    retention_rate: float,
    discount_rate: float,
    margin: float,
) -> dict:
    """Calculate customer lifetime value using the infinite horizon CLV formula.

    Formula:
        CLV = margin x revenue_per_period x retention_rate / (1 + discount_rate - retention_rate)

    This is the closed-form solution for an infinite-horizon CLV with constant
    retention and discount rates.

    Args:
        revenue_per_period: Revenue generated per customer per period.
        retention_rate: Probability a customer remains active (0-1, must be < 1).
        discount_rate: Discount rate per period (decimal).
        margin: Profit margin on revenue (0-1, decimal).

    Returns:
        Dict with value (CLV per customer), method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If retention_rate >= 1 or other inputs are invalid.

    Example:
        >>> result = customer_lifetime_value(100, 0.80, 0.10, 0.30)
        >>> result["value"]  # 0.30 * 100 * 0.80 / (1 + 0.10 - 0.80) = 80.0
        80.0

    Reference:
        Gupta, S. & Lehmann, D. (2005). Managing Customers as Investments.
        Wharton School Publishing.
    """
    inputs = CLVInputs(
        revenue_per_period=revenue_per_period,
        retention_rate=retention_rate,
        discount_rate=discount_rate,
        margin=margin,
    )

    steps: list[str] = []

    profit_per_period = inputs.revenue_per_period * inputs.margin
    denominator = 1 + inputs.discount_rate - inputs.retention_rate

    if denominator <= 0:
        raise ValueError(
            "Discount rate must be greater than retention_rate - 1. "
            f"Got discount_rate={inputs.discount_rate}, retention_rate={inputs.retention_rate}"
        )

    clv = profit_per_period * inputs.retention_rate / denominator

    steps.append(f"Revenue per period: {inputs.revenue_per_period:,.2f}")
    steps.append(f"Profit margin: {inputs.margin:.2%}")
    steps.append(f"Profit per period: {profit_per_period:,.2f}")
    steps.append(f"Retention rate: {inputs.retention_rate:.2%}")
    steps.append(f"Discount rate: {inputs.discount_rate:.2%}")
    steps.append(f"Denominator (1+r-retention): {denominator:.4f}")
    steps.append(f"CLV: {clv:,.2f}")

    return {
        "value": clv,
        "method": "Customer Lifetime Value (Infinite Horizon)",
        "formula_reference": "CLV = margin x RPP x r / (1 + d - r)",
        "steps": steps,
        "assumptions": {
            "revenue_per_period": inputs.revenue_per_period,
            "retention_rate": inputs.retention_rate,
            "discount_rate": inputs.discount_rate,
            "margin": inputs.margin,
            "profit_per_period": profit_per_period,
        },
    }

customer_relationship_valuation(customer_count: int, avg_revenue_per_customer: float, retention_rate: float, profit_margin: float, discount_rate: float, projection_period: int) -> dict

Value customer relationships with multi-period cash flow and attrition.

Projects declining customer base over time using retention rate, calculates profit from remaining customers, and discounts to present value.

Parameters:

Name Type Description Default
customer_count int

Initial number of customers.

required
avg_revenue_per_customer float

Average annual revenue per customer.

required
retention_rate float

Annual customer retention rate (0-1).

required
profit_margin float

Profit margin (decimal).

required
discount_rate float

Discount rate (decimal).

required
projection_period int

Projection period in years.

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/customer_valuation.py
def customer_relationship_valuation(
    customer_count: int,
    avg_revenue_per_customer: float,
    retention_rate: float,
    profit_margin: float,
    discount_rate: float,
    projection_period: int,
) -> dict:
    """Value customer relationships with multi-period cash flow and attrition.

    Projects declining customer base over time using retention rate,
    calculates profit from remaining customers, and discounts to present value.

    Args:
        customer_count: Initial number of customers.
        avg_revenue_per_customer: Average annual revenue per customer.
        retention_rate: Annual customer retention rate (0-1).
        profit_margin: Profit margin (decimal).
        discount_rate: Discount rate (decimal).
        projection_period: Projection period in years.

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = CustomerRelationshipInputs(
        customer_count=customer_count,
        avg_revenue_per_customer=avg_revenue_per_customer,
        retention_rate=retention_rate,
        profit_margin=profit_margin,
        discount_rate=discount_rate,
        projection_period=projection_period,
    )

    steps: list[str] = [
        f"Initial customers: {inputs.customer_count:,}",
        f"Revenue per customer: {inputs.avg_revenue_per_customer:,.0f}",
        f"Retention rate: {inputs.retention_rate:.2%}",
        f"Profit margin: {inputs.profit_margin:.2%}",
    ]

    total_pv = 0.0
    current_customers = float(inputs.customer_count)

    for t in range(1, inputs.projection_period + 1):
        # Attrition: customers decay by retention rate
        current_customers = current_customers * inputs.retention_rate
        revenue = current_customers * inputs.avg_revenue_per_customer
        profit = revenue * inputs.profit_margin
        pv = present_value(profit, inputs.discount_rate, t)
        total_pv += pv
        steps.append(
            f"Year {t}: customers={current_customers:,.0f}, "
            f"profit={profit:,.0f}, PV={pv:,.0f}"
        )

    return {
        "value": total_pv,
        "method": "Multi-Period Customer Cash Flow with Attrition",
        "formula_reference": "V = sum(C0 x r^t x RPU x PM / (1+d)^t)",
        "steps": steps,
        "assumptions": {
            "customer_count": inputs.customer_count,
            "avg_revenue_per_customer": inputs.avg_revenue_per_customer,
            "retention_rate": inputs.retention_rate,
            "profit_margin": inputs.profit_margin,
            "discount_rate": inputs.discount_rate,
            "projection_period": inputs.projection_period,
        },
    }

distribution_network_valuation(channel_count: int, revenue_per_channel: float, channel_margin: float, useful_life: int, discount_rate: float) -> dict

Value a distribution network based on channel profitability.

Calculates PV of expected profits from distribution channels over the network's useful life.

Parameters:

Name Type Description Default
channel_count int

Number of distribution channels.

required
revenue_per_channel float

Annual revenue per channel.

required
channel_margin float

Profit margin per channel (decimal).

required
useful_life int

Useful life in years.

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/customer_valuation.py
def distribution_network_valuation(
    channel_count: int,
    revenue_per_channel: float,
    channel_margin: float,
    useful_life: int,
    discount_rate: float,
) -> dict:
    """Value a distribution network based on channel profitability.

    Calculates PV of expected profits from distribution channels over
    the network's useful life.

    Args:
        channel_count: Number of distribution channels.
        revenue_per_channel: Annual revenue per channel.
        channel_margin: Profit margin per channel (decimal).
        useful_life: Useful life in years.
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = DistributionNetworkInputs(
        channel_count=channel_count,
        revenue_per_channel=revenue_per_channel,
        channel_margin=channel_margin,
        useful_life=useful_life,
        discount_rate=discount_rate,
    )

    annual_profit = (
        inputs.channel_count
        * inputs.revenue_per_channel
        * inputs.channel_margin
    )

    # PV of annuity
    pv = 0.0
    for t in range(1, inputs.useful_life + 1):
        pv += present_value(annual_profit, inputs.discount_rate, t)

    steps = [
        f"Channel count: {inputs.channel_count:,}",
        f"Revenue per channel: {inputs.revenue_per_channel:,.0f}",
        f"Channel margin: {inputs.channel_margin:.2%}",
        f"Annual profit: {annual_profit:,.0f}",
        f"Useful life: {inputs.useful_life} years",
        f"Discount rate: {inputs.discount_rate:.2%}",
        f"PV of channel profits: {pv:,.0f}",
    ]

    return {
        "value": pv,
        "method": "Distribution Network Income Approach",
        "formula_reference": "V = sum(Channels x Rev/Ch x Margin / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "channel_count": inputs.channel_count,
            "revenue_per_channel": inputs.revenue_per_channel,
            "channel_margin": inputs.channel_margin,
            "useful_life": inputs.useful_life,
            "discount_rate": inputs.discount_rate,
        },
    }

non_compete_valuation(protected_revenue: float, profit_margin: float, term: int, enforcement_probability: float, discount_rate: float) -> dict

Value a non-compete agreement based on protected profits.

Values the expected profit stream protected by the non-compete, adjusted for the probability of successful enforcement.

Parameters:

Name Type Description Default
protected_revenue float

Annual revenue protected by the agreement.

required
profit_margin float

Profit margin on protected revenue (decimal).

required
term int

Agreement term in years.

required
enforcement_probability float

Probability of enforcement (0-1).

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/customer_valuation.py
def non_compete_valuation(
    protected_revenue: float,
    profit_margin: float,
    term: int,
    enforcement_probability: float,
    discount_rate: float,
) -> dict:
    """Value a non-compete agreement based on protected profits.

    Values the expected profit stream protected by the non-compete,
    adjusted for the probability of successful enforcement.

    Args:
        protected_revenue: Annual revenue protected by the agreement.
        profit_margin: Profit margin on protected revenue (decimal).
        term: Agreement term in years.
        enforcement_probability: Probability of enforcement (0-1).
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = NonCompeteInputs(
        protected_revenue=protected_revenue,
        profit_margin=profit_margin,
        term=term,
        enforcement_probability=enforcement_probability,
        discount_rate=discount_rate,
    )

    annual_profit = inputs.protected_revenue * inputs.profit_margin

    steps: list[str] = [
        f"Protected revenue: {inputs.protected_revenue:,.0f}",
        f"Profit margin: {inputs.profit_margin:.2%}",
        f"Annual protected profit: {annual_profit:,.0f}",
        f"Term: {inputs.term} years",
        f"Enforcement probability: {inputs.enforcement_probability:.2%}",
    ]

    # PV of protected profits adjusted for enforcement risk
    total_pv = 0.0
    for t in range(1, inputs.term + 1):
        expected_profit = annual_profit * inputs.enforcement_probability
        pv = present_value(expected_profit, inputs.discount_rate, t)
        total_pv += pv

    steps.append(f"PV of protected profits: {total_pv:,.0f}")

    return {
        "value": total_pv,
        "method": "Non-Compete Income Approach with Enforcement Risk",
        "formula_reference": "V = sum(Rev x PM x P(enforce) / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "protected_revenue": inputs.protected_revenue,
            "profit_margin": inputs.profit_margin,
            "term": inputs.term,
            "enforcement_probability": inputs.enforcement_probability,
            "discount_rate": inputs.discount_rate,
        },
    }

Human Capital

human_capital

Human capital valuation methods.

Implements valuation for assembled workforce and key person dependencies.

Classes

AssembledWorkforceInputs

Bases: BaseModel

Inputs for assembled workforce valuation.

KeyPersonInputs

Bases: BaseModel

Inputs for key person valuation.

Functions

assembled_workforce_valuation(employee_count: int, avg_replacement_cost: float, training_cost: float, productivity_factor: float, attrition_rate: float) -> dict

Value an assembled workforce using replacement cost approach.

The value reflects the cost savings from having a trained, productive workforce versus hiring and training new employees. Accounts for attrition over a standard ramp-up period.

Parameters:

Name Type Description Default
employee_count int

Number of employees.

required
avg_replacement_cost float

Average cost to replace one employee.

required
training_cost float

Training cost per employee.

required
productivity_factor float

Productivity of new hires vs assembled workforce (0-1).

required
attrition_rate float

Annual attrition rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/human_capital.py
def assembled_workforce_valuation(
    employee_count: int,
    avg_replacement_cost: float,
    training_cost: float,
    productivity_factor: float,
    attrition_rate: float,
) -> dict:
    """Value an assembled workforce using replacement cost approach.

    The value reflects the cost savings from having a trained, productive
    workforce versus hiring and training new employees. Accounts for
    attrition over a standard ramp-up period.

    Args:
        employee_count: Number of employees.
        avg_replacement_cost: Average cost to replace one employee.
        training_cost: Training cost per employee.
        productivity_factor: Productivity of new hires vs assembled workforce (0-1).
        attrition_rate: Annual attrition rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = AssembledWorkforceInputs(
        employee_count=employee_count,
        avg_replacement_cost=avg_replacement_cost,
        training_cost=training_cost,
        productivity_factor=productivity_factor,
        attrition_rate=attrition_rate,
    )

    steps: list[str] = [
        f"Employee count: {inputs.employee_count:,}",
        f"Avg replacement cost: {inputs.avg_replacement_cost:,.0f}",
        f"Training cost per employee: {inputs.training_cost:,.0f}",
        f"Productivity factor: {inputs.productivity_factor:.2f}",
        f"Attrition rate: {inputs.attrition_rate:.2%}",
    ]

    # Total replacement cost (hiring + training)
    total_replacement = inputs.employee_count * (
        inputs.avg_replacement_cost + inputs.training_cost
    )

    # Productivity loss during ramp-up (assume 1 year)
    productivity_loss = total_replacement * (1 - inputs.productivity_factor)

    # Attrition-adjusted value (workforce decays over time)
    # Use 3-year horizon for assembled workforce value
    horizon = 3
    total_pv = 0.0
    remaining_employees = float(inputs.employee_count)

    for t in range(1, horizon + 1):
        remaining_employees = remaining_employees * (1 - inputs.attrition_rate)
        annual_value = remaining_employees * (
            inputs.avg_replacement_cost + inputs.training_cost
        )
        pv = present_value(annual_value, 0.10, t)  # 10% standard discount
        total_pv += pv
        steps.append(
            f"Year {t}: employees={remaining_employees:,.0f}, "
            f"value={annual_value:,.0f}, PV={pv:,.0f}"
        )

    # Final value is the PV of replacement costs over horizon
    value = total_pv

    steps.append(f"Total replacement cost: {total_replacement:,.0f}")
    steps.append(f"Productivity loss factor: {productivity_loss:,.0f}")
    steps.append(f"Final assembled workforce value: {value:,.0f}")

    return {
        "value": value,
        "method": "Replacement Cost with Attrition Adjustment",
        "formula_reference": "V = sum(N_t x (RC + TC) / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "employee_count": inputs.employee_count,
            "avg_replacement_cost": inputs.avg_replacement_cost,
            "training_cost": inputs.training_cost,
            "productivity_factor": inputs.productivity_factor,
            "attrition_rate": inputs.attrition_rate,
            "horizon_years": horizon,
        },
    }

key_person_value(revenue_contribution: float, replacement_cost: float, departure_probability: float, discount_rate: float) -> dict

Value a key person based on revenue contribution and replacement risk.

Combines the cost to replace the person with the PV of their revenue contribution, adjusted for departure probability over a standard horizon.

Parameters:

Name Type Description Default
revenue_contribution float

Annual revenue attributable to the person.

required
replacement_cost float

Cost to find and train a replacement.

required
departure_probability float

Annual probability of departure (0-1).

required
discount_rate float

Discount rate (decimal).

required

Returns:

Type Description
dict

Dict with value, method, formula_reference, steps, and assumptions.

Raises:

Type Description
ValueError

If any input is invalid.

Source code in src/asset_types/human_capital.py
def key_person_value(
    revenue_contribution: float,
    replacement_cost: float,
    departure_probability: float,
    discount_rate: float,
) -> dict:
    """Value a key person based on revenue contribution and replacement risk.

    Combines the cost to replace the person with the PV of their revenue
    contribution, adjusted for departure probability over a standard horizon.

    Args:
        revenue_contribution: Annual revenue attributable to the person.
        replacement_cost: Cost to find and train a replacement.
        departure_probability: Annual probability of departure (0-1).
        discount_rate: Discount rate (decimal).

    Returns:
        Dict with value, method, formula_reference, steps, and assumptions.

    Raises:
        ValueError: If any input is invalid.
    """
    inputs = KeyPersonInputs(
        revenue_contribution=revenue_contribution,
        replacement_cost=replacement_cost,
        departure_probability=departure_probability,
        discount_rate=discount_rate,
    )

    steps: list[str] = [
        f"Revenue contribution: {inputs.revenue_contribution:,.0f}",
        f"Replacement cost: {inputs.replacement_cost:,.0f}",
        f"Departure probability: {inputs.departure_probability:.2%}",
        f"Discount rate: {inputs.discount_rate:.2%}",
    ]

    # 3-year horizon for key person value
    horizon = 3
    total_pv = 0.0

    for t in range(1, horizon + 1):
        # Probability person is still present
        survival_prob = (1 - inputs.departure_probability) ** t
        expected_revenue = inputs.revenue_contribution * survival_prob
        pv = present_value(expected_revenue, inputs.discount_rate, t)
        total_pv += pv
        steps.append(
            f"Year {t}: survival={survival_prob:.2%}, "
            f"expected rev={expected_revenue:,.0f}, PV={pv:,.0f}"
        )

    # Value is PV of revenue contribution plus replacement cost
    value = total_pv + inputs.replacement_cost
    steps.append(
        f"PV of revenue contribution: {total_pv:,.0f}"
    )
    steps.append(f"Final value (PV + replacement cost): {value:,.0f}")

    return {
        "value": value,
        "method": "Key Person Income and Replacement Cost",
        "formula_reference": "V = RC + sum(Rev x P(survival)^t / (1+r)^t)",
        "steps": steps,
        "assumptions": {
            "revenue_contribution": inputs.revenue_contribution,
            "replacement_cost": inputs.replacement_cost,
            "departure_probability": inputs.departure_probability,
            "discount_rate": inputs.discount_rate,
            "horizon_years": horizon,
        },
    }