#!/usr/bin/env python3 """Make-vs-buy cost tree for a material at one exchange, plus sourcing depth for every raw input. Cost to make = inputs at min(buy, make) per output unit; wages/capex excluded unless --wages. tools/chain.py DEC # AI1 tools/chain.py BHP --depth 3 --qty 13 """ import argparse, sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from puga import config, econ, fio, market, saturation as sat def main(): ap = argparse.ArgumentParser() ap.add_argument("ticker") ap.add_argument("--cx", default=config.DEFAULT_CX) ap.add_argument("--depth", type=int, default=2, help="levels of make-vs-buy expansion") ap.add_argument("--qty", type=float, default=1.0, help="units/day you would make (sourcing check)") ap.add_argument("--wages", action="store_true", help="add wage cost per output unit (both luxuries at ask)") ap.add_argument("--cogc") a = ap.parse_args() snap = market.snapshot() Q = lambda t: snap.get((t, a.cx)) blds = {b["Ticker"]: b for b in fio.buildings()} by_out = {} for r in fio.recipes(): for o in r["Outputs"]: by_out.setdefault(o["Ticker"], []).append(r) def wage_per_day(b): tot = 0.0 for t in econ.TIERS: n = b[t.capitalize() + "s"] if n: tot += n * sum(need / 100 * (Q(tk).ask if Q(tk) and Q(tk).ask else 0) for tk, need, _, _ in econ.CONSUMPTION[t]) return tot def buy(t): q = Q(t) return q.ask if q and q.ask else None memo = {} def make_cost(t, depth, stack=()): """(best unit cost, how). Compares buy vs best recipe.""" key = (t, depth) if key in memo: return memo[key] b = buy(t) best = (b if b is not None else float("inf"), "buy" if b is not None else "n/a", None) if depth > 0 and t not in stack: for r in by_out.get(t, []): bld = blds.get(r["BuildingTicker"]) if not bld: continue cost = 0.0 for i in r["Inputs"]: c, _, _ = make_cost(i["Ticker"], depth - 1, stack + (t,)) cost += i["Amount"] * c outs = sum(o["Amount"] for o in r["Outputs"] if o["Ticker"] == t) if outs == 0: continue if a.wages: eff, _ = econ.building_efficiency({x.lower() + "s": bld[x + "s"] for x in ("Pioneer", "Settler", "Technician", "Engineer", "Scientist")}, {x: 1.0 for x in econ.TIERS}, expertise=bld["Expertise"] or None, cogc=a.cogc) cost += wage_per_day(bld) * (r["TimeMs"] / 86400e3) / eff unit = cost / outs if unit < best[0]: best = (unit, f"make {r['BuildingTicker']} {r['TimeMs']/3.6e6:.1f}h", r) memo[key] = best return best def show(t, depth, qty, indent=0): c, how, r = make_cost(t, depth) q = Q(t) pad = " " * indent mk = f"ask {q.ask:.0f} bid {q.bid:.0f} sup {q.supply:.0f} dem {q.demand:.0f} flow {sat.tref(q.traded7, q.traded30):.1f}/d" if q and q.ask and q.bid else "no market" print(f"{pad}{t:5} x{qty:<8.1f} best {c:9.0f}/u via {how:18} | {mk}") if r and how.startswith("make") and depth > 0: outs = sum(o["Amount"] for o in r["Outputs"] if o["Ticker"] == t) for i in r["Inputs"]: show(i["Ticker"], depth - 1, qty * i["Amount"] / outs, indent + 1) show(a.ticker.upper(), a.depth, a.qty) print("\n# sourcing check for buy-leaves at", a.qty, "output units/day: ask-walk price for one day of inputs") q0 = Q(a.ticker.upper()) _, _, r = make_cost(a.ticker.upper(), a.depth) if r: outs = sum(o["Amount"] for o in r["Outputs"] if o["Ticker"] == a.ticker.upper()) for i in r["Inputs"]: need = a.qty * i["Amount"] / outs w = market.walk(i["Ticker"], a.cx, need, "buy") x = Q(i["Ticker"]) days = x.supply / need if x and need else 0 print(f" {i['Ticker']:5} need {need:8.1f}/d ask {x.ask if x else None} walked avg {w['avg'] and round(w['avg'])} short={w['short']} standing supply = {days:.0f} days of your use, market flow {sat.tref(x.traded7, x.traded30):.1f}/d") if __name__ == "__main__": main()