Add multi-base chain modelling, fix price-band-aware demand/supply, tranche-split sell tool
- puga/network.py + tools/network.py: model a multi-base chain (e.g. mine LST at one base, ship it, consume it at another). Simulates each base independently, nets a transferred material's producer-surplus against consumer-need, charges real freight only on what's moved, cm_free for a founding covered by a Core Module Kit. Worked example in plans/chains/. - puga/saturation.py: real price-band filtering. FIO's order_book NarrowPriceBandLow/High and WidePriceBandLow/High match APEX's own displayed Price Band exactly (verified live) - an order outside it is a stale artifact, not just uncompetitive. Added in_band() and effective_demand(); effective_supply() gained the same hard band filter alongside its existing soft vwap-proximity filter (renamed that param mult to free up `band`). Wired into tools/scan.py stage 2 in place of the raw, unfiltered demand figure. Was flagged as an unimplemented refinement in saturation-design.md since the original design review. - tools/sell.py: self-contained tranche split (aggressive tranche capped at a volume quantile, median normally or 80th pct with --tight when a payment is imminent and stockout risk outweighs margin; patient tranche priced under the next competitor tier). Instant-bid comparison moved behind --show-bid (off by default). - Docs and roadmap updated accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user