Spaces:
Running
Running
chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)
Browse filesAutomated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): serve.py, szl_energy_operator.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.
- serve.py +7 -0
- szl_energy_operator.py +13 -0
serve.py
CHANGED
|
@@ -1450,6 +1450,13 @@ try:
|
|
| 1450 |
import szl_energy_ledger as _szl_energy_ledger
|
| 1451 |
_szl_energy_ledger_paths = _szl_energy_ledger.register(app, ns="a11oy")
|
| 1452 |
print(f"[a11oy] energy ledger wired: {_szl_energy_ledger_paths}", file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1453 |
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 |
|
|
|
|
| 1450 |
import szl_energy_ledger as _szl_energy_ledger
|
| 1451 |
_szl_energy_ledger_paths = _szl_energy_ledger.register(app, ns="a11oy")
|
| 1452 |
print(f"[a11oy] energy ledger wired: {_szl_energy_ledger_paths}", file=sys.stderr)
|
| 1453 |
+
try:
|
| 1454 |
+
import szl_energy_operator as _eo_wire
|
| 1455 |
+
import szl_energy_ledger as _el_wire
|
| 1456 |
+
_eo_wire.get_operator().subscribe(_el_wire.record_job)
|
| 1457 |
+
print("[a11oy] energy operator->ledger receipts hook wired", file=sys.stderr)
|
| 1458 |
+
except Exception as _eo_led_exc:
|
| 1459 |
+
print(f"[a11oy] energy operator->ledger hook NOT wired: {_eo_led_exc!r}", file=sys.stderr)
|
| 1460 |
except Exception as _ledger_exc: # additive: never break the Space
|
| 1461 |
print(f"[a11oy] energy ledger NOT mounted ({_ledger_exc!r}); SPA + API unaffected", file=sys.stderr)
|
| 1462 |
|
szl_energy_operator.py
CHANGED
|
@@ -416,6 +416,7 @@ class OperatorDaemon:
|
|
| 416 |
self._last_records: list[dict] = [] # rolling tail for status/dashboards
|
| 417 |
self._subscribers: list[Callable[[dict], None]] = []
|
| 418 |
self._grid_price_eur_mwh: Optional[float] = None # latest meter grid price
|
|
|
|
| 419 |
|
| 420 |
# -- subscription (Dev2 receipts hook) --------------------------------
|
| 421 |
def subscribe(self, cb: Callable[[dict], None]) -> None:
|
|
@@ -596,6 +597,9 @@ class OperatorDaemon:
|
|
| 596 |
wall_s = time.time() - t0
|
| 597 |
meter_after = _fetch_joule_meter()
|
| 598 |
sample_after = _exporter_sample_for_node(meter_after, node.exporter_node)
|
|
|
|
|
|
|
|
|
|
| 599 |
j_after = (sample_after or {}).get("joules_measured_total")
|
| 600 |
joules_measured = None
|
| 601 |
if (isinstance(j_before, (int, float)) and isinstance(j_after, (int, float))
|
|
@@ -661,6 +665,10 @@ class OperatorDaemon:
|
|
| 661 |
"node_status": dict(self._node_status),
|
| 662 |
"by_node": {k: dict(v) for k, v in st.by_node.items()},
|
| 663 |
"uptime_s": round(uptime, 3),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
"grid_price_eur_mwh": self._grid_price_eur_mwh,
|
| 665 |
"recent_jobs": list(self._last_records[-10:]),
|
| 666 |
"exporter": _JOULE_METER_URL,
|
|
@@ -698,6 +706,11 @@ def get_operator() -> OperatorDaemon:
|
|
| 698 |
return _OPERATOR
|
| 699 |
|
| 700 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
# ---------------------------------------------------------------------------
|
| 702 |
# Registration — dual-register under /api/{ns}/v1/energy/operator/* AND
|
| 703 |
# /v1/energy/operator/* (mirrors the add_api_route pattern used across the repo).
|
|
|
|
| 416 |
self._last_records: list[dict] = [] # rolling tail for status/dashboards
|
| 417 |
self._subscribers: list[Callable[[dict], None]] = []
|
| 418 |
self._grid_price_eur_mwh: Optional[float] = None # latest meter grid price
|
| 419 |
+
self._last_power_w: Optional[float] = None # latest live exporter power_w (W)
|
| 420 |
|
| 421 |
# -- subscription (Dev2 receipts hook) --------------------------------
|
| 422 |
def subscribe(self, cb: Callable[[dict], None]) -> None:
|
|
|
|
| 597 |
wall_s = time.time() - t0
|
| 598 |
meter_after = _fetch_joule_meter()
|
| 599 |
sample_after = _exporter_sample_for_node(meter_after, node.exporter_node)
|
| 600 |
+
if sample_after is not None and sample_after.get("power_w_sample") is not None:
|
| 601 |
+
with self._lock:
|
| 602 |
+
self._last_power_w = float(sample_after["power_w_sample"])
|
| 603 |
j_after = (sample_after or {}).get("joules_measured_total")
|
| 604 |
joules_measured = None
|
| 605 |
if (isinstance(j_before, (int, float)) and isinstance(j_after, (int, float))
|
|
|
|
| 665 |
"node_status": dict(self._node_status),
|
| 666 |
"by_node": {k: dict(v) for k, v in st.by_node.items()},
|
| 667 |
"uptime_s": round(uptime, 3),
|
| 668 |
+
"window_seconds": round(uptime, 3),
|
| 669 |
+
"jobs_completed": st.jobs_done,
|
| 670 |
+
"exporter_node": next((n.exporter_node for n in self.nodes), None),
|
| 671 |
+
"power_w_sample": self._last_power_w,
|
| 672 |
"grid_price_eur_mwh": self._grid_price_eur_mwh,
|
| 673 |
"recent_jobs": list(self._last_records[-10:]),
|
| 674 |
"exporter": _JOULE_METER_URL,
|
|
|
|
| 706 |
return _OPERATOR
|
| 707 |
|
| 708 |
|
| 709 |
+
def handle_status() -> dict:
|
| 710 |
+
"""Module-level status accessor for in-process readers (Dev3 projection)."""
|
| 711 |
+
return get_operator().status()
|
| 712 |
+
|
| 713 |
+
|
| 714 |
# ---------------------------------------------------------------------------
|
| 715 |
# Registration — dual-register under /api/{ns}/v1/energy/operator/* AND
|
| 716 |
# /v1/energy/operator/* (mirrors the add_api_route pattern used across the repo).
|