| import json |
| import hashlib |
| from datetime import datetime |
| from pathlib import Path |
| from filelock import FileLock |
|
|
| DATA_DIR = Path("data") |
| CARDS_DIR = DATA_DIR / "cards" |
| DECISIONS_FILE = DATA_DIR / "decisions.json" |
| LOCK_FILE = DATA_DIR / ".decisions.lock" |
|
|
|
|
| def ensure_dirs(): |
| DATA_DIR.mkdir(exist_ok=True) |
| CARDS_DIR.mkdir(exist_ok=True) |
|
|
|
|
| def generate_commit_hash(timestamp: str, raw_input: str) -> str: |
| content = f"{timestamp}:{raw_input}" |
| return hashlib.sha256(content.encode()).hexdigest()[:7] |
|
|
|
|
| def generate_id() -> str: |
| now = datetime.now() |
| decisions = load_decisions() |
| today_count = sum( |
| 1 for d in decisions |
| if d["timestamp"].startswith(now.strftime("%Y-%m-%d")) |
| ) |
| return f"dec_{now.strftime('%Y%m%d')}_{today_count + 1:03d}" |
|
|
|
|
| def load_decisions() -> list[dict]: |
| ensure_dirs() |
| if not DECISIONS_FILE.exists(): |
| return [] |
| try: |
| with open(DECISIONS_FILE, "r", encoding="utf-8") as f: |
| return json.load(f) |
| except (json.JSONDecodeError, IOError): |
| return [] |
|
|
|
|
| def _write_decisions(decisions: list[dict]): |
| ensure_dirs() |
| with open(DECISIONS_FILE, "w", encoding="utf-8") as f: |
| json.dump(decisions, f, indent=2, default=str) |
|
|
|
|
| def save_decision(record: dict): |
| lock = FileLock(str(LOCK_FILE)) |
| with lock: |
| decisions = load_decisions() |
| decisions.append(record) |
| _write_decisions(decisions) |
|
|
|
|
| def update_decision(decision_id: str, updates: dict): |
| lock = FileLock(str(LOCK_FILE)) |
| with lock: |
| decisions = load_decisions() |
| for d in decisions: |
| if d["id"] == decision_id: |
| d.update(updates) |
| break |
| _write_decisions(decisions) |
|
|
|
|
| def get_open_decisions() -> list[dict]: |
| return [d for d in load_decisions() if d.get("status") == "open"] |
|
|
|
|
| def get_decision_by_id(decision_id: str) -> dict | None: |
| for d in load_decisions(): |
| if d["id"] == decision_id: |
| return d |
| return None |
|
|
|
|
| def resolve_decision(decision_id: str, description: str, valence: str): |
| decision = get_decision_by_id(decision_id) |
| if not decision: |
| return |
|
|
| predictions = decision.get("consequence_predictions", []) |
| high_preds = [p for p in predictions if p.get("probability") == "high"] |
| if high_preds: |
| matching = sum(1 for p in high_preds if p.get("valence") == valence) |
| accuracy = matching / len(high_preds) |
| else: |
| accuracy = 0.5 |
|
|
| outcome = { |
| "timestamp": datetime.now().isoformat(), |
| "description": description, |
| "actual_valence": valence, |
| "prediction_accuracy": round(accuracy, 2), |
| } |
| update_decision(decision_id, {"status": "resolved", "outcome": outcome}) |
|
|
|
|
| def export_decisions() -> str: |
| return json.dumps(load_decisions(), indent=2, default=str) |
|
|
|
|
| def create_decision_record( |
| raw_input: str, |
| input_type: str, |
| follow_up_qa: list[dict], |
| category: str, |
| subcategory: str, |
| severity: int, |
| status_emoji: str, |
| consequence_predictions: list[dict], |
| moment_card_prompt: str, |
| moment_card_path: str | None = None, |
| image_description: str | None = None, |
| ) -> dict: |
| now = datetime.now() |
| decision_id = generate_id() |
| commit_hash = generate_commit_hash(now.isoformat(), raw_input) |
|
|
| record = { |
| "id": decision_id, |
| "timestamp": now.isoformat(), |
| "input_type": input_type, |
| "raw_input": raw_input, |
| "image_description": image_description, |
| "follow_up_qa": follow_up_qa, |
| "category": category, |
| "subcategory": subcategory, |
| "severity": severity, |
| "consequence_predictions": consequence_predictions, |
| "moment_card_prompt": moment_card_prompt, |
| "moment_card_path": moment_card_path, |
| "status": "open", |
| "outcome": None, |
| "debug_metadata": { |
| "commit_hash": commit_hash, |
| "branch": category, |
| "status_emoji": status_emoji, |
| }, |
| } |
| save_decision(record) |
| return record |
|
|