"""Multi-base chains: combine several independently-simulated plans into one balance sheet. Each base is simulated on its own (puga.simulate.simulate), which prices every flow at market — that already gives each base's own correct profit whether or not a material actually leaves the base. For a material transferred between two bases in the SAME chain (e.g. LST mined at Nike, consumed at Deimos), the two bases' market-priced flows cancel out in the combined total to within the transferred quantity (base A "sells" it, base B "buys" it, at the same price basis) — so the combined total needs only ONE correction: the real freight cost of physically moving it, which isn't in either base's isolated numbers. Leftover surplus (more produced than a partner needs) is still validly sold at market by the producing base; leftover deficit is still validly bought. """ from dataclasses import dataclass, field from . import fio from .simulate import simulate @dataclass class BaseNode: name: str plan: dict recipes: list buildings: list resources: list fertility: float price: callable faction: str | None = None permits: tuple[float, float] = (1, 2) built: dict = field(default_factory=dict) @dataclass class Transfer: material: str src: str # producing base's name (key in the `bases` dict) dst: str # consuming base's name trip_cost: float = 9250.0 # AIC per round trip; PLACEHOLDER until a real route/fuel model exists cargo: float = 500.0 # t or m3 per trip def combine(bases: dict[str, BaseNode], transfers: list[Transfer]) -> dict: mat = {m["Ticker"]: m for m in fio.materials()} results = {name: simulate(b.plan, b.recipes, b.buildings, b.resources, b.fertility, b.price, b.faction, b.permits, built=b.built) for name, b in bases.items()} ledger = [] total_profit = sum(r["profit"] for r in results.values()) for t in transfers: prod = results[t.src]["flows"].get(t.material, {"out": 0.0, "inp": 0.0}) cons = results[t.dst]["flows"].get(t.material, {"out": 0.0, "inp": 0.0}) avail = prod["out"] - prod["inp"] # net surplus at the source (0 if it's a net consumer) need = cons["inp"] - cons["out"] # net requirement at the destination moved = max(0.0, min(avail, need)) m = mat.get(t.material) weight, volume = moved * (m["Weight"] if m else 0), moved * (m["Volume"] if m else 0) trips = max(weight, volume) / t.cargo if t.cargo else 0.0 freight = trips * t.trip_cost total_profit -= freight ledger.append(dict(material=t.material, src=t.src, dst=t.dst, moved=moved, avail=avail, need=need, shortfall=max(0.0, need - avail), surplus=max(0.0, avail - need), weight=weight, volume=volume, trips=trips, freight=freight)) return dict(results=results, ledger=ledger, total_profit=total_profit)