betterwithage commited on
Commit
ad8edf2
·
verified ·
1 Parent(s): b0c66c5

chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)

Browse files

Automated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): Dockerfile, serve.py, szl_energy_projection.py
Deleted (gone from the repo + Dockerfile COPY set): (none)

Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.

Files changed (3) hide show
  1. Dockerfile +1 -1
  2. serve.py +17 -0
  3. szl_energy_projection.py +573 -0
Dockerfile CHANGED
@@ -99,7 +99,7 @@ COPY szl_formula_wiring.py a11oy_code_engine.py a11oy_code.py a11oy_seismic.py s
99
  COPY szl_energy_budget.py szl_energy_sovereign.py szl_energy_provenance.py szl_heart_blood.py szl_engine_status.py szl_backend_hardening.py revenue_endpoints.py a11oy_harvest_endpoints.py ./
100
  # energy operator/ledger/projection modules — imported by serve.py (guarded);
101
  # MUST be per-file COPY'd (this Dockerfile uses no `COPY . .`) or the import falls back to a STUB.
102
- COPY joule_billing.py szl_energy_ledger.py szl_energy_operator.py ./
103
  # ADDITIVE (I3): FABRO-style Governed Factory + Constitutional Engines modules.
104
  # MUST be COPY'd or serve.py's guarded imports fall back (merged-but-not-live).
105
  # HTML/JS is inlined in these .py modules, so NO web/ or static-vendor COPY needed.
 
99
  COPY szl_energy_budget.py szl_energy_sovereign.py szl_energy_provenance.py szl_heart_blood.py szl_engine_status.py szl_backend_hardening.py revenue_endpoints.py a11oy_harvest_endpoints.py ./
100
  # energy operator/ledger/projection modules — imported by serve.py (guarded);
101
  # MUST be per-file COPY'd (this Dockerfile uses no `COPY . .`) or the import falls back to a STUB.
102
+ COPY joule_billing.py szl_energy_ledger.py szl_energy_operator.py szl_energy_projection.py ./
103
  # ADDITIVE (I3): FABRO-style Governed Factory + Constitutional Engines modules.
104
  # MUST be COPY'd or serve.py's guarded imports fall back (merged-but-not-live).
105
  # HTML/JS is inlined in these .py modules, so NO web/ or static-vendor COPY needed.
serve.py CHANGED
@@ -1454,6 +1454,23 @@ except Exception as _ledger_exc: # additive: never break the Space
1454
  print(f"[a11oy] energy ledger NOT mounted ({_ledger_exc!r}); SPA + API unaffected", file=sys.stderr)
1455
 
1456
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1457
  # ---------------------------------------------------------------------------
1458
  # ADDITIVE (Formulas SECTION for the SPA navigation — closeout, A11oy Full-Stack
1459
  # Team / Perplexity Computer Agent): mount GET /formulas/wired, a premium
 
1454
  print(f"[a11oy] energy ledger NOT mounted ({_ledger_exc!r}); SPA + API unaffected", file=sys.stderr)
1455
 
1456
 
1457
+ # ---------------------------------------------------------------------------
1458
+ # ADDITIVE (SZL Energy projection — Dev 3, energy/03-projection): honest 1-day +
1459
+ # scale projection from the live MEASURED rate (joules/tokens/jobs over the
1460
+ # running window, read from Dev1 operator status + Dev2 ledger totals). Every value
1461
+ # labeled MEASURED/MODELED/ESTIMATE; FLOPs shown with formula; resale dollar is an
1462
+ # ESTIMATE and NEVER MEASURED. Mounts GET /api/a11oy/v1/energy/projection.
1463
+ # Registered BEFORE the SPA catch-all. try/except so a11oy boots if import fails.
1464
+ # ---------------------------------------------------------------------------
1465
+ try:
1466
+ import szl_energy_projection as _szl_energy_projection
1467
+ _szl_energy_projection_status = _szl_energy_projection.register(app, ns="a11oy")
1468
+ print(f"[a11oy] energy projection wired ({_szl_energy_projection_status}): /api/a11oy/v1/energy/projection", file=sys.stderr)
1469
+ except Exception as _energy_proj_exc: # additive: never break the Space
1470
+ _szl_energy_projection_status = f"energy-projection-not-wired:{_energy_proj_exc!r}"
1471
+ print(f"[a11oy] energy projection NOT mounted ({_energy_proj_exc!r}); SPA + API unaffected", file=sys.stderr)
1472
+
1473
+
1474
  # ---------------------------------------------------------------------------
1475
  # ADDITIVE (Formulas SECTION for the SPA navigation — closeout, A11oy Full-Stack
1476
  # Team / Perplexity Computer Agent): mount GET /formulas/wired, a premium
szl_energy_projection.py ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173
4
+ # Doctrine v11 LOCKED · Λ = Conjecture 1 · sovereign=false on this path
5
+ """szl_energy_projection.py — SZL Energy: honest 1-day + scale projection engine.
6
+
7
+ Dev 3 (backend). Takes the REAL MEASURED rate observed over the operator's running
8
+ window (joules/hr, tokens/hr, jobs/hr — read from Dev1's operator status + Dev2's
9
+ ledger totals) and extrapolates a 1-DAY figure plus the CSV's node-scaling lines.
10
+
11
+ DOCTRINE v11 (this module is the one most at risk of dishonest numbers — be ruthless):
12
+ - The MEASURED inputs are the live joules/tokens/jobs and the window seconds. Those
13
+ are MEASURED and only MEASURED. Anything we multiply forward in time is a
14
+ PROJECTION and is labeled MODELED.
15
+ - A PROJECTED DOLLAR IS NEVER "MEASURED". The compute-resale line is the big number
16
+ and it is an ESTIMATE built on a clearly-labeled ESTIMATE input (45¢/kWh resale).
17
+ The grid-arbitrage credit is tiny and derived from MEASURED joules × grid price,
18
+ but the forward-extrapolated dollar is still MODELED (a projection of a measured
19
+ rate), never MEASURED.
20
+ - Every value carries a label: MEASURED | MODELED | ESTIMATE | STRUCTURAL-ONLY.
21
+ - FLOPs are MODELED with the formula string shown so the number is re-derivable:
22
+ FLOPs = tokens × model_params × 2 (the standard ~2N-per-token dense-
23
+ transformer inference estimate; cf. Kaplan et al. 2020, "Scaling Laws for
24
+ Neural Language Models", and forge_pinn_measure.py:62 in this repo).
25
+ - The scale lines (3 / 10 / 100 / 1000 nodes) are DERIVED from the live single-node
26
+ measured rate (× nodes × time), all MODELED. They re-derive the founder's
27
+ energy_revenue_model.csv lines from live data instead of restating the CSV.
28
+ - NO fabricated joules / FLOPs / tokens / dollars. If the operator+ledger are not
29
+ reachable, we fall back to the documented live single-node ground-truth sample
30
+ (78,369.586 J off exporter `betterwithage`) and SAY SO via measured_source.
31
+
32
+ Endpoint (existing pattern, dual-register — handler functions + FastAPI register()):
33
+ GET /api/a11oy/v1/energy/projection?window=running
34
+ Returns the 1-day projection + the 1/3/10/100/1000-node scale projections, with
35
+ every value labeled and the formula strings included.
36
+
37
+ Contracts consumed (built against the spec's endpoint shapes; graceful if absent):
38
+ - Dev1 operator status: GET /api/a11oy/v1/energy/operator/status
39
+ { running, window_seconds, joules_measured_total, jobs_completed,
40
+ power_w_sample, exporter_node, exporter_last_seen_ts, ... }
41
+ - Dev2 ledger totals: GET /api/a11oy/v1/energy/ledger
42
+ { totals: { tokens_total, jobs_total, joules_total }, grid_price_eur_mwh, ... }
43
+ """
44
+ from __future__ import annotations
45
+
46
+ import datetime
47
+ import importlib
48
+ import math
49
+ import os
50
+ from typing import Any, Mapping, Optional
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Honesty label constants (doctrine v11 vocabulary). These exact strings are
54
+ # what the tests grep for; a projected dollar must NEVER carry MEASURED.
55
+ # ---------------------------------------------------------------------------
56
+ MEASURED = "MEASURED" # a real, currently-observable reading
57
+ MODELED = "MODELED" # extrapolated/projected from a measured rate
58
+ ESTIMATE = "ESTIMATE" # depends on an assumed (non-measured) input, e.g. resale price
59
+ STRUCTURAL_ONLY = "STRUCTURAL-ONLY"
60
+
61
+ # Physical / model constants.
62
+ JOULES_PER_KWH = 3_600_000.0
63
+ SECONDS_PER_HOUR = 3600.0
64
+ SECONDS_PER_DAY = 86_400.0
65
+ HOURS_PER_DAY = 24.0
66
+
67
+ # FLOPs/token: the standard dense-transformer inference estimate ~2N per generated
68
+ # token (N = parameter count). Formula string is emitted in the response so the
69
+ # number is re-derivable. Cite: Kaplan et al. 2020; forge_pinn_measure.py:62.
70
+ FLOPS_PER_PARAM_PER_TOKEN = 2.0
71
+ FLOP_FORMULA = "FLOPs = tokens × model_params × 2"
72
+ FLOP_FORMULA_CITATION = (
73
+ "standard ~2N-per-token dense-transformer inference FLOP estimate "
74
+ "(2 FLOPs/param/token: one multiply + one add); "
75
+ "cf. Kaplan et al. 2020 'Scaling Laws for Neural Language Models', "
76
+ "and forge_pinn_measure.py:62 in this repo"
77
+ )
78
+
79
+ # Resale price — an ESTIMATE input (the founder's CSV/PAYLOAD use 45¢/kWh). Swap a
80
+ # real power contract + resale rate to make the dollar true. NEVER a measured number.
81
+ RESALE_CENTS_PER_KWH = float(os.environ.get("STRIPE_PRICE_PER_KWH_CENTS", "45"))
82
+
83
+ # Model parameter count for the FLOP estimate. Default mirrors the rig's 7B-class
84
+ # coder model (qwen2.5-coder:7b ≈ 7.6e9 params). ESTIMATE input, overridable.
85
+ DEFAULT_MODEL_PARAMS = float(os.environ.get("MODEL_PARAMS", "7.6e9"))
86
+ DEFAULT_MODEL_NAME = os.environ.get("MODEL_NAME", "qwen2.5-coder:7b")
87
+
88
+ # CSV scale lines the founder charts (energy_revenue_model.csv). We RE-DERIVE these
89
+ # from the live measured single-node rate rather than restate the CSV numbers.
90
+ SCALE_NODES = (1, 3, 10, 100, 1000)
91
+
92
+ # Documented live single-node ground truth (BUILD_SPEC §GROUND TRUTH, 2026-06-14
93
+ # 13:26 EDT): 78,369.586 J MEASURED off exporter `betterwithage`, ~9.74 W,
94
+ # grid 62.08 EUR/MWh. Used ONLY as a labeled fallback when the operator+ledger are
95
+ # not reachable in-process. Never presented as a live reading when it is a fallback.
96
+ _GROUND_TRUTH_JOULES = 78_369.586
97
+ _GROUND_TRUTH_POWER_W = 9.74
98
+ _GROUND_TRUTH_GRID_EUR_MWH = 62.08
99
+ _GROUND_TRUTH_NODE = "betterwithage"
100
+ # A window long enough to make the rate sane for a demo extrapolation; the joules
101
+ # above were the cumulative exporter total at the cited sample. We treat the rate
102
+ # as joules / window. If no live window is available we use this documented window.
103
+ _GROUND_TRUTH_WINDOW_S = float(os.environ.get("GROUND_TRUTH_WINDOW_S", "3600.0"))
104
+ # Tokens/jobs are NOT in the ground-truth joules sample; without Dev2's ledger we
105
+ # cannot know them, so the fallback reports them as None (honest unknown) and the
106
+ # token/FLOP projection is omitted rather than fabricated.
107
+
108
+
109
+ def _now_iso() -> str:
110
+ return datetime.datetime.now(datetime.timezone.utc).isoformat()
111
+
112
+
113
+ def _coerce_float(value: Any) -> Optional[float]:
114
+ if value is None or isinstance(value, bool):
115
+ return None
116
+ try:
117
+ f = float(value)
118
+ except (TypeError, ValueError):
119
+ return None
120
+ if f != f or f in (float("inf"), float("-inf")):
121
+ return None
122
+ return f
123
+
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # MEASURED-rate reader: pull the live window from Dev1 operator + Dev2 ledger.
127
+ # In-process import of the sibling handlers (no network); graceful fallback to the
128
+ # documented ground-truth single-node sample, ALWAYS saying which source was used.
129
+ # ---------------------------------------------------------------------------
130
+
131
+ def _try_operator_status() -> Optional[Mapping[str, Any]]:
132
+ """Best-effort in-process call to Dev1's operator status handler. None if absent."""
133
+ for modname, fn in (
134
+ ("szl_energy_operator", "handle_status"),
135
+ ("szl_energy_operator", "operator_status"),
136
+ ("a11oy_energy_operator", "handle_status"),
137
+ ):
138
+ try:
139
+ mod = importlib.import_module(modname)
140
+ except Exception:
141
+ continue
142
+ handler = getattr(mod, fn, None)
143
+ if callable(handler):
144
+ try:
145
+ out = handler()
146
+ if isinstance(out, Mapping):
147
+ return out
148
+ except Exception:
149
+ continue
150
+ return None
151
+
152
+
153
+ def _try_ledger_totals() -> Optional[Mapping[str, Any]]:
154
+ """Best-effort in-process call to Dev2's ledger handler. None if absent."""
155
+ for modname, fn in (
156
+ ("szl_energy_ledger", "handle_ledger"),
157
+ ("szl_energy_ledger", "ledger_totals"),
158
+ ("a11oy_energy_ledger", "handle_ledger"),
159
+ ):
160
+ try:
161
+ mod = importlib.import_module(modname)
162
+ except Exception:
163
+ continue
164
+ handler = getattr(mod, fn, None)
165
+ if callable(handler):
166
+ try:
167
+ out = handler()
168
+ if isinstance(out, Mapping):
169
+ return out
170
+ except Exception:
171
+ continue
172
+ return None
173
+
174
+
175
+ def _extract_window(op: Optional[Mapping[str, Any]],
176
+ led: Optional[Mapping[str, Any]]) -> dict:
177
+ """Assemble the MEASURED window from operator status + ledger totals.
178
+
179
+ Returns a dict of MEASURED inputs (or the documented fallback) plus the
180
+ ``measured_source`` so the projection is honest about provenance. All numbers
181
+ here are MEASURED (live exporter / ledger) or the documented ground-truth
182
+ sample — never fabricated.
183
+ """
184
+ # --- joules + window + power from the operator status ---
185
+ joules = window_s = power_w = node = grid = None
186
+ jobs = tokens = None
187
+
188
+ if op is not None:
189
+ joules = _coerce_float(op.get("joules_measured_total"))
190
+ window_s = _coerce_float(op.get("window_seconds"))
191
+ power_w = _coerce_float(op.get("power_w_sample"))
192
+ node = op.get("exporter_node") or op.get("node")
193
+ jobs = _coerce_float(op.get("jobs_completed"))
194
+ grid = _coerce_float(op.get("grid_price_eur_mwh"))
195
+
196
+ # --- tokens (and possibly jobs/joules) from the ledger totals ---
197
+ if led is not None:
198
+ totals = led.get("totals") if isinstance(led.get("totals"), Mapping) else led
199
+ if isinstance(totals, Mapping):
200
+ tokens = _coerce_float(totals.get("tokens_total")) if tokens is None else tokens
201
+ if jobs is None:
202
+ jobs = _coerce_float(totals.get("jobs_total"))
203
+ if joules is None:
204
+ joules = _coerce_float(totals.get("joules_total"))
205
+ if grid is None:
206
+ grid = _coerce_float(led.get("grid_price_eur_mwh"))
207
+
208
+ live = (
209
+ joules is not None and joules > 0
210
+ and window_s is not None and window_s > 0
211
+ )
212
+ if live:
213
+ return {
214
+ "measured_source": "live:operator+ledger",
215
+ "joules_measured": joules,
216
+ "window_seconds": window_s,
217
+ "tokens_measured": tokens, # may be None if ledger absent
218
+ "jobs_measured": jobs,
219
+ "power_w_sample": power_w,
220
+ "grid_price_eur_mwh": grid if grid is not None else _GROUND_TRUTH_GRID_EUR_MWH,
221
+ "node": node or _GROUND_TRUTH_NODE,
222
+ "all_measured": True,
223
+ }
224
+
225
+ # --- documented ground-truth fallback (labeled, never silent) ---
226
+ return {
227
+ "measured_source": "fallback:ground-truth-sample (BUILD_SPEC 2026-06-14 13:26 EDT)",
228
+ "joules_measured": _GROUND_TRUTH_JOULES,
229
+ "window_seconds": _GROUND_TRUTH_WINDOW_S,
230
+ "tokens_measured": None, # unknown without Dev2 ledger — NOT fabricated
231
+ "jobs_measured": None,
232
+ "power_w_sample": _GROUND_TRUTH_POWER_W,
233
+ "grid_price_eur_mwh": _GROUND_TRUTH_GRID_EUR_MWH,
234
+ "node": _GROUND_TRUTH_NODE,
235
+ "all_measured": True, # the joules sample itself is MEASURED; tokens unknown
236
+ }
237
+
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Core math — every projected quantity is rate × time, fully re-derivable.
241
+ # ---------------------------------------------------------------------------
242
+
243
+ def _rate_per_hour(value: Optional[float], window_s: float) -> Optional[float]:
244
+ if value is None or window_s <= 0:
245
+ return None
246
+ return value * (SECONDS_PER_HOUR / window_s)
247
+
248
+
249
+ def _grid_arb_usd(joules: float, grid_eur_mwh: float,
250
+ eur_usd: float = 1.08) -> float:
251
+ """Grid-arbitrage credit from MEASURED joules × grid price.
252
+
253
+ kWh = joules / 3.6e6 ; cost(or credit) = kWh × (grid_eur_mwh/1000) EUR → USD.
254
+ A negative grid price means the grid paid us to compute (credit positive).
255
+ The dollar is tiny by design — this is the pennies line, not the headline.
256
+ """
257
+ kwh = joules / JOULES_PER_KWH
258
+ eur_per_kwh = grid_eur_mwh / 1000.0
259
+ usd = kwh * eur_per_kwh * eur_usd
260
+ # Arbitrage *credit*: when grid price is negative we are paid; flip sign so a
261
+ # negative price yields a positive credit. When positive, it is our energy cost.
262
+ return -usd
263
+
264
+
265
+ def _resale_usd(joules: float, cents_per_kwh: float) -> float:
266
+ """Compute-resale ESTIMATE: joules → kWh × resale rate. The BIG line — ESTIMATE.
267
+
268
+ This is the founder's CSV resale_usd column logic: kWh × 45¢. The 45¢/kWh is an
269
+ ESTIMATE input, so this dollar is ESTIMATE/MODELED and NEVER MEASURED.
270
+ """
271
+ kwh = joules / JOULES_PER_KWH
272
+ return kwh * (cents_per_kwh / 100.0)
273
+
274
+
275
+ def _flops(tokens: Optional[float], model_params: float) -> Optional[float]:
276
+ if tokens is None:
277
+ return None
278
+ return tokens * model_params * FLOPS_PER_PARAM_PER_TOKEN
279
+
280
+
281
+ def _labeled(value, label, formula=None, **extra):
282
+ d = {"value": value, "label": label}
283
+ if formula is not None:
284
+ d["formula"] = formula
285
+ d.update(extra)
286
+ return d
287
+
288
+
289
+ def build_projection(window: str = "running",
290
+ model_params: float = DEFAULT_MODEL_PARAMS,
291
+ model_name: str = DEFAULT_MODEL_NAME,
292
+ resale_cents_per_kwh: float = RESALE_CENTS_PER_KWH,
293
+ _measured: Optional[Mapping[str, Any]] = None) -> dict:
294
+ """Build the honest 1-day + scale projection from the live MEASURED rate.
295
+
296
+ ``_measured`` lets tests inject an exact measured window; otherwise we read it
297
+ live from the operator+ledger handlers (or the documented fallback).
298
+ """
299
+ m = dict(_measured) if _measured is not None else _extract_window(
300
+ _try_operator_status(), _try_ledger_totals()
301
+ )
302
+
303
+ joules = float(m["joules_measured"])
304
+ window_s = float(m["window_seconds"])
305
+ tokens = _coerce_float(m.get("tokens_measured"))
306
+ jobs = _coerce_float(m.get("jobs_measured"))
307
+ grid = _coerce_float(m.get("grid_price_eur_mwh"))
308
+ if grid is None:
309
+ grid = _GROUND_TRUTH_GRID_EUR_MWH
310
+
311
+ # --- MEASURED rates (per hour) — these are observations, not projections ---
312
+ joules_per_hr = _rate_per_hour(joules, window_s)
313
+ tokens_per_hr = _rate_per_hour(tokens, window_s)
314
+ jobs_per_hr = _rate_per_hour(jobs, window_s)
315
+
316
+ # --- 1-DAY single-node projection: rate × 24h (MODELED) ---
317
+ joules_day = joules * (SECONDS_PER_DAY / window_s)
318
+ tokens_day = tokens * (SECONDS_PER_DAY / window_s) if tokens is not None else None
319
+ jobs_day = jobs * (SECONDS_PER_DAY / window_s) if jobs is not None else None
320
+ flops_day = _flops(tokens_day, model_params)
321
+
322
+ # --- earnings/day (MODELED), split EXACTLY like the founder's CSV/chart ---
323
+ # grid-arbitrage credit: tiny, derived from MEASURED joules-rate × grid price.
324
+ arb_day_usd = _grid_arb_usd(joules_day, grid)
325
+ # compute-resale: the BIG line, ESTIMATE input (45¢/kWh). NEVER MEASURED.
326
+ resale_day_usd = _resale_usd(joules_day, resale_cents_per_kwh)
327
+ total_day_usd = arb_day_usd + resale_day_usd
328
+
329
+ earnings_day = {
330
+ "grid_arbitrage_credit_usd": _labeled(
331
+ round(arb_day_usd, 9), MODELED,
332
+ formula="(-1) × (joules/day ÷ 3.6e6 kWh) × (grid_EUR_per_MWh ÷ 1000) × EUR→USD",
333
+ derived_from="measured joules-rate × live grid price (tiny by design)",
334
+ note="projection of a measured rate — MODELED, not an observation",
335
+ ),
336
+ "compute_resale_usd": _labeled(
337
+ round(resale_day_usd, 6), ESTIMATE,
338
+ formula=f"(joules/day ÷ 3.6e6 kWh) × ({resale_cents_per_kwh}¢/kWh ÷ 100)",
339
+ estimate_input=f"{resale_cents_per_kwh}¢/kWh resale rate (ASSUMED — swap your real contract)",
340
+ note="the headline line. ESTIMATE input → ESTIMATE dollar (an assumption, not an observation).",
341
+ ),
342
+ "total_usd": _labeled(
343
+ round(total_day_usd, 6), MODELED,
344
+ formula="grid_arbitrage_credit_usd + compute_resale_usd",
345
+ note="MODELED projection; dominated by the ESTIMATE resale line",
346
+ ),
347
+ }
348
+
349
+ compute_day = {
350
+ "tokens": _labeled(
351
+ round(tokens_day, 3) if tokens_day is not None else None,
352
+ MODELED if tokens_day is not None else STRUCTURAL_ONLY,
353
+ formula="tokens/hr (measured rate) × 24 h",
354
+ note=("measured-rate-extrapolated → MODELED" if tokens_day is not None
355
+ else "tokens unknown without Dev2 ledger — not fabricated"),
356
+ ),
357
+ "flops": _labeled(
358
+ flops_day, MODELED if flops_day is not None else STRUCTURAL_ONLY,
359
+ formula=FLOP_FORMULA,
360
+ formula_expanded=(f"{round(tokens_day,3)} tokens × {model_params:g} params × "
361
+ f"{FLOPS_PER_PARAM_PER_TOKEN} FLOPs/param"
362
+ if tokens_day is not None else None),
363
+ citation=FLOP_FORMULA_CITATION,
364
+ model=model_name,
365
+ model_params=model_params,
366
+ note=("FLOPs are MODELED with the formula shown" if flops_day is not None
367
+ else "no measured tokens → FLOPs not derivable, not fabricated"),
368
+ ),
369
+ "joules": _labeled(
370
+ round(joules_day, 3), MODELED,
371
+ formula="joules/hr (measured rate) × 24 h",
372
+ note="measured-rate-extrapolated → MODELED",
373
+ ),
374
+ "jobs": _labeled(
375
+ round(jobs_day, 3) if jobs_day is not None else None,
376
+ MODELED if jobs_day is not None else STRUCTURAL_ONLY,
377
+ formula="jobs/hr (measured rate) × 24 h",
378
+ ),
379
+ }
380
+
381
+ # --- scale-out: re-derive the CSV's 1/3/10/100/1000-node lines from the live
382
+ # single-node measured rate (× nodes × 1 year for the CSV columns, and the
383
+ # 1-day resale/arb for the daily view). ALL MODELED. ---
384
+ scale = []
385
+ for n in SCALE_NODES:
386
+ # CSV columns are annual; re-derive kwh/yr, resale_usd/yr, arb_usd/yr from
387
+ # the live single-node measured rate so the founder's chart is data-backed.
388
+ joules_yr_node = joules * (SECONDS_PER_DAY * 365.0 / window_s)
389
+ kwh_yr = (joules_yr_node / JOULES_PER_KWH) * n
390
+ resale_yr = _resale_usd(joules_yr_node, resale_cents_per_kwh) * n
391
+ arb_yr = _grid_arb_usd(joules_yr_node, grid) * n
392
+ scale.append({
393
+ "nodes": n,
394
+ "label": MODELED,
395
+ "derivation": "live single-node measured joules-rate × nodes × 365 d",
396
+ "kwh_yr": _labeled(round(kwh_yr, 6), MODELED,
397
+ formula="(joules/yr ÷ 3.6e6) × nodes"),
398
+ "compute_resale_usd_yr": _labeled(
399
+ round(resale_yr, 6), ESTIMATE,
400
+ formula=f"kwh_yr × {resale_cents_per_kwh}¢/kWh",
401
+ estimate_input=f"{resale_cents_per_kwh}¢/kWh resale (ASSUMED)"),
402
+ "grid_arbitrage_usd_yr": _labeled(
403
+ round(arb_yr, 9), MODELED,
404
+ formula="(-1) × kwh_yr × grid_price (live measured grid)"),
405
+ "total_usd_yr": _labeled(round(resale_yr + arb_yr, 6), MODELED,
406
+ formula="compute_resale_usd_yr + grid_arbitrage_usd_yr"),
407
+ })
408
+
409
+ return {
410
+ "ok": True,
411
+ "endpoint": "energy/projection",
412
+ "window": window,
413
+ "doctrine": (
414
+ "v11: MEASURED inputs are the live joules/tokens/jobs over the running "
415
+ "window; everything extrapolated forward is MODELED. The compute-resale "
416
+ "dollar is an ESTIMATE (45¢/kWh assumed) and is NEVER labeled MEASURED. "
417
+ "Grid arbitrage is the tiny pennies line. Λ = Conjecture 1; sovereign=false."
418
+ ),
419
+ "measured_inputs": {
420
+ "label": MEASURED,
421
+ "measured_source": m.get("measured_source"),
422
+ "joules_measured": _labeled(joules, MEASURED, note="real exporter reading"),
423
+ "window_seconds": _labeled(window_s, MEASURED),
424
+ "tokens_measured": _labeled(tokens, MEASURED if tokens is not None else STRUCTURAL_ONLY),
425
+ "jobs_measured": _labeled(jobs, MEASURED if jobs is not None else STRUCTURAL_ONLY),
426
+ "grid_price_eur_mwh": _labeled(grid, MEASURED, note="live grid price at sample"),
427
+ "node": m.get("node"),
428
+ "measured_rates_per_hour": {
429
+ "joules_per_hr": _labeled(round(joules_per_hr, 6) if joules_per_hr is not None else None, MEASURED),
430
+ "tokens_per_hr": _labeled(round(tokens_per_hr, 6) if tokens_per_hr is not None else None,
431
+ MEASURED if tokens_per_hr is not None else STRUCTURAL_ONLY),
432
+ "jobs_per_hr": _labeled(round(jobs_per_hr, 6) if jobs_per_hr is not None else None,
433
+ MEASURED if jobs_per_hr is not None else STRUCTURAL_ONLY),
434
+ },
435
+ },
436
+ "projection_1day_single_node": {
437
+ "label": MODELED,
438
+ "basis": "single-node measured rate × 24 h",
439
+ "compute_done": compute_day,
440
+ "earnings": earnings_day,
441
+ },
442
+ "flop_estimate": {
443
+ "formula": FLOP_FORMULA,
444
+ "flops_per_param_per_token": FLOPS_PER_PARAM_PER_TOKEN,
445
+ "model": model_name,
446
+ "model_params": model_params,
447
+ "citation": FLOP_FORMULA_CITATION,
448
+ "label": MODELED,
449
+ },
450
+ "scale_projection": {
451
+ "label": MODELED,
452
+ "basis": "re-derived from the live single-node measured rate (not the CSV restated)",
453
+ "csv_reference": "energy_revenue_model.csv (founder's 1/10/100/1000-node chart)",
454
+ "resale_estimate_input_cents_per_kwh": resale_cents_per_kwh,
455
+ "lines": scale,
456
+ },
457
+ "honesty": {
458
+ "sovereign": False,
459
+ "lambda": "Conjecture 1",
460
+ "free_energy": False,
461
+ "projected_revenue_label": MODELED,
462
+ "resale_input_label": ESTIMATE,
463
+ "note": ("A projection is MODELED with its formula shown. A projected "
464
+ "dollar is NEVER MEASURED. Revenue is MEASURED only when a real "
465
+ "charge clears (joule_billing.py)."),
466
+ },
467
+ "timestamp_utc": _now_iso(),
468
+ }
469
+
470
+
471
+ def handle_projection(window: str = "running") -> dict:
472
+ """GET /energy/projection?window=running — handler used by FastAPI and __main__."""
473
+ try:
474
+ return build_projection(window=window)
475
+ except Exception as exc: # never 500: honest degraded response
476
+ return {
477
+ "ok": False,
478
+ "endpoint": "energy/projection",
479
+ "error": str(exc),
480
+ "doctrine": "v11: projection unavailable; no fabricated numbers emitted.",
481
+ "timestamp_utc": _now_iso(),
482
+ }
483
+
484
+
485
+ # ---------------------------------------------------------------------------
486
+ # FastAPI router registration — mirrors a11oy_harvest_endpoints.register() exactly.
487
+ # ---------------------------------------------------------------------------
488
+
489
+ def register(app, ns: str = "a11oy") -> str:
490
+ """Mount the projection endpoint on the FastAPI ``app``. Returns a status string."""
491
+ from fastapi.responses import JSONResponse
492
+
493
+ base = f"/api/{ns}/v1/energy"
494
+
495
+ @app.get(f"{base}/projection")
496
+ async def _energy_projection(window: str = "running"):
497
+ """Honest 1-day + scale projection from the live MEASURED rate (every value labeled)."""
498
+ return JSONResponse(handle_projection(window=window))
499
+
500
+ return "energy-projection-wired:1"
501
+
502
+
503
+ # ---------------------------------------------------------------------------
504
+ # Self-test — verifies the projection math, labels, FLOP formula, and scale lines.
505
+ # ---------------------------------------------------------------------------
506
+
507
+ if __name__ == "__main__":
508
+ import json as _json
509
+ import sys as _sys
510
+
511
+ print("=" * 72)
512
+ print("szl_energy_projection — self-test (math, labels, FLOP formula, scale)")
513
+ print("=" * 72)
514
+
515
+ # Inject an exact measured window so the projection is deterministic.
516
+ measured = {
517
+ "measured_source": "selftest:injected",
518
+ "joules_measured": 78_369.586,
519
+ "window_seconds": 3600.0, # 1 h window → daily = ×24
520
+ "tokens_measured": 120_000.0,
521
+ "jobs_measured": 40.0,
522
+ "power_w_sample": 9.74,
523
+ "grid_price_eur_mwh": -2.90, # negative → grid paid us → positive arb credit
524
+ "node": "betterwithage",
525
+ "all_measured": True,
526
+ }
527
+ proj = build_projection(window="running", _measured=measured)
528
+
529
+ # 1) projection == measured_rate × time (EXACT)
530
+ j_day = proj["projection_1day_single_node"]["compute_done"]["joules"]["value"]
531
+ assert abs(j_day - 78_369.586 * 24.0) < 1e-6, f"joules/day not exact: {j_day}"
532
+ t_day = proj["projection_1day_single_node"]["compute_done"]["tokens"]["value"]
533
+ assert abs(t_day - 120_000.0 * 24.0) < 1e-6, f"tokens/day not exact: {t_day}"
534
+ print(f"[1] EXACT rate×time: joules/day={j_day} tokens/day={t_day} OK")
535
+
536
+ # 2) FLOPs formula present + correct
537
+ flops = proj["projection_1day_single_node"]["compute_done"]["flops"]
538
+ assert flops["formula"] == FLOP_FORMULA, "FLOP formula string missing"
539
+ expect_flops = t_day * DEFAULT_MODEL_PARAMS * 2.0
540
+ assert abs(flops["value"] - expect_flops) < 1.0, "FLOPs value not re-derivable"
541
+ print(f"[2] FLOPs formula '{flops['formula']}' → {flops['value']:.3e} OK")
542
+
543
+ # 3) every projected DOLLAR labeled MODELED/ESTIMATE (never MEASURED)
544
+ earn = proj["projection_1day_single_node"]["earnings"]
545
+ assert earn["compute_resale_usd"]["label"] == ESTIMATE
546
+ assert earn["grid_arbitrage_credit_usd"]["label"] == MODELED
547
+ assert earn["total_usd"]["label"] == MODELED
548
+ print(f"[3] resale={earn['compute_resale_usd']['label']} "
549
+ f"arb={earn['grid_arbitrage_credit_usd']['label']} "
550
+ f"total={earn['total_usd']['label']} OK")
551
+
552
+ # 4) scale lines derive from the single-node rate (3 must be 3× node-1 resale)
553
+ lines = {l["nodes"]: l for l in proj["scale_projection"]["lines"]}
554
+ r1 = lines[1]["compute_resale_usd_yr"]["value"]
555
+ r3 = lines[3]["compute_resale_usd_yr"]["value"]
556
+ r1000 = lines[1000]["compute_resale_usd_yr"]["value"]
557
+ assert abs(r3 - 3 * r1) < 1e-6, "3-node resale not 3× single-node"
558
+ assert abs(r1000 - 1000 * r1) < 1e-3, "1000-node resale not 1000× single-node"
559
+ print(f"[4] scale derives: 1-node=${r1:.4f}/yr 3-node=${r3:.4f}/yr "
560
+ f"1000-node=${r1000:,.2f}/yr OK")
561
+
562
+ # 5) NO projected revenue may be labeled MEASURED (grep the whole output)
563
+ blob = _json.dumps(proj)
564
+ # measured_inputs legitimately carry MEASURED. Projected revenue must not.
565
+ for path in ("projection_1day_single_node", "scale_projection"):
566
+ sub = _json.dumps(proj[path])
567
+ assert MEASURED not in sub, f"DOCTRINE VIOLATION: '{MEASURED}' in projected block {path}"
568
+ print(f"[5] no '{MEASURED}' label inside any projected block OK")
569
+
570
+ print("\n--- example 1-day output (single node, injected selftest window) ---")
571
+ print(_json.dumps(proj["projection_1day_single_node"], indent=2))
572
+ print("\nok:true checks:5")
573
+ _sys.exit(0)