""" store.py — tiny JSON-backed persistence with a swappable interface. Why JSON for now: the user asked to "store what's needed when there's a need." This keeps zero external dependencies. NOTE: a Hugging Face Space's disk is EPHEMERAL — it resets on rebuild/restart — so this is fine for development and demos, but for durable data we'll swap this layer for a real DB (Supabase, Postgres, an HF Dataset, etc.) without touching the rest of the app. Shape: { "domains": [ {"id", "name", "description", "system_prompt", "projects": [ {"id", "name", "description", "milestones": [ {milestone row dicts} ]} ]} ], "hr": {"documents": [{"id", "name", "text", "chunks": [str, ...]}]} } """ import json import os import threading import uuid DATA_DIR = os.environ.get("DATA_DIR", os.path.join(os.path.dirname(__file__), "..", "data")) DATA_FILE = os.path.join(DATA_DIR, "store.json") _lock = threading.Lock() _state = None def _new_id() -> str: return uuid.uuid4().hex[:12] def _empty_state() -> dict: return {"domains": [], "hr": {"documents": []}} def _load() -> dict: global _state if _state is not None: return _state try: with open(DATA_FILE, "r", encoding="utf-8") as f: _state = json.load(f) except (FileNotFoundError, json.JSONDecodeError): _state = _empty_state() # forward-compat: ensure keys exist _state.setdefault("domains", []) _state.setdefault("hr", {"documents": []}) return _state def _save() -> None: os.makedirs(DATA_DIR, exist_ok=True) tmp = DATA_FILE + ".tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(_state, f, ensure_ascii=False, indent=2) os.replace(tmp, DATA_FILE) # ---- domains ----------------------------------------------------------------- def list_domains() -> list: return _load()["domains"] def get_domain(domain_id: str): return next((d for d in _load()["domains"] if d["id"] == domain_id), None) def add_domain(name: str, description: str = "", system_prompt: str = "") -> dict: with _lock: d = { "id": _new_id(), "name": name.strip(), "description": description.strip(), "system_prompt": system_prompt.strip(), "projects": [], } _load()["domains"].append(d) _save() return d def delete_domain(domain_id: str) -> bool: with _lock: st = _load() before = len(st["domains"]) st["domains"] = [d for d in st["domains"] if d["id"] != domain_id] changed = len(st["domains"]) != before if changed: _save() return changed # ---- projects ---------------------------------------------------------------- def get_project(domain_id: str, project_id: str): d = get_domain(domain_id) if not d: return None return next((p for p in d["projects"] if p["id"] == project_id), None) def add_project(domain_id: str, name: str, description: str = "") -> dict: with _lock: d = get_domain(domain_id) if not d: raise KeyError("domain not found") p = { "id": _new_id(), "name": name.strip(), "description": description.strip(), "sheet_url": "", "last_synced": None, "milestones": [], } d["projects"].append(p) _save() return p def delete_project(domain_id: str, project_id: str) -> bool: with _lock: d = get_domain(domain_id) if not d: return False before = len(d["projects"]) d["projects"] = [p for p in d["projects"] if p["id"] != project_id] changed = len(d["projects"]) != before if changed: _save() return changed def set_project_milestones(domain_id: str, project_id: str, milestones: list) -> dict: with _lock: p = get_project(domain_id, project_id) if not p: raise KeyError("project not found") p["milestones"] = milestones _save() return p def update_project(domain_id: str, project_id: str, **fields) -> dict: with _lock: p = get_project(domain_id, project_id) if not p: raise KeyError("project not found") p.update(fields) _save() return p # ---- HR documents ------------------------------------------------------------ def list_hr_documents() -> list: return _load()["hr"]["documents"] def add_hr_document(name: str, text: str, chunks: list) -> dict: with _lock: doc = {"id": _new_id(), "name": name, "text": text, "chunks": chunks} _load()["hr"]["documents"].append(doc) _save() return doc def delete_hr_document(doc_id: str) -> bool: with _lock: docs = _load()["hr"]["documents"] before = len(docs) _load()["hr"]["documents"] = [d for d in docs if d["id"] != doc_id] changed = len(_load()["hr"]["documents"]) != before if changed: _save() return changed