Implement customer-tuning solver and close WP-0006

This commit is contained in:
codex
2026-07-02 23:46:58 +02:00
parent 386c8a46fe
commit 124ad48720
10 changed files with 997 additions and 9 deletions

View File

@@ -0,0 +1,38 @@
{
"version": 1,
"requests": [
{
"id": "small-team-lower-usage-price",
"name": "Small team lower usage price",
"profile_id": "small-team",
"model_id": "membership-plus-overage",
"preference": "lower_usage_price",
"approval_mode": "self_serve_only",
"selected_tunables": {
"included_tokens": "50000",
"contract_duration_months": 3
},
"search_policy": {
"min_usage_unit_price": "0.0005",
"usage_unit_price_step": "0.0001"
}
},
{
"id": "small-team-high-included-bundle",
"name": "Small team high included bundle",
"profile_id": "small-team",
"model_id": "membership-plus-overage",
"preference": "lower_usage_price",
"approval_mode": "self_serve_only",
"selected_tunables": {
"included_tokens": "150000",
"contract_duration_months": 3
},
"search_policy": {
"min_usage_unit_price": "0.0005",
"usage_unit_price_step": "0.0001"
}
}
],
"notes": "Customer-tuning pilot requests for the Coulomb hybrid overage prototype."
}

View File

@@ -18,6 +18,7 @@ from .load import (
load_payment_records,
load_pricing_models,
load_product,
load_tuning_requests,
load_value_range,
)
from .allocation import build_cost_allocation
@@ -27,6 +28,7 @@ from .membership_analytics import build_membership_analytics
from .pricing_context import build_cost_floor, build_market_price_view, build_value_range_view
from .recommendations import build_pricing_recommendations
from .simulator import build_pricing_simulations
from .tuning import build_customer_tuning_pilot
from .usage import build_usage_summary, load_usage_records
@@ -90,6 +92,7 @@ def build_dashboard_payload(data_dir: Path | None = None, period: str | None = N
usage_records = load_usage_records(root)
usage_summary = build_usage_summary(usage_records, target_period)
ltv_scenarios = load_ltv_scenarios(root)
tuning_requests = load_tuning_requests(root)
cost_floor = build_cost_floor(snapshot, models)
value_range = build_value_range_view(value_range_raw, snapshot, product, models)
market_price = build_market_price_view(market_raw)
@@ -102,6 +105,13 @@ def build_dashboard_payload(data_dir: Path | None = None, period: str | None = N
usage_records=usage_records,
scenario_catalog=ltv_scenarios,
)
customer_tuning = build_customer_tuning_pilot(
snapshot,
models,
usage_records,
ltv_scenarios,
tuning_requests,
)
boundary_validation = build_boundary_validation(snapshot, models, usage_records)
credit_wallets = load_credit_wallets(root)
credit_summary = build_credit_summary(
@@ -135,6 +145,7 @@ def build_dashboard_payload(data_dir: Path | None = None, period: str | None = N
"usage": usage_summary,
"cost_allocation": cost_allocation,
"pricing_simulations": simulations,
"customer_tuning": customer_tuning,
"boundary_validation": boundary_validation,
"credit_wallets": credit_summary,
"recommendations": recommendations,

View File

@@ -119,6 +119,13 @@ def load_ltv_scenarios(data_dir: Path | None = None) -> dict:
return _read_json((data_dir or default_data_dir()) / "ltv_scenarios.json")
def load_tuning_requests(data_dir: Path | None = None) -> dict:
path = (data_dir or default_data_dir()) / "tuning_requests.json"
if not path.exists():
return {}
return _read_json(path)
def load_membership(data_dir: Path | None = None) -> list[MembershipRecord]:
raw = _read_json((data_dir or default_data_dir()) / "membership.json")
return [

View File

@@ -0,0 +1,178 @@
from __future__ import annotations
from decimal import Decimal
from typing import Any
from ._repo_root import ensure_repo_root_on_syspath
from .boundary import build_boundary_policy
from .ltv import _configuration, _ltv_policy, _profile, _usage_unit_cost
from .models import EconomicsSnapshot, PricingModel
ensure_repo_root_on_syspath()
from adaptive_pricing_core.customer_tuning import ( # noqa: E402
CustomerTuningRequest,
UsagePriceSearchPolicy,
solve_customer_tuning,
)
def _serialize(value: Any) -> Any:
if isinstance(value, Decimal):
return str(value)
if hasattr(value, "__dataclass_fields__"):
return {key: _serialize(getattr(value, key)) for key in value.__dataclass_fields__}
if isinstance(value, tuple):
return [_serialize(item) for item in value]
if isinstance(value, list):
return [_serialize(item) for item in value]
if isinstance(value, dict):
return {key: _serialize(item) for key, item in value.items()}
return value
def _decimal(value: Decimal | str | int | float | None) -> Decimal | None:
if value in (None, ""):
return None
return Decimal(str(value))
def _customer_tunable_keys(model: PricingModel) -> set[str]:
return {
parameter.key
for parameter in model.tunable_parameters
if parameter.parameter_class == "customer_tunable"
}
def _validate_request_surface(model: PricingModel, selected_tunables: dict[str, Any]) -> tuple[str, ...]:
tunable_keys = _customer_tunable_keys(model)
issues: list[str] = []
for key in selected_tunables:
if key not in tunable_keys:
issues.append(f"{key} is not customer-tunable on {model.id}")
return tuple(issues)
def _request(raw: dict[str, Any]) -> CustomerTuningRequest:
selected = raw.get("selected_tunables", {})
return CustomerTuningRequest(
included_units=_decimal(selected.get("included_tokens")),
contract_duration_months=(
int(selected["contract_duration_months"])
if selected.get("contract_duration_months") is not None
else None
),
minimum_monthly_turnover=_decimal(selected.get("minimum_monthly_turnover")) or Decimal("0"),
prepaid_amount=_decimal(selected.get("prepaid_amount")) or Decimal("0"),
guaranteed_platform_fee=_decimal(selected.get("guaranteed_platform_fee")) or Decimal("0"),
customer_funded_onboarding=_decimal(selected.get("customer_funded_onboarding")) or Decimal("0"),
reduced_cancellation_flexibility=raw.get("reduced_cancellation_flexibility"),
preference=raw.get("preference", "lower_usage_price"),
approval_mode=raw.get("approval_mode", "self_serve_only"),
)
def _search_policy(raw: dict[str, Any]) -> UsagePriceSearchPolicy | None:
if not raw:
return None
return UsagePriceSearchPolicy(
min_usage_unit_price=_decimal(raw.get("min_usage_unit_price")),
max_usage_unit_price=_decimal(raw.get("max_usage_unit_price")),
usage_unit_price_step=_decimal(raw.get("usage_unit_price_step")) or Decimal("0.0001"),
max_usage_price_multiplier=_decimal(raw.get("max_usage_price_multiplier")) or Decimal("4"),
)
def build_customer_tuning_pilot(
snapshot: EconomicsSnapshot,
models: list[PricingModel],
usage_records: list[dict[str, Any]],
scenario_catalog: dict[str, Any],
request_catalog: dict[str, Any] | None = None,
) -> dict[str, Any]:
request_catalog = request_catalog or {}
if not request_catalog.get("requests"):
return {
"period": snapshot.period,
"currency": snapshot.currency,
"requests": [],
"notes": [
"No customer-tuning pilot requests are configured for this observatory deployment.",
],
}
profile_index = {item["id"]: _profile(item) for item in scenario_catalog.get("profiles", [])}
model_index = {model.id: model for model in models if model.status in ("active", "candidate")}
policy = _ltv_policy(scenario_catalog)
boundary_policy = build_boundary_policy(snapshot)
observed_usage_unit_cost = _usage_unit_cost(usage_records, snapshot.period)
results = []
for raw_request in request_catalog.get("requests", []):
model = model_index[raw_request["model_id"]]
profile = profile_index[raw_request["profile_id"]]
selected_tunables = raw_request.get("selected_tunables", {})
issues = _validate_request_surface(model, selected_tunables)
if issues:
results.append(
{
"id": raw_request["id"],
"name": raw_request["name"],
"decision": "rejected",
"issues": list(issues),
"profile_id": profile.id,
"model_id": model.id,
}
)
continue
base_configuration = _configuration(model, profile, snapshot, observed_usage_unit_cost)
reference_configurations = [
_configuration(candidate, profile, snapshot, observed_usage_unit_cost)
for candidate in models
if candidate.status in ("active", "candidate")
]
outcome = solve_customer_tuning(
base_configuration,
reference_configurations,
profile,
boundary_policy,
policy,
_request(raw_request),
search_policy=_search_policy(raw_request.get("search_policy", {})),
)
results.append(
{
"id": raw_request["id"],
"name": raw_request["name"],
"profile_id": profile.id,
"profile_name": profile.name,
"model_id": model.id,
"model_name": model.name,
"selected_tunables": selected_tunables,
"result": outcome,
}
)
accepted = [
item["id"]
for item in results
if item.get("result") is not None and getattr(item["result"], "decision", None) == "accepted"
]
return _serialize(
{
"period": snapshot.period,
"currency": snapshot.currency,
"request_count": len(results),
"accepted_request_ids": accepted,
"requests": results,
"notes": [
request_catalog.get("notes", ""),
"Pilot requests map product-level tunables into canonical pricing configuration fields before running the generic solver.",
"For Coulomb's hybrid prototype, selected included token values are treated as total package allowances rather than per-seat multipliers.",
],
}
)

View File

@@ -28,6 +28,8 @@ def test_dashboard_payload_contains_live_ledger_totals() -> None:
assert payload["pricing_simulations"]["primary_profile_id"] == "solo-builder"
assert payload["pricing_simulations"]["required_improvement_factor"] == "1.05"
assert payload["pricing_simulations"]["reference_model_id"] is not None
assert payload["customer_tuning"]["request_count"] == 2
assert payload["customer_tuning"]["accepted_request_ids"] == ["small-team-lower-usage-price"]
assert len(payload["boundary_validation"]["model_results"]) == 3
assert payload["boundary_validation"]["policy"]["target_margin_pct"] == "15"
assert any(

View File

@@ -0,0 +1,149 @@
from __future__ import annotations
from decimal import Decimal
from pathlib import Path
from adaptive_pricing_core.customer_tuning import CustomerTuningRequest, solve_customer_tuning
from observatory.boundary import build_boundary_policy
from observatory.economics import build_snapshot
from observatory.load import (
load_ltv_scenarios,
load_membership,
load_monthly_ledger,
load_payment_records,
load_pricing_models,
load_product,
)
from observatory.ltv import _configuration, _ltv_policy, _profile, _usage_unit_cost
from observatory.tuning import build_customer_tuning_pilot
from observatory.usage import load_usage_records
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
def _scenario_inputs(profile_id: str = "small-team"):
product = load_product(DATA_DIR)
models = load_pricing_models(DATA_DIR)
members = load_membership(DATA_DIR)
payments = load_payment_records(DATA_DIR)
ledger = load_monthly_ledger(DATA_DIR)
snapshot = build_snapshot("2026-06", product, models, members, payments, ledger)
usage_records = load_usage_records(DATA_DIR)
scenario_catalog = load_ltv_scenarios(DATA_DIR)
profile = _profile(next(item for item in scenario_catalog["profiles"] if item["id"] == profile_id))
observed_usage_unit_cost = _usage_unit_cost(usage_records, snapshot.period)
return snapshot, models, usage_records, scenario_catalog, profile, observed_usage_unit_cost
def test_lower_usage_price_request_can_stay_seller_safe() -> None:
(
snapshot,
models,
usage_records,
scenario_catalog,
profile,
observed_usage_unit_cost,
) = _scenario_inputs()
model = next(item for item in models if item.id == "membership-plus-overage")
outcome = solve_customer_tuning(
_configuration(model, profile, snapshot, observed_usage_unit_cost),
[
_configuration(candidate, profile, snapshot, observed_usage_unit_cost)
for candidate in models
if candidate.status in ("active", "candidate")
],
profile,
build_boundary_policy(snapshot),
_ltv_policy(scenario_catalog),
CustomerTuningRequest(
included_units=Decimal("50000"),
contract_duration_months=3,
preference="lower_usage_price",
approval_mode="self_serve_only",
),
)
assert outcome.decision == "accepted"
assert outcome.reference_model_id == "flat-899-eur-monthly"
assert outcome.passes_required_improvement is True
assert outcome.solved_usage_unit_price < Decimal("0.002")
assert "lower_included_usage" in outcome.tradeoffs
assert "longer_contract_duration" in outcome.tradeoffs
def test_high_included_request_is_rejected_for_self_serve() -> None:
(
snapshot,
models,
_usage_records,
scenario_catalog,
profile,
observed_usage_unit_cost,
) = _scenario_inputs()
model = next(item for item in models if item.id == "membership-plus-overage")
outcome = solve_customer_tuning(
_configuration(model, profile, snapshot, observed_usage_unit_cost),
[
_configuration(candidate, profile, snapshot, observed_usage_unit_cost)
for candidate in models
if candidate.status in ("active", "candidate")
],
profile,
build_boundary_policy(snapshot),
_ltv_policy(scenario_catalog),
CustomerTuningRequest(
included_units=Decimal("150000"),
contract_duration_months=3,
preference="lower_usage_price",
approval_mode="self_serve_only",
),
)
assert outcome.decision == "rejected"
assert outcome.passes_required_improvement is True
assert any(
constraint.id == "discount-exposure-limit"
for constraint in outcome.binding_constraints
)
def test_customer_tuning_pilot_surfaces_accepted_and_rejected_requests() -> None:
snapshot, models, usage_records, scenario_catalog, _profile_data, _usage_unit_cost_value = _scenario_inputs()
pilot = build_customer_tuning_pilot(
snapshot,
models,
usage_records,
scenario_catalog,
{
"requests": [
{
"id": "accepted",
"name": "Accepted",
"profile_id": "small-team",
"model_id": "membership-plus-overage",
"preference": "lower_usage_price",
"approval_mode": "self_serve_only",
"selected_tunables": {
"included_tokens": "50000",
"contract_duration_months": 3,
},
},
{
"id": "rejected",
"name": "Rejected",
"profile_id": "small-team",
"model_id": "membership-plus-overage",
"preference": "lower_usage_price",
"approval_mode": "self_serve_only",
"selected_tunables": {
"included_tokens": "150000",
"contract_duration_months": 3,
},
},
]
},
)
assert pilot["request_count"] == 2
assert pilot["accepted_request_ids"] == ["accepted"]
assert {item["result"]["decision"] for item in pilot["requests"]} == {"accepted", "rejected"}