Add buy/sell tools, library docs, and permit/HQ fixes
- tools/buy.py: cash-aware shopping list (construction gap vs empire state's built buildings, plus N days of NET operating stock via simulate() so self-produced inputs net against consumption), priced at real order-book fill cost, checked against cash/reserve, flags multi-trip, binary-searches an affordable size when short - tools/sell.py: where to post an ask vs hitting the bids now, with expected clear time from 30-day traded-volume percentiles - docs/library.md: module map and patterns for using puga.* directly instead of the CLI, plus the "simulate() only knows what the plan lists" gotcha that a first version of buy.py hit - tools/simulate.py: fix --no-hq being a no-op; tools/state.py: permits_total_override for when the in-game HQ screen disagrees with FIO's MaximumPermits - docs/mechanics.md: HQ display vs PRUNplanner's HQ flag, workforce arrival sources - CLAUDE.md, docs/roadmap.md updated Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
# Using `puga` as a library (not the CLI)
|
||||
|
||||
The `tools/*.py` scripts cover the common questions. For anything more involved — a custom
|
||||
what-if, a one-off cost breakdown with wages and capex, a market check the CLI doesn't expose —
|
||||
import the modules directly and write a short script inline. This is normal; most answers in
|
||||
this project's history came from a `python - <<EOF ... EOF` block, not a tool flag.
|
||||
|
||||
```sh
|
||||
. .venv/bin/activate # or use .venv/bin/python directly
|
||||
python - <<'EOF'
|
||||
from puga import market, econ, fio, prunplanner as pp, saturation as sat
|
||||
...
|
||||
EOF
|
||||
```
|
||||
|
||||
## Modules and what they give you
|
||||
|
||||
- **`puga.fio`** — public FIO REST, cached. `exchange_all()`, `order_book(mat, cx)` (full book,
|
||||
`BuyingOrders`/`SellingOrders`), `recipes()`, `buildings()`, `materials()`, `planets_full()`,
|
||||
`workforce_needs()`. `own(path, ttl=0)` for the player's authenticated data
|
||||
(`/sites/{u}`, `/storage/{u}`, `/production/{u}` — has live `Efficiency`/`Condition` per
|
||||
building, `/workforce/{u}/{planet}`, `/ship/ships/{u}`, `/ship/flights/{u}`). `me()` gives the
|
||||
configured username.
|
||||
- **`puga.prunplanner`** — `exchanges()` (has vwap_daily/7d/30d, traded volume — richer than FIO's
|
||||
book totals), `recipes()`/`buildings()` (different field names than `fio`'s: `building_ticker`,
|
||||
`material_ticker`, snake_case), `_g(path, ttl)` for any other `/data/...` GET (e.g.
|
||||
`_g(f"/data/planet/{id}/", 3600)` for fertility, resources, COGC, build requirements).
|
||||
`request(method, path, body)` for authenticated calls (`Api-Key`) — reads are always fine;
|
||||
writes must go through `tools/plan_push.py`'s guardrails, don't call `request("POST"/"PUT"/"DELETE", "/planning/plan/...")` directly.
|
||||
- **`puga.market`** — `snapshot(refresh=False)` returns `{(ticker, cx): Quote}` merging FIO book
|
||||
totals with PRUNplanner's VWAP/volume; this is the one-stop source for prices, use it instead
|
||||
of calling `fio`/`prunplanner` separately. `walk(mat, cx, qty, side, refresh=False)` walks the
|
||||
live order book for a real fill price (`side="buy"` hits asks, `"sell"` hits bids; handles
|
||||
market-maker orders with `ItemCount: None` as unlimited). `uni30(snap, tk)` = PRUNplanner's
|
||||
"Universe 30D" price basis (volume-weighted 30d VWAP across all CX).
|
||||
- **`puga.econ`** — pure formulas, no I/O, ported from PRUNplanner and tested against its own
|
||||
suite plus live FIO: `tier_efficiency`, `workforce_consumption`, `building_efficiency`,
|
||||
`production_io`, `daily_extraction`/`extraction_cycle`, `optimize_habs`, `CONSUMPTION` (the
|
||||
per-tier need table), `HAB_CAP`/`HAB_AREA`, `TIERS`. This is what you use for "what if I
|
||||
understaff this building" or "what's the real wage bill for N heads" — the CLI tools only
|
||||
expose fixed slices of this.
|
||||
- **`puga.saturation`** — `tref`, `n_out`, `p_patient`, `effective_supply`, `is_thin`: the
|
||||
depth/persistence primitives behind `scan.py`. Reuse these instead of re-deriving a "how many
|
||||
buildings can the market take" estimate by hand.
|
||||
- **`puga.simulate`** — `simulate(plan, recipes, buildings, resources, fertility, price, ...)`:
|
||||
the PRUNplanner-simulator replica. Reuse this for anything plan-shaped rather than reimplementing
|
||||
efficiency/workforce/flows again.
|
||||
- **`puga.config`** — `get(key)` reads `.env`; `state_path()` returns the real
|
||||
`empire/state/company.yaml` if synced, else the tracked example.
|
||||
|
||||
## Patterns that come up a lot
|
||||
|
||||
**Price a basket at real fill cost** (construction lists, working stock, a chain's inputs):
|
||||
```python
|
||||
from puga import market, fio
|
||||
mat = {m["Ticker"]: m for m in fio.materials()}
|
||||
def price(items, cx="AI1"):
|
||||
tot = wt = 0
|
||||
for t, q in items.items():
|
||||
w = market.walk(t, cx, q, "buy"); tot += w["total"]; wt += q * mat[t]["Weight"]
|
||||
return tot, wt
|
||||
```
|
||||
|
||||
**Building construction cost incl. MCG**, using either `fio.buildings()` (`Ticker`,
|
||||
`BuildingCosts`, `AreaCost`) or `prunplanner.buildings()` (`building_ticker`, `costs`,
|
||||
`area_cost`) — pick one client and stay consistent within a script, the field names differ.
|
||||
|
||||
**Efficiency of a hypothetical building**, e.g. staffing/COGC/expert what-ifs:
|
||||
```python
|
||||
from puga import econ
|
||||
eff, elements = econ.building_efficiency(
|
||||
dict(pioneers=40, settlers=10), {"pioneer": 1.0, "settler": 1.0},
|
||||
expertise="METALLURGY", cogc="METALLURGY", experts={"METALLURGY": 2},
|
||||
faction="ANTARES", permits_used=1, permits_total=2)
|
||||
```
|
||||
|
||||
**Live production truth** (skip the model, read the game's own number):
|
||||
```python
|
||||
from puga import fio
|
||||
for line in fio.own(f"/production/{fio.me()}", ttl=0):
|
||||
print(line["Type"], line["Efficiency"], line["Condition"])
|
||||
```
|
||||
|
||||
**A recipe's daily flows for N buildings**:
|
||||
```python
|
||||
io = econ.production_io([dict(time_ms=r["TimeMs"], inputs=..., outputs=...)], eff, n_buildings)
|
||||
```
|
||||
|
||||
## `puga.simulate.simulate()` only knows what the plan tells it
|
||||
|
||||
`simulate()` (and anything built on it, e.g. `tools/buy.py`) computes material flows from the
|
||||
plan_data's own building list — it has no idea what else exists at the base beyond the `built`
|
||||
dict used for the construction-gap check. Pass a plan covering everything whose production or
|
||||
consumption matters, not just the piece you're adding, or a self-sufficient material will look
|
||||
like something you need to buy (a standalone HWP plan says "buy 42.8 AL/day"; the full base+HWP
|
||||
plan nets that against the smelters to ~0). This bit a first version of `tools/buy.py`; caught by
|
||||
comparing it against the by-hand restock calc, not by a unit test (the pure functions were right,
|
||||
the plan file chosen was wrong).
|
||||
|
||||
## Conventions to keep
|
||||
|
||||
- Cache TTLs matter: market data is cached ~15 min, static game data ~24h (`puga/cache.py`).
|
||||
Pass `refresh=True` if you just changed something in-game and need it now.
|
||||
- `fio` uses PascalCase ticker/field names (`MaterialTicker`, `TimeMs`); `prunplanner` uses
|
||||
snake_case (`material_ticker`, `time_ms`). Don't mix them in the same dict without converting.
|
||||
- Never call `prunplanner.request` with a write verb outside `tools/plan_push.py` — the `[PuGa]`
|
||||
name guard and dry-run default live there, not in the client.
|
||||
- One-off scripts like these are fine to throw away; if a calculation gets reused more than once
|
||||
or two, promote it into a function in the relevant module instead of re-deriving it in a shell
|
||||
heredoc each time.
|
||||
@@ -58,3 +58,8 @@ Sources: Prosperous Universe dev log #239 "Enlisting Labor" (https://prosperousu
|
||||
- Consequences: a habitation building alone (HB2) gets nothing: capacity without a job requiring those workers requests none. The trigger is the production building. Build the housing first (capacity must exist), then the production building: the reserve tops it up at once, up to the instant allowance; the rest arrives at the next weekly report. Buildings run at efficiency = workers present / required until then.
|
||||
- Report cadence seen on one planet: weekly, Wednesdays ~08:00 UTC (report #289 on 16 Sep 2026).
|
||||
- The old handoff detail "bases keep 75% of workforce between reports" is not in these sources; treat as unverified.
|
||||
|
||||
## HQ in game vs PRUNplanner's "HQ" flag (observed 2026-09-21)
|
||||
- The HQ screen (level 1, base permits 1 / 2) shows "Efficiency gains: Electronics 20.0%", and 23.3% for the next level (permits 1 / 3). That is exactly the faction bonus formula (Antares electronics 5% x 2 x (3 - 2 x used/total)), so the HQ display is the faction bonus.
|
||||
- PRUNplanner's plan flag "HQ" (x1.1 on every building) is NOT visible in live efficiencies (smelters 1.3361, HWP 1.0696 both match the model without it). Keep it off. The HQ upgrade adds a base permit and raises the faction bonus.
|
||||
- FIO `/sites` MaximumPermits (3) disagreed with the HQ screen (2). The HQ screen wins; `tools/state.py` supports `permits_total_override`.
|
||||
|
||||
@@ -16,6 +16,9 @@ Legend: [ ] todo, [x] done. Build order matters; each step is usable on its own.
|
||||
10. [x] `tools/state.py sync` (sites, production efficiency, storage, ships, cash, permits): inventory, production, ships into `state/`.
|
||||
11. [ ] Docs to fill: `docs/apex.md`, `docs/fio.md`, `docs/playbook.md` (question type -> tool), `docs/decisions.md`.
|
||||
|
||||
13. [x] `tools/buy.py`: cash-aware shopping list (construction gap vs empire state's built count, plus N days of net operating stock via simulate(), priced at real fill cost, checked against cash/reserve).
|
||||
14. [x] `tools/sell.py`: ask-post-price + expected-clear-time advisor from 30d traded-volume percentiles, vs hitting the bids now.
|
||||
|
||||
12. [ ] `tools/portfolio.py`: choose the set of 1-building opportunities for one base that maximises total profit given shared whole-building housing (HB1/HB2/...), capex, permits and (for demolish-later) 60-day value decay. Persistence check exists (`tools/persistence.py`); wire it into the ranking.
|
||||
|
||||
## Open questions
|
||||
|
||||
Reference in New Issue
Block a user