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:
+10
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user