import json from typing import Any, Dict from fastapi import FastAPI, HTTPException from pydantic import BaseModel from .storage import EventLog from .adapters import build_block_from_events from .ledger import SimpleLedger from .narrative import diff_narrative_blocks, render_narrative_block app = FastAPI(title="NarrativeWeave API") _store = EventLog(path=":memory:") _ledger = SimpleLedger() class IngestPayload(BaseModel): type: str payload: Dict[str, Any] @app.post("/ingest") def ingest(ev: IngestPayload): # store the raw event _store.append_event(ev.type, ev.payload) return {"status": "ok"} @app.post("/build_block") def build_block(): events = _store.read_events() if not events: raise HTTPException(status_code=404, detail="no events") block = build_block_from_events(events) # produce deterministic JSON for ledger anchoring block_json = json.dumps(block.model_dump(), sort_keys=True, default=str) anchor = _ledger.anchor(block_json) _store.save_block(block, ledger_anchor=anchor) return {"block_id": block.id, "ledger_anchor": anchor} @app.get("/block/{block_id}") def get_block(block_id: str): b = _store.get_block(block_id) if not b: raise HTTPException(status_code=404, detail="block not found") return b.dict() @app.get("/block/{block_id}/render") def render_block(block_id: str, redacted: bool = False): b = _store.get_block(block_id) if not b: raise HTTPException(status_code=404, detail="block not found") return {"block_id": block_id, "redacted": redacted, "markdown": render_narrative_block(b, redacted=redacted)} @app.get("/block/{left_block_id}/diff/{right_block_id}") def diff_blocks(left_block_id: str, right_block_id: str): left = _store.get_block(left_block_id) right = _store.get_block(right_block_id) if not left or not right: raise HTTPException(status_code=404, detail="block not found") diff = diff_narrative_blocks(left, right) return diff.dict()