#!/usr/bin/env python3 """PolyCode Simulator v0.1 — single-run demo.""" from __future__ import annotations import json import random from dataclasses import asdict, dataclass from datetime import UTC, datetime from pathlib import Path from strategies import ( LenientVoter, Proposal, RandomInvestor, ThresholdInvestor, ValueVoter, ) @dataclass class RoundResult: round: int proposal_id: int funded: float approved: bool effort: float value: float @dataclass class RunSummary: accepted: int rejected: int total_funded: float acceptance_rate: float rounds: list[RoundResult] def load_params(path: Path | None = None) -> dict: params_path = path or Path(__file__).with_name("params_default.json") return json.loads(params_path.read_text(encoding="utf-8")) def make_proposal(round_idx: int, rng: random.Random) -> Proposal: effort = rng.uniform(30, 120) value = effort * rng.uniform(0.7, 1.4) return Proposal(id=round_idx, effort=round(effort, 2), value=round(value, 2)) def run_simulation(params: dict | None = None) -> RunSummary: params = params or load_params() rng = random.Random(params.get("random_seed", 42)) investors = [ThresholdInvestor(), RandomInvestor(), RandomInvestor()] voters = [ValueVoter(), ValueVoter(), LenientVoter(), LenientVoter(), ValueVoter()] capital = float(params["initial_capital"]) threshold = float(params["funding_threshold"]) rounds: list[RoundResult] = [] accepted = rejected = 0 total_funded = 0.0 for i in range(int(params["rounds"])): proposal = make_proposal(i + 1, rng) funded = 0.0 for investor in investors: stake = min(capital, capital / len(investors)) amount = investor.allocate(proposal, funded, stake, rng) amount = min(amount, capital) funded += amount capital -= amount if funded >= threshold: break votes = sum(1 for voter in voters if voter.approve(proposal, funded, rng)) approved = votes > len(voters) / 2 and funded >= proposal.effort if approved: accepted += 1 else: rejected += 1 capital += funded * 0.5 total_funded += funded rounds.append( RoundResult( round=i + 1, proposal_id=proposal.id, funded=round(funded, 2), approved=approved, effort=proposal.effort, value=proposal.value, ) ) total = accepted + rejected return RunSummary( accepted=accepted, rejected=rejected, total_funded=round(total_funded, 2), acceptance_rate=round(accepted / total, 3) if total else 0.0, rounds=rounds, ) def write_run(summary: RunSummary, params: dict, out_dir: Path | None = None) -> Path: out_dir = out_dir or Path("runs") out_dir.mkdir(exist_ok=True) stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ") path = out_dir / f"run-{stamp}.json" payload = { "params": params, "summary": { "accepted": summary.accepted, "rejected": summary.rejected, "total_funded": summary.total_funded, "acceptance_rate": summary.acceptance_rate, }, "rounds": [asdict(r) for r in summary.rounds], } path.write_text(json.dumps(payload, indent=2), encoding="utf-8") return path def main() -> None: params = load_params() summary = run_simulation(params) out = write_run(summary, params) print(f"PolyCode demo run complete: {summary.accepted} accepted, {summary.rejected} rejected") print(f"acceptance_rate={summary.acceptance_rate} total_funded={summary.total_funded}") print(f"output: {out}") if __name__ == "__main__": main()