The public evaluation website is a static, synthetic snapshot. The live investigation workspace is GET /operator on a running FastAPI service. It reads PostgreSQL through authenticated API routes; it is not part of the GitHub Pages deployment.
Install dependencies with uv sync --extra dev. Set DATABASE_URL to a PostgreSQL URL, then run uv run python -m riskqueue.db.migrate. This works for a fresh database and for an existing installation made from sql/schema.sql. The migration preserves existing operational rows and marks their feature and score provenance legacy-unknown; it cannot retroactively recover the old feature inputs. Do not run sql/schema.sql on a new installation. docker compose up --build postgres migrate api dashboard runs the migration service before the API.
For local demonstration, SCORING_MODE=demo selects the deterministic development heuristic. Its output is marked development_heuristic and is not a validated probability. For trained scoring, set SCORING_MODE=trained and MODEL_ARTIFACT_DIR to a directory containing riskqueue.joblib and metadata with a matching feature_version. A configured but missing or mismatched artifact fails visibly. Rebuild older artifacts with the current training script to add feature-version metadata. API scoring and the worker use the same feature and scoring pipeline. Restart API workers to activate a new artifact at an existing path; an API process keeps its loaded artifact stable.
Generate an analyst token with python -c 'import secrets; print(secrets.token_urlsafe(32))'. Set ANALYST_TOKENS to a JSON object mapping that token to an analyst ID, for example {"YOUR_RANDOM_TOKEN":"analyst-id"}. Replace the example value; never commit a real token. Run uv run uvicorn riskqueue.api.main:app --host 127.0.0.1 --port 8000 and open http://127.0.0.1:8000/operator. The token remains in browser memory for that tab and must be entered again after reload. The server determines identity from the bearer token; case mutations are limited to the assigned analyst. Use TLS, secret-managed tokens, restricted database access, and an identity provider before exposing an operational API publicly.
The SQS worker starts with uv run python -m riskqueue.worker once PROCESSING_QUEUE_URL, object storage settings, database URL, and model settings are configured. The Docker Compose worker profile uses the same environment and waits for migrations. SQS messages are deleted only after processing commits; failures remain for retry.
For each incoming batch the worker validates transactions, excludes previously persisted transaction IDs, loads previously committed history for the involved senders and recipients, and scores all current rows through score_transactions. Earlier event hours can contribute to later hours. Equal-hour rows remain invisible to each other, including across batches. Out-of-order arrivals do not see later event hours; a late arrival can inform subsequent scores once committed. Each transaction stores its score, model/feature/policy version, review cost, loss fraction, scoring time, output kind, and feature snapshot.
Eligible transactions enter review_queue. A capacity_periods row sets the budget for each UTC processing date. Workers claim slots with a conditional database update inside the event transaction and lock backlog candidates in priority order. A budget conflict between workers is an error rather than an implicit capacity change. Overflow stays backlog and competes for capacity when another batch arrives on a later date; there is no background rollover scheduler. Claimed work creates exactly one case per transaction. Capacity counts review allocations, so resolving a case does not refund that day's slot. API /v1/review-queue remains a stateless policy preview over supplied rows and is not the operational capacity allocator.
An analyst opens /operator, enters their configured token, claims a queued case, reviews the stored inputs, records append-only notes, escalates or resolves it with a reason and disposition, then records an observed outcome. Disposition is the analyst's decision; outcome is separately recorded evidence. Recovered and prevented amounts have distinct fields and are never inferred from the transaction amount. The case audit log records actor, time, old and new values, and score provenance. The output shown in the case is a stored model output; feature values are inputs, not causal feature attributions.
POST /v1/replay accepts an availability-time range and up to eight capacity/threshold/cost policies. Capacity can be supplied directly or calculated from analysts × reviews_per_analyst; the latter is an explicit staffing assumption, not an observed productivity claim. Replay uses stored scores from a single model/feature version, processes batches in availability order, ranks each available batch together, and applies daily capacity and backlog without looking ahead to later batches. The result includes a hash of the exact stored dataset rows, policy inputs, selected IDs, backlog, and estimated review cost. Outcomes recorded and observed by the requested cutoff are used only to evaluate the selected cases after ranking. Precision is reported only for known resolved outcomes; unknown cases remain explicit. Cross-model replay, unbiased recall, prevented loss, and recovered value estimates are not supported by the current operational evidence.
Run uv run pytest -q, uv run ruff check ., uv run ruff format --check ., and the website commands in the root README. tests/test_migrations.py checks fresh creation and upgrade from a populated legacy schema with SQLite; run python -m riskqueue.db.migrate against a real PostgreSQL staging copy before production rollout. tests/test_operations.py checks actual feature values, API/worker score parity, duplicate/retry behavior, concurrent allocation attempts, and the 250-slot multi-batch invariant. tests/test_case_workflow.py exercises authenticated case persistence, notes, disposition, outcome, audit, and time-aware replay.
PostgreSQL, Snowflake, AWS SQS, and a public API hosting service are not provisioned by the GitHub Pages workflow. Historical feature retrieval is scoped to involved entities but still recomputes exact lifetime medians from their prior rows; high-volume entities need an incremental indexed summary or feature-state redesign. Snowflake MERGE updates rows when a retry or backlog rollover changes the operational decision, but PostgreSQL and Snowflake do not share one atomic transaction. A persistent cross-store failure still needs reconciliation.