Skip to content
tutrosPublic

About

AI-powered decision analysis tool with N options, three depth tiers, and hidden systemic impact discovery

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Prism

An AI-powered decision comparison tool that surfaces hidden costs and systemic impacts invisible in traditional comparisons.

Enter two to six options (the API accepts up to ten) and your intent (e.g. "buying a house vs. renting, optimizing for 10-year financial outcome"). Prism suggests a depth tier, researches the options via web search (Medium and Deep tiers), then uses Claude to generate scored criteria covering financial, environmental, social, and long-term factors you might not have considered. The Deep tier also surfaces hidden systemic properties: second-order effects of each option.

Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 20.19+ or 22.12+ (required by Vite 7)
  • API keys for Anthropic and Tavily

1. Set environment variables

export ANTHROPIC_API_KEY=sk-ant-...
export TAVILY_API_KEY=tvly-...

Optional:

export DAILY_SPEND_CAP_USD=5.0   # Default $5/day cap on Claude API costs
export LLM_BACKEND=api           # api (default) or cli; see below

Running Claude on a subscription (LLM_BACKEND=cli)

With LLM_BACKEND=cli, Prism sends every Claude call through Claude Code's headless mode (claude -p) instead of the Anthropic API, so it runs on your logged-in Claude subscription and ANTHROPIC_API_KEY isn't needed. This is for running Prism locally for yourself only; don't use it for a shared or hosted instance.

  • Install Claude Code with the native installer and log in (claude, then /login). The backend refuses to start if claude isn't on PATH, or if it's an npm .cmd shim on Windows (which would garble the prompts).
  • If ANTHROPIC_API_KEY is set, claude -p bills the key instead of the subscription. Prism removes it, plus ANTHROPIC_AUTH_TOKEN and the CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY switches, from the claude subprocess's environment.
  • CLI mode isn't supported in Docker. Run the backend directly on the host with uvicorn (step 2); the Docker setup keeps using the API key.
  • CLI calls are logged in usage_metrics.json with backend: "cli" and the cost claude -p reports, but that cost isn't billed and doesn't count toward DAILY_SPEND_CAP_USD. /api/monitoring reports it separately as cli_notional_cost_usd; its token totals and per-feature counts include CLI calls, each of which adds about 24k cache-creation tokens of Claude Code overhead.
  • The cap is still checked when starting an analysis, so if earlier API spend reached it today, CLI mode gets 429 too until tomorrow (or raise DAILY_SPEND_CAP_USD).
  • Each call has a fixed overhead of about 1 to 2 seconds. max_tokens isn't applied in CLI mode.

2. Start the backend

cd backend
pip install -r requirements.txt
uvicorn app.main:app --reload     # Runs on http://localhost:8000

Verify: curl http://localhost:8000/health should return {"status":"ok"}.

3. Start the frontend

cd frontend
npm install
npm run dev                       # Runs on http://localhost:5173

Open http://localhost:5173 in your browser.

4. (Optional) Override the API base URL

If the backend runs on a different host or port, set this before starting the frontend:

export VITE_API_BASE=http://your-host:8000

5. (Alternative) Run with Docker

Docker Compose auto-loads docker-compose.override.yml, so the bare command is the dev stack (hot reload, source bind-mounted, backend on 8000, Vite on 5173):

cp .env.example .env          # then fill in the two API keys
docker compose up --build

Production (nginx on port 80 serving the built SPA and proxying /api, backend not exposed) must skip the override file explicitly. UI and API are served from one origin; the CSP and backend CORS settings assume this, so a split frontend/backend deployment is not supported:

docker compose -f docker-compose.yml up --build -d

In production, analysis data lives in the named Docker volume prism-data (Compose prefixes it with the project name; find it with docker volume ls --filter name=prism-data, copy out with docker cp). In dev it stays in backend/data/ on the host. Requires Docker Compose v2.24.4 or newer (for the !override tag).

Security: local use only

Prism has no authentication. Every endpoint, including /api/monitoring (usage and cost stats), is open to anyone who can reach the server, and every analysis spends your Anthropic and Tavily credit. Run it on localhost or a private network only. DAILY_SPEND_CAP_USD is not access control and only partly limits spending (see Spending guard below). If you expose it beyond that, put it behind an authenticating reverse proxy first.

To report a security problem, please open a private security advisory rather than a public issue.

How It Works

  1. Create a decision: options plus intent. Claude Haiku suggests a tier (Quick, Medium or Deep) with a rationale.
  2. Run an analysis at the chosen tier. The backend returns 202 with an analysis_id and works in a background task.
  3. Watch progress over Server-Sent Events while the frontend polls for the result.
  4. Read the result: a winner with reasoning, scored criteria with sources, and (Deep only) hidden properties.

Tiers

Tier Approx. cost Approx. time What it does
Quick ~$0.002 ~5s Claude's own knowledge, no web search
Medium ~$0.04 ~45s Tavily web search per option, then criteria with cited sources
Deep ~$0.18 ~3min Medium, plus discovery of hidden systemic properties (best-effort: a failure here doesn't fail the analysis)

Costs and times are rough guides, not measurements.

Architecture

Browser (React) --HTTP/SSE--> FastAPI backend --> Tavily (web search)
                                              --> Claude (analysis)
                                              --> data/ (JSON files)

The backend persists everything as JSON files; there is no database. Decisions own analyses:

data/
  decisions/
    {decision_id}.json            Decision (options, intent, suggested tier)
    {decision_id}/analyses/
      {analysis_id}.json          AnalysisResult (written as "processing", then "completed" or "error")
  usage_metrics.json              Token usage and cost per Claude call

Writes are atomic (tempfile, fsync, os.replace), and IDs are validated as UUIDs before any file access.

Backend

backend/app/
  main.py                  FastAPI app, CORS, router registration
  models.py                Pydantic v2 models
  storage.py               JSONStorage: atomic, UUID-validated reads and writes
  routers/
    comparison.py          Decisions, analyses, criteria, weights
    progress.py            SSE progress stream
    monitoring.py          Usage and daily spend
    health.py              Readiness probe
  services/
    orchestrator.py        run_analysis(): routes the quick / medium / deep pipeline
    suggest_tier.py        Tier suggestion (Claude Haiku)
    claude_service.py      Criteria generation (Claude Sonnet, prompt caching)
    hidden_service.py      Hidden property discovery (Deep only)
    criteria_service.py    Go deeper, regenerate weights
    tavily_service.py      Web search with a 24h in-memory cache
    spending_guard.py      Daily cost cap
    usage_tracker.py       Token and cost tracking, persisted to disk
    progress_store.py      In-memory SSE message buffer
    config.py              API client singletons

Frontend

frontend/src/
  App.tsx                  Single page; hash routing (#decisionId:analysisId); history sidebar
  lib/api.ts               apiGet / apiPost helpers and all domain types
  lib/scoring.ts           Client-side weighted scoring
  features/
    comparison/            DecisionForm: create decision, then start analysis
    progress/              SSE consumer and progress view
    results/               Winner card, criteria, hidden properties; polling hook
    history/               Past decisions in the sidebar

Design Decisions

  • No database. JSON files keep the stack free of infrastructure; atomic writes prevent corruption on crash.
  • Async with polling. Analyses take seconds to minutes, so the API answers 202 at once and the client polls (with SSE for progress messages).
  • Deduplication. Starting an analysis at a tier that already has a completed analysis for the same decision returns the existing analysis_id instead of spending again.
  • Spending guard. A daily cap (default $5, DAILY_SPEND_CAP_USD) rejects new analyses with 429 once reached; requests that match a completed analysis still get it back, since they cost nothing. It rebuilds today's total from usage_metrics.json on startup, so it survives restarts. Calls made with LLM_BACKEND=cli are not counted (see above). Limits: only starting an analysis checks the cap, so go-deeper, regenerate-weights and the tier-suggestion call on every new decision are counted but still run past it; Tavily usage is not counted; analyses already running can overshoot it.
  • Confidence. On Medium and Deep, Claude labels each cited source as peer-reviewed, government, industry, news or blog (stored in source_quality), and code grades each criterion from its sources: high needs at least 2 sources including a peer-reviewed or government one; medium needs at least 1 source of news quality or better (industry or news, or a single peer-reviewed or government source); anything else (no sources, or only blog or unclassified sources) is low. Only URLs from the web research count. Go-deeper criteria are graded the same way. Quick-tier criteria (no sources) and user-added criteria are not graded (confidence: null): they get no badge and don't count toward the winner card's overall confidence, which is the share of graded criteria rated high. Quick results show "Based on AI knowledge only, no sources" instead of a badge. Analyses saved before this change keep their old low grades. Deep-tier hidden properties get their confidence from Claude.

API Endpoints

The web UI uses the decision, analysis and progress endpoints. The weights, criteria, go-deeper and regenerate-weights endpoints are API-only for now. Full schemas are at http://localhost:8000/docs while the backend runs.

Decisions and analyses

Method Path Description
POST /api/decisions Create a decision; returns decision_id, suggested_tier, rationale
GET /api/decisions List decisions, newest first
POST /api/decisions/{decision_id}/analyses Start an analysis at a tier (202 + analysis_id)
GET /api/decisions/{decision_id}/analyses List a decision's analyses
GET /api/decisions/{decision_id}/analyses/{analysis_id} Get a result; poll until status != "processing"
GET /api/progress/{analysis_id} SSE stream (progress / complete / error events)

Refining a result (API-only)

Method Path Description
PATCH .../analyses/{analysis_id}/weights Persist weight overrides
POST .../analyses/{analysis_id}/go-deeper Add criteria from cached research (Medium/Deep)
POST .../analyses/{analysis_id}/regenerate-weights Let Claude re-weight the criteria
POST .../analyses/{analysis_id}/criteria Add a criterion
PATCH .../analyses/{analysis_id}/criteria/{criterion_id} Edit a criterion
DELETE .../analyses/{analysis_id}/criteria/{criterion_id} Delete a criterion

... is /api/decisions/{decision_id}.

Ops

Method Path Description
GET /health Readiness check
GET /api/monitoring Token usage and cost stats (unauthenticated; see Security)

Tech Stack

Layer Technology Version
Backend Python 3.11+
FastAPI 0.128
Pydantic 2.12
Anthropic SDK 0.75
Tavily SDK 0.7
Frontend React 19
Vite 7
TypeScript 5.9
TanStack Query 5
Tailwind CSS 4

Development

# Backend tests. Most mock Claude and Tavily; a few live tests also call the real
# Anthropic API when ANTHROPIC_API_KEY is set, so unset it to stay offline.
cd backend
pip install -r requirements.txt -r requirements-dev.txt
pytest tests/

# Frontend
cd frontend
npx tsc -b         # type check (tsconfig.json is references-only; plain `tsc --noEmit` checks nothing)
npm run lint
npm test           # vitest
npm run build      # output in frontend/dist/

License

MIT

About

AI-powered decision analysis tool with N options, three depth tiers, and hidden systemic impact discovery

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages