57 lines
1.5 KiB
Python
57 lines
1.5 KiB
Python
"""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
|