initial commit
This commit is contained in:
56
hameter/cost_state.py
Normal file
56
hameter/cost_state.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Persistent cost state management.
|
||||
|
||||
Cost state is stored at /data/cost_state.json, separate from config.json.
|
||||
This file is updated each time a cost calculation occurs, enabling
|
||||
persistence across restarts without bloating the config file.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
COST_STATE_PATH = "/data/cost_state.json"
|
||||
|
||||
|
||||
def load_cost_state(path: Optional[str] = None) -> dict:
|
||||
"""Load persisted cost state from disk.
|
||||
|
||||
Returns:
|
||||
Dict keyed by meter_id (str) with cost state values.
|
||||
"""
|
||||
path = path or COST_STATE_PATH
|
||||
if not os.path.isfile(path):
|
||||
return {}
|
||||
try:
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to load cost state: %s", e)
|
||||
return {}
|
||||
|
||||
|
||||
def save_cost_state(states: dict, path: Optional[str] = None):
|
||||
"""Atomically save cost state to disk."""
|
||||
path = path or COST_STATE_PATH
|
||||
dir_path = os.path.dirname(path)
|
||||
if dir_path:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
|
||||
fd, tmp_path = tempfile.mkstemp(dir=dir_path or ".", suffix=".tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
json.dump(states, f, indent=2)
|
||||
f.write("\n")
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp_path, path)
|
||||
except Exception:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
Reference in New Issue
Block a user