Add simulator replica, persistence, planet scan, README; split empire state out of the repo

- tools/simulate.py + puga/simulate.py: replica of PRUNplanner's simulator (flows and
  efficiency verified against screenshots), reports real new capex (planned minus built)
- tools/scan.py: staffing variants, freight, HQ/experts, --planet mode, demolish-later,
  --min-n as a pure market-size filter, --json output
- tools/history.py, tools/persistence.py: margin history and short-horizon payback checks
- tools/plan_push.py: guarded delete; tools/state.py: syncs to empire/
- README with features and setup; CLAUDE.md made generic
- Own-empire material (profile, state, plans, notes) moved to gitignored empire/;
  generic examples in plans/examples and state/company.example.yaml

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 00:11:02 +02:00
co-authored by Claude Sonnet 5
parent 7a538cb300
commit 3bbf524ebb
26 changed files with 778 additions and 259 deletions
+10 -3
View File
@@ -1,5 +1,5 @@
"""Tiny disk cache for JSON over HTTP, keyed by URL, expiry by TTL seconds."""
import hashlib, json, time, urllib.request
import hashlib, json, time, urllib.error, urllib.request
from .config import CACHE_DIR
UA = "PuGa/0.1 (personal advisory toolkit)"
@@ -11,7 +11,14 @@ def get_json(url: str, ttl: int, headers: dict[str, str] | None = None, refresh:
if not refresh and f.exists() and time.time() - f.stat().st_mtime < ttl:
return json.loads(f.read_text())
req = urllib.request.Request(url, headers={"User-Agent": UA, **(headers or {})})
with urllib.request.urlopen(req, timeout=60) as r:
data = json.load(r)
for attempt in range(4): # transient SSL/connection errors happen on long scans
try:
with urllib.request.urlopen(req, timeout=60) as r:
data = json.load(r)
break
except (urllib.error.URLError, ConnectionError, TimeoutError):
if attempt == 3:
raise
time.sleep(1.5 * (attempt + 1))
f.write_text(json.dumps(data))
return data