whatif.py: model demolition correctly - reclaim materials aren't a dead loss
Was wrong earlier this session claiming demolition gives nothing back. FIO's /rain/userplanetbuildingreclaimables confirms partial material return (observed 50-90% depending on material, not a fixed fraction - looked up live, not approximated). --remove now prices the real reclaim for anything actually built and nets it against new capex, so a demolish-and-reinvest question gets the true net cash picture instead of double-penalizing the removed building. Used this to check "demolish BHP's HWP, build a 2nd CHP or POL instead" - both are clearly net losers even counting the ~57,750 AIC reclaim credit (net capex still positive, and marginal profit is negative either way) - dominated by just adding the new building without removing anything, since nothing scarce actually forces the swap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+56
-7
@@ -10,10 +10,11 @@ session - e.g. PP2 overloading pioneer housing after POL+CHP already maxed it).
|
|||||||
tools/whatif.py before.yaml --add POL:1 # reuses POL's existing recipe in before.yaml
|
tools/whatif.py before.yaml --add POL:1 # reuses POL's existing recipe in before.yaml
|
||||||
"""
|
"""
|
||||||
import argparse, copy, sys
|
import argparse, copy, sys
|
||||||
|
from collections import Counter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import yaml
|
import yaml
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
from puga import config, market, prunplanner as pp
|
from puga import config, fio, market, prunplanner as pp
|
||||||
from puga.simulate import simulate
|
from puga.simulate import simulate
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
@@ -59,6 +60,34 @@ def apply_infra(spec: dict, infra: str):
|
|||||||
inf[ticker] = inf.get(ticker, 0) + int(amount)
|
inf[ticker] = inf.get(ticker, 0) + int(amount)
|
||||||
|
|
||||||
|
|
||||||
|
def reclaim_value(ticker: str, qty: int, built_count: int, cx: str):
|
||||||
|
"""Sell value of the materials you'd actually get back demolishing `qty` of a building you
|
||||||
|
already own `built_count` of - real per-site data from FIO's own reclaimables endpoint
|
||||||
|
(demolition is NOT a dead loss: observed ~50-90%% of original materials back, varies by
|
||||||
|
material, not a fixed fraction - don't approximate it, look it up). None if you don't
|
||||||
|
actually own one (can't reclaim a hypothetical building)."""
|
||||||
|
if built_count <= 0 or qty <= 0:
|
||||||
|
return None
|
||||||
|
u = fio.me()
|
||||||
|
rec = fio.own(f"/rain/userplanetbuildingreclaimables/{u}", ttl=3600)
|
||||||
|
totals = Counter()
|
||||||
|
for x in rec:
|
||||||
|
if x["BuildingId"].startswith(f"{ticker}-"):
|
||||||
|
totals[x["Material"]] += x["Amount"]
|
||||||
|
if not totals:
|
||||||
|
return None
|
||||||
|
value = 0.0
|
||||||
|
items = {}
|
||||||
|
for mat, amt in totals.items():
|
||||||
|
q = round(amt / built_count * min(qty, built_count)) # amt is already summed over all built_count units
|
||||||
|
if q <= 0:
|
||||||
|
continue
|
||||||
|
w = market.walk(mat, cx, q, "sell")
|
||||||
|
items[mat] = q
|
||||||
|
value += w["total"]
|
||||||
|
return {"value": value, "items": items}
|
||||||
|
|
||||||
|
|
||||||
def run(spec, recipes, blds, price, faction, perm, built):
|
def run(spec, recipes, blds, price, faction, perm, built):
|
||||||
plan = plan_push.build_payload(spec, recipes, {b["building_ticker"] for b in blds})
|
plan = plan_push.build_payload(spec, recipes, {b["building_ticker"] for b in blds})
|
||||||
planet = pp._g(f"/data/planet/{plan['planet_natural_id']}/", 3600)
|
planet = pp._g(f"/data/planet/{plan['planet_natural_id']}/", 3600)
|
||||||
@@ -122,15 +151,35 @@ def main():
|
|||||||
d_profit = r_a["profit"] - r_b["profit"]
|
d_profit = r_a["profit"] - r_b["profit"]
|
||||||
d_capex = r_a["new_capex"] - r_b["new_capex"]
|
d_capex = r_a["new_capex"] - r_b["new_capex"]
|
||||||
|
|
||||||
|
before_counts = Counter(b["building"] for b in before_spec.get("buildings", []) for _ in range(b["amount"]))
|
||||||
|
after_counts = Counter(b["building"] for b in after_spec.get("buildings", []) for _ in range(b["amount"]))
|
||||||
|
reclaim_total = 0.0
|
||||||
|
reclaim_lines = []
|
||||||
|
for ticker in before_counts:
|
||||||
|
removed_qty = before_counts[ticker] - after_counts.get(ticker, 0)
|
||||||
|
if removed_qty > 0:
|
||||||
|
r = reclaim_value(ticker, removed_qty, built.get(ticker, 0), a.cx)
|
||||||
|
if r:
|
||||||
|
reclaim_total += r["value"]
|
||||||
|
items = ", ".join(f"{q}{m}" for m, q in r["items"].items())
|
||||||
|
reclaim_lines.append(f" {ticker} x{removed_qty}: {r['value']:,.0f} AIC ({items})")
|
||||||
|
|
||||||
print(f"BEFORE profit/day {r_b['profit']:,.0f} new_capex {r_b['new_capex']:,.0f}")
|
print(f"BEFORE profit/day {r_b['profit']:,.0f} new_capex {r_b['new_capex']:,.0f}")
|
||||||
print(f"AFTER profit/day {r_a['profit']:,.0f} new_capex {r_a['new_capex']:,.0f}")
|
print(f"AFTER profit/day {r_a['profit']:,.0f} new_capex {r_a['new_capex']:,.0f}")
|
||||||
print(f"\nMARGINAL profit/day {d_profit:,.0f} new_capex {d_capex:,.0f}", end="")
|
if reclaim_lines:
|
||||||
if d_capex > 0 and d_profit > 0:
|
print(f"\nRECLAIMED MATERIALS (demolishing what's removed, sell value, real FIO data):")
|
||||||
print(f" ROI/day {100*d_profit/d_capex:.2f}% payback {d_capex/d_profit:.1f} d")
|
for line in reclaim_lines:
|
||||||
elif d_capex <= 0:
|
print(line)
|
||||||
print(" (no new capex)")
|
print(f" total: {reclaim_total:,.0f} AIC")
|
||||||
|
net_capex = d_capex - reclaim_total
|
||||||
|
net_label = f" net cash GAINED {-net_capex:,.0f}" if net_capex < 0 and reclaim_total else (f" net new capex {net_capex:,.0f}" if reclaim_total else "")
|
||||||
|
print(f"\nMARGINAL profit/day {d_profit:,.0f} new_capex {d_capex:,.0f}" + net_label, end="")
|
||||||
|
if net_capex > 0 and d_profit > 0:
|
||||||
|
print(f" ROI/day {100*d_profit/net_capex:.2f}% payback {net_capex/d_profit:.1f} d")
|
||||||
|
elif net_capex <= 0 and d_profit > 0:
|
||||||
|
print(" (no net new capex after reclaim)" if reclaim_total else " (no new capex)")
|
||||||
else:
|
else:
|
||||||
print(" (marginal profit <= 0 - not worth it)")
|
print(" (marginal profit <= 0 - not worth it, regardless of reclaim)")
|
||||||
|
|
||||||
print("\nWORKFORCE (after) need supply open eff%")
|
print("\nWORKFORCE (after) need supply open eff%")
|
||||||
newly_overloaded = []
|
newly_overloaded = []
|
||||||
|
|||||||
Reference in New Issue
Block a user