# Project export: AuData - AI Copilot for Scientific Integrity

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: An AI research-integrity auditor that catches statistical errors, numerical inconsistencies, manipulated figures, citation problems, and overstated claims before they mislead science.
- Devpost: https://devpost.com/software/audata
- GitHub: https://github.com/haile-teshome/AuData
- Video: https://www.youtube.com/embed/V6_vP_2T528?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — nikita-gounder (11 commits), Esther (10 commits), Claude Opus 4.8 (1M context) (6 commits)

## Devpost submission (written by the team)

### Inspiration

Science runs on trust, but that trust is getting harder to verify by hand. Some of the most consequential errors of the last two decades hid in plain sight: the 2006 Nature paper that helped anchor the amyloid hypothesis of Alzheimer's disease was found in 2022 to contain manipulated figure panels, after steering years of work and enormous funding. Stanford's president stepped down in 2023 over image-integrity problems in his labs' papers, and in 2024 Dana-Farber moved to correct or retract dozens of studies flagged for duplicated images. These are not one-offs. The automated tool statcheck found that roughly half of psychology papers contain at least one statistical reporting inconsistency, and Retraction Watch now tracks more than 50,000 retractions. At the same time, the volume of research has outrun our ability to read it. Millions of papers appear every year, PubMed alone adds over a million, and generative AI is accelerating both legitimate writing and outright fabrication, including industrial-scale paper mills. Peer reviewers are unpaid and overstretched, and no human can realistically recompute every p-value, re-derive every meta-analysis, or visually compare every figure against the prior literature. The community is starting to respond: venues like ICLR and NeurIPS have begun piloting AI-assisted peer review, and research shows large language models can already give useful, structured feedback on manuscripts. AuData was built for exactly this gap. Small inconsistencies, a wrong p-value, an impossible sample size, a misleading citation, a duplicated figure panel, quietly corrode the scientific record, and catching them at the scale science now operates demands machines working alongside human reviewers.

### What it does

AuData is a multimodal research-integrity auditor for biomedical papers and preprints. Point it at a study and a team of detection agents go to work: they recompute reported statistics from their test values, recreate meta-analyses, cross-check internal numbers (sums, percentages, table-vs-text), screen figures for cloning, splicing, and cross-paper reuse, test whether the conclusions are actually supported by the methods, and verify every citation for retractions and errors. Each finding comes back prioritized by severity and linked to exact evidence, with a one-click jump to the highlighted spot in the PDF, so a reviewer can see precisely what was flagged and why. Everything consolidates into a downloadable audit report. The point is to turn research integrity from a manual spot-check into a structured, repeatable audit.

### How we built it

AuData is a modular multi-agent pipeline behind a standalone FastAPI service. It uses Claude for reasoning-heavy checks and routes lighter and vision work to local models, with Redis powering session-aware caching, semantic caching, vector search for cross-paper figure matching, and long-term agent memory. Paper ingest handles PDF parsing, full-text extraction, section/table/figure detection, and clean metadata resolution, with Browserbase as a fallback fetcher for hard-to-reach sources. On top of that sit the specialized detectors: a statistics engine for p-value recomputation and meta-analysis recreation, numerical-consistency checks, reference integrity, methods-versus-claims analysis, and image forensics (ELA, copy-move detection, splice detection, perceptual hashing, and CLIP-embedding cross-paper comparison). A persistence layer ties every flag back to exact paper evidence so nothing is a black box, and we exposed the auditor as a Band agent so it can collaborate with other agents in a shared room.

### Challenges we ran into

Research integrity is not one problem, it is many. Statistical recomputation, citation verification, figure comparison, and methods-claims review each fail in different ways and demand different evidence and different confidence thresholds, so there is no single model or metric that covers them. The harder challenge was trust: every flag had to be inspectable, grounded in the source paper, and framed as reviewer assistance rather than an automated accusation. We spent a lot of time and effort making findings point to a precise line, figure, or reference instead of an opaque score.

### Accomplishments we're proud of

We brought five distinct integrity checks into one coherent, multimodal workflow instead of five disconnected scripts, end to end from ingest to a downloadable report. The image forensics surfaces the actual figure with the suspicious region highlighted, the statistical checks show the full recomputation, and every flag carries a locator back into the PDF. Most of all, the whole system is built around transparency and human judgment, which is the only responsible way to ship a research-integrity tool.

### What we learned

Building AuData showed us that research integrity is really a systems problem. It requires combining document parsing, retrieval, statistical validation, image analysis, and semantic reasoning in a way that is precise, practical, and understandable. We also learned that the most important part is not just detecting a possible issue, but presenting it with enough evidence and context for a reviewer to make a defensible judgment.

### What's next

for AuData We want to deepen each agent and close the loop. That means stronger statistical and numerical recomputation, richer cross-paper analysis using related-work and same-author figure comparison, and reviewer-decision calibration, where the system learns from past accept/dismiss decisions (via agent memory) to reduce noise over time. We also want to harden the reporting layer so any flag can be exported with clear severity, confidence, and evidence links for researchers, reviewers, and journals.

## README (from the GitHub repository)

# AuData — Biomedical Research-Integrity Auditor

AuData audits a **single paper or preprint** for statistical errors, numerical
inconsistencies, figure manipulation, methods-vs-claim mismatches, and
citation/reference problems — then surfaces **prioritized, calibrated,
evidence-linked flags** through a human review surface. The framing is
**reviewer-assist, never automated accusation**: a human stays in the loop on
every flag.

AuData is built on the **Evidence Engine** systematic-review platform as a
template. The reusable parts (LLM dispatcher, literature APIs, statistics
engine, session store, SSE streaming, decision/report UI) are kept intact; the
review workflow is repurposed into an audit pipeline.

## Audit pipeline

```
Manage → Ingest → Detect → Reliability → Report
```

Each tab in the app is currently a **placeholder** carrying the spec for the
feature it will become (inputs, outputs, the template modules it reuses, and
what's left to build). We build them one at a time.

| Stage | Tab | Feature |
|-------|-----|---------|
| Manage | Dashboard | Pipeline overview |
| Manage | Audits | Paper-under-audit projects, versions, shared review |
| Ingest | Ingest | Parse structure, stats, tables, figures, references; version diff |
| Detect | Statistical Recompute | Recompute reported statistics, flag mismatches |
| Detect | Numerical Consistency | Internal-number / total / percentage / table checks |
| Detect | Image Forensics | Figure manipulation, duplication, AI-generation |
| Detect | Methods ↔ Claims | Conclusions vs. methods/results support |
| Detect | Reference Integrity | Resolve, verify, retraction-check citations |
| Reliability | Reliability Layer | Per-flag calibration, abstention, conclusion-impact triage |
| Reliability | Flag Review | Human-in-the-loop accept / dismiss / needs-human |
| Report | Audit Report | Structured report (PDF + JSON) with severity/confidence/evidence |

## Reuse / Adapt / Add (against the actual codebase)

**Reuse directly**
- **LLM dispatcher** — `AIService.get_model*` in `Backend/utils.py` (provider-agnostic: Claude, OpenAI, Gemini, Ollama via LangChain).
- **Literature APIs** — `Backend/data_services.py` (Crossref, Semantic Scholar, OpenAlex, Europe PMC, PubMed, arXiv/bioRxiv/medRxiv, …) for citation resolution, reference checks, and version fetch.
- **Statistics engine** — `Backend/meta_analysis.py` (pure-numpy effect sizes, pooling, heterogeneity, Egger/Begg, etc.) for stats-recompute.
- **Frontend shell + SSE + decision UI** — `src/app/` session store, the `/api/simulation/agentic/stream` SSE pattern, and the screening-decision table → per-flag triage.

**Adapt**
- Extraction service (`AITableExtractor`, `/api/extract/text`) → extractor of reported stats / Ns / claims.
- Session model (PICO, papers, decisions, extractions) → (paper-under-audit + versions, flags, decisions, labels).
- Supabase KV store (`supabase/`) → extend namespaces with flags, labels, versions, reports.
- PRISMA/export UI (`PrismaFlow.tsx`, `docx`) → structured audit report.

**Add (new builds — not present in the template)**
- Detection agents: stats-recompute, numerical-consistency, image-forensics, methods-claims, reference-integrity.
- **Reliability layer**: per-flag calibration (Platt/isotonic — note: *no* calibration code or sklearn exists in the template yet) + abstention + conclusion-impact triage.
- Full-PDF parsing (GROBID + PyMuPDF), figure + coordinate maps, table parsing (pdfplumber/Camelot).
- Image forensics (OpenCV / ELA / perceptual hashing / embeddings / AI-figure detection).
- Recompute sandbox (E2B / Modal), scipy/statsmodels.
- Retraction Watch checks + preprint version diff.
- Sponsor integrations: Fetch uAgents, Redis (vector/memory/cache/queue), Terac (labeling + calibration fine-tune), Arize (tracing/evals/calibration curves), Browserbase (web fetch), Sentry (errors).

## Stack

- **Frontend**: React + TypeScript + Vite, shadcn/Radix + Tailwind, REST + SSE.
- **Backend**: a standalone AuData FastAPI service (`Backend/audata/`), separate from the legacy Evidence Engine app (`Backend/api.py`).
- **LLM serving**: Ollama local models + cloud (Claude/GPT/Gemini) for reasoning-heavy steps.
- **Storage**: Redis for short-term session storage/cache (set `REDIS_URL`; falls back to in-memory if unset), SQLite for long-term persistence (`Backend/audata.db`) — both separate from Evidence Engine's Supabase. Browserbase for web fetch; biomedical literature APIs (Crossref/OpenAlex/Unpaywall/…).

## Quick start

One command installs an isolated environment (Backend Python venv + frontend
`node_modules`), configures `Backend/.env`, and launches both services:

```bash
./setup.sh                 # frontend :5173, backend :8010 (health-checked)
./setup.sh --with-models   # also install Ollama + pull local LLMs (~9 GB)
./teardown.sh              # stop everything it started
```

- Open **http://localhost:5173**. API docs at **http://localhost:8010/docs**.
- Re-running `./setup.sh` is idempotent. Override ports with
  `BACKEND_PORT=8011 FRONTEND_PORT=5174 ./setup.sh`.
- Local LLMs are **opt-in** — the placeholder app runs without them. To enable
  AI, either add a cloud key (`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` /
  `GEMINI_API_KEY`) to `Backend/.env` and pick the model in the sidebar, or run
  `./setup.sh --with-models`.

## Develop (manual)

```bash
# Frontend
pnpm install
pnpm dev          # Vite dev server (http://localhost:5173 → proxies /api to :8010)
pnpm typecheck
pnpm build

# Backend — the AuData service (in its venv)
cd Backend && python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn audata.main:app --port 8010
```

Copy `Backend/.env.example` → `Backend/.env` and fill what you need: `ENTREZ_EMAIL`
(polite pool for Crossref/OpenAlex/Unpaywall), `BROWSERBASE_API_KEY` /
`BROWSERBASE_PROJECT_ID` (URL fetch), `REDIS_URL` (short-term storage; optional —
falls back to in-memory), and a model key (`ANTHROPIC_API_KEY` / …) for AI steps.

The legacy Evidence Engine FastAPI app still lives at `Backend/api.py` for
reference but is **not** started by AuData.


## Detected evidence (automated analysis)

Indexed codebase: 154 recognized source files, 1739 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- LangChain (technology) — detected in the code
- Ollama (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- SQL (language) — detected in the code
- Streamlit (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 163)

```
.env.example
.gitignore
ATTRIBUTIONS.md
auditor/__init__.py
auditor/__main__.py
auditor/claims.py
auditor/cli.py
auditor/core.py
Backend/.env.example
Backend/api.py
Backend/app.py
Backend/audata/__init__.py
Backend/audata/agent_memory.py
Backend/audata/agents.py
Backend/audata/band_agent.py
Backend/audata/band.py
Backend/audata/browserbase_fetch.py
Backend/audata/browserbase.py
Backend/audata/crosspaper_comparator.py
Backend/audata/dataset_audit.py
Backend/audata/fulltext.py
Backend/audata/image_forensics.py
Backend/audata/image_vector_store.py
Backend/audata/imageforensicsagents.py
Backend/audata/ingest.py
Backend/audata/langcache.py
Backend/audata/llm.py
Backend/audata/main.py
Backend/audata/meta_analysis.py
Backend/audata/methods_claims.py
Backend/audata/numerical.py
Backend/audata/paperclip_image_comparator.py
Backend/audata/reference_integrity.py
Backend/audata/report_doc.py
Backend/audata/settings.py
Backend/audata/storage.py
Backend/browserbase_input.py
Backend/config.py
Backend/data_services.py
Backend/image_auditor_agent.py
Backend/leads_screening.py
Backend/meta_analysis.py
Backend/models.py
Backend/packages.txt
Backend/README.md
Backend/requirements.txt
Backend/run_api.sh
Backend/state_manager.py
Backend/streamlit_shim.py
Backend/ui_components.py
Backend/utils.py
default_shadcn_theme.css
docs/HOW_IT_WORKS.md
index.html
package.json
pnpm-workspace.yaml
postcss.config.mjs
README.md
samples/make_audata_demo.py
samples/README.md
setup_supabase.sh
setup.sh
src/app/App.tsx
src/app/components/AnalysisProgress.tsx
src/app/components/AuthScreen.tsx
src/app/components/ConflictsSection.tsx
src/app/components/CorpusHeatmap.tsx
src/app/components/InterraterReliability.tsx
src/app/components/PdfHighlightViewer.tsx
src/app/components/PicoCards.tsx
src/app/components/PrismaFlow.tsx
src/app/components/ProjectScreeningBar.tsx
src/app/components/QueryDiff.tsx
src/app/components/SessionsPanel.tsx
src/app/components/Sidebar.tsx
src/app/components/TaskProgressCard.tsx
src/app/components/ui/accordion.tsx
src/app/components/ui/alert-dialog.tsx
src/app/components/ui/alert.tsx
src/app/components/ui/aspect-ratio.tsx
src/app/components/ui/avatar.tsx
src/app/components/ui/badge.tsx
src/app/components/ui/breadcrumb.tsx
src/app/components/ui/button.tsx
src/app/components/ui/calendar.tsx
src/app/components/ui/card.tsx
src/app/components/ui/carousel.tsx
src/app/components/ui/chart.tsx
src/app/components/ui/checkbox.tsx
src/app/components/ui/collapsible.tsx
src/app/components/ui/command.tsx
src/app/components/ui/context-menu.tsx
src/app/components/ui/dialog.tsx
src/app/components/ui/drawer.tsx
src/app/components/ui/dropdown-menu.tsx
src/app/components/ui/form.tsx
src/app/components/ui/hover-card.tsx
src/app/components/ui/input-otp.tsx
src/app/components/ui/input.tsx
src/app/components/ui/label.tsx
src/app/components/ui/menubar.tsx
src/app/components/ui/navigation-menu.tsx
src/app/components/ui/pagination.tsx
src/app/components/ui/popover.tsx
src/app/components/ui/progress.tsx
src/app/components/ui/radio-group.tsx
src/app/components/ui/resizable.tsx
src/app/components/ui/scroll-area.tsx
src/app/components/ui/select.tsx
src/app/components/ui/separator.tsx
src/app/components/ui/sheet.tsx
src/app/components/ui/sidebar.tsx
src/app/components/ui/skeleton.tsx
src/app/components/ui/slider.tsx
src/app/components/ui/sonner.tsx
src/app/components/ui/switch.tsx
src/app/components/ui/table.tsx
src/app/components/ui/tabs.tsx
src/app/components/ui/textarea.tsx
src/app/components/ui/toggle-group.tsx
[43 more files omitted for size]
```

### Dependencies

- Backend/requirements.txt: agent-memory-client@>=0.1.0, anthropic@==0.34.1, band-sdk[anthropic]@>=1.0.0, beautifulsoup4@==4.12.3, biopython@>=1.85, browserbase@==1.13.0, certifi@==2024.8.30, fastapi@==0.115.0, google-generativeai, graphviz@==0.20.1, ImageHash@>=4.3.1, langcache@>=0.1.0, langchain@==0.3.0, langchain-anthropic@==0.2.0, langchain-core@==0.3.0, langchain-google-genai, langchain-ollama@==0.2.0, langchain-openai@==0.2.0, lxml@==5.3.0, numpy@==1.26.4, ollama@==0.3.3, openai@==1.52.2, opencv-python-headless@==4.10.0.84, packaging@==24.1, pandas@==2.2.2, playwright@==1.60.0, pydantic@==2.9.2, pymupdf@==1.27.2.3, pypdf@==4.3.1, python-docx@==1.1.2, python-dotenv@==1.0.1, python-multipart@==0.0.9, redis@==8.0.0, requests@==2.32.3, scipy@==1.14.1, sentry-sdk[fastapi]@>=2.0.0, streamlit@==1.42.0, uagents@>=0.13.0, urllib3@==1.26.19, uvicorn[standard]@==0.31.0
- package.json: @emotion/react@11.14.0, @emotion/styled@11.14.1, @mui/icons-material@7.3.5, @mui/material@7.3.5, @popperjs/core@2.11.8, @radix-ui/react-accordion@1.2.3, @radix-ui/react-alert-dialog@1.1.6, @radix-ui/react-aspect-ratio@1.1.2, @radix-ui/react-avatar@1.1.3, @radix-ui/react-checkbox@1.1.4, @radix-ui/react-collapsible@1.1.3, @radix-ui/react-context-menu@2.2.6, @radix-ui/react-dialog@1.1.6, @radix-ui/react-dropdown-menu@2.1.6, @radix-ui/react-hover-card@1.1.6, @radix-ui/react-label@2.1.2, @radix-ui/react-menubar@1.1.6, @radix-ui/react-navigation-menu@1.2.5, @radix-ui/react-popover@1.1.6, @radix-ui/react-progress@1.1.2, @radix-ui/react-radio-group@1.2.3, @radix-ui/react-scroll-area@1.2.3, @radix-ui/react-select@2.1.6, @radix-ui/react-separator@1.1.2, @radix-ui/react-slider@1.2.3, @radix-ui/react-slot@1.1.2, @radix-ui/react-switch@1.1.3, @radix-ui/react-tabs@1.1.3, @radix-ui/react-toggle@1.1.2, @radix-ui/react-toggle-group@1.1.2, @radix-ui/react-tooltip@1.1.8, @sentry/react@^10.59.0, @supabase/supabase-js@^2.105.4, @tailwindcss/vite@4.1.12, @types/react@18.3.12, @types/react-dom@18.3.1, @vitejs/plugin-react@4.7.0, canvas-confetti@1.9.4, class-variance-authority@0.7.1, clsx@2.1.1, cmdk@1.1.1, date-fns@3.6.0, docx@^9.7.1, embla-carousel-react@8.6.0, exceljs@^4.4.0, input-otp@1.4.2, lucide-react@0.487.0, motion@12.23.24, next-themes@0.4.6, pdfjs-dist@^5.7.284, react@18.3.1, react-day-picker@8.10.1, react-dnd@16.0.1, react-dnd-html5-backend@16.0.1, react-dom@18.3.1, react-hook-form@7.55.0, react-popper@2.3.0, react-resizable-panels@2.1.7, react-responsive-masonry@2.7.1, react-router@7.13.0, react-slick@0.31.0, recharts@2.15.2, sonner@2.0.3, tailwind-merge@3.2.0, tailwindcss@4.1.12, tw-animate-css@1.3.8, typescript@5.5.4, vaul@1.1.2, vite@6.3.5

### Recent commits (newest first)

- Merge remote-tracking branch 'origin/main' into feature_integration
- Redis: reuse a single bounded-pool client (fix connection-cap exhaustion)
- Restore Audits, Dashboard, Ingest as separate sidebar nav items
- Merge feature_NumericalConsistency: resolve conflicts keeping image forensics + llm/chat endpoint
- Add /api/llm/chat endpoint, route numerical consistency through backend, combine sidebar nav
- docs: add How It Works explainer (overall + per-tab)
- Remove stats row from landing page
- Merge branch 'main' of https://github.com/haile-teshome/AuData
- Merge branch 'feature_NumericalConsistency'
- Add landing page with cursor spotlight, animated auditors, stats row, and Enter key shortcut
- Show only Splice Detection analysis, remove Copy-Move Detection duplication
- Sentry: full observability (tracing + profiling + logs + frontend replay)
- Remove duplicate image display in copy-move detection
- Display forensics overlay images inline instead of as links
- Remove ELA Analysis overlay link
- Add API endpoint to serve forensics images; update frontend to use API URLs instead of file paths
- Numerical Consistency: show source numbers + the calculation per flag
- Numerical Consistency: per-check subsections with jump links + prominent source
- Fix detector results vanishing (Methods/Claims and others)
- Image Forensics: native always-on VLM, drop toggle + figure-count note; swap nav order

## Key source files (fetched from GitHub, selected and truncated for size)

### ATTRIBUTIONS.md

```markdown
This project includes components from [shadcn/ui](https://ui.shadcn.com/) used under the [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).

This project includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).

```

### docs/HOW_IT_WORKS.md

```markdown
# AuData: How It Works

AuData is an AI research-integrity auditor for biomedical papers and preprints. You give it one paper; a team of detection agents recompute its statistics, cross-check its internal numbers, screen its figures for manipulation and reuse, test whether its conclusions are supported, and verify its citations. Every finding comes back with a severity, the evidence behind it, and a one-click jump to the exact spot in the source PDF.

The guiding principle is **reviewer assistance, not an automated verdict**. AuData surfaces leads with their evidence so a human can make a defensible call. It never accuses, ranks authors, or publishes a score.

---

## The big picture

**Flow:** Ingest a paper, run the detectors (individually or all at once), review the evidence-linked flags, and export a consolidated report.

**Architecture:**
- **Frontend** (React + Vite + Tailwind/shadcn). A keep-alive tab shell so switching tabs is instant and per-paper results persist across tab changes and refreshes.
- **Backend** (FastAPI, port 8010). A standalone service with paper ingest, the detection agents, persistence, and reporting.
- **Task-aware LLM router.** Heavy reasoning goes to Claude; extraction, vision, and embeddings run on local models via Ollama. Every model is env-overridable. Calls are cached.
- **Redis** powers the KV/response cache, semantic caching (LangCache), figure vector search (RediSearch KNN), and long-term agent memory.
- **Observability:** Sentry captures errors, performance traces (frontend-to-backend distributed), profiling, logs, and session replay.
- **Agent interfaces:** the auditor is exposed as a Band agent (agent-to-agent mesh) and a Fetch uAgent.

**Persistence.** Each detector's results are saved per paper (Redis + SQLite) and rehydrate automatically, so a paper you audited earlier reopens with all its findings intact.

**Evidence linking.** Wherever possible a finding carries a verbatim quote and a locator that opens the PDF highlighted on the exact statistic, figure, or reference.

---

## The tabs

### Dashboard (command center)
The hub for the paper under audit. Run every detector from one place with **Run all**, or run/re-run any single detector, watch progress, and see each detector's status (idle, running, clean, or flagged) with its flag count. From here you jump to the Audit Report. Running a detector here populates its tab and persists the result.

### Ingest
Brings a paper in four ways: upload a PDF, pull by DOI, search by name, or fetch any paper URL. It resolves clean metadata and pulls full text through an open-access ladder (Europe PMC, PMC, Unpaywall, arXiv), with **Browserbase** as a headless-browser fallback for hard-to-reach sources. It parses the PDF into sections, tables, and figures, and flags retraction status. Everything downstream reads from this one normalized paper object.

### Numerical Consistency
Checks the paper's internal numbers across **six checks**, each with its own subsection on 
[truncated — 5261 more characters]
```

### package.json

```
{
  "name": "audata-platform",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@emotion/react": "11.14.0",
    "@emotion/styled": "11.14.1",
    "@mui/icons-material": "7.3.5",
    "@mui/material": "7.3.5",
    "@popperjs/core": "2.11.8",
    "@radix-ui/react-accordion": "1.2.3",
    "@radix-ui/react-alert-dialog": "1.1.6",
    "@radix-ui/react-aspect-ratio": "1.1.2",
    "@radix-ui/react-avatar": "1.1.3",
    "@radix-ui/react-checkbox": "1.1.4",
    "@radix-ui/react-collapsible": "1.1.3",
    "@radix-ui/react-context-menu": "2.2.6",
    "@radix-ui/react-dialog": "1.1.6",
    "@radix-ui/react-dropdown-menu": "2.1.6",
    "@radix-ui/react-hover-card": "1.1.6",
    "@radix-ui/react-label": "2.1.2",
    "@radix-ui/react-menubar": "1.1.6",
    "@radix-ui/react-navigation-menu": "1.2.5",
    "@radix-ui/react-popover": "1.1.6",
    "@radix-ui/react-progress": "1.1.2",
    "@radix-ui/react-radio-group": "1.2.3",
    "@radix-ui/react-scroll-area": "1.2.3",
    "@radix-ui/react-select": "2.1.6",
    "@radix-ui/react-separator": "1.1.2",
    "@radix-ui/react-slider": "1.2.3",
    "@radix-ui/react-slot": "1.1.2",
    "@radix-ui/react-switch": "1.1.3",
    "@radix-ui/react-tabs": "1.1.3",
    "@radix-ui/react-toggle": "1.1.2",
    "@radix-ui/react-toggle-group": "1.1.2",
    "@radix-ui/react-tooltip": "1.1.8",
    "@sentry/react": "^10.59.0",
    "@supabase/supabase-js": "^2.105.4",
    "canvas-confetti": "1.9.4",
    "class-variance-authority": "0.7.1",
    "clsx": "2.1.1",
    "cmdk": "1.1.1",
    "date-fns": "3.6.0",
    "docx": "^9.7.1",
    "embla-carousel-react": "8.6.0",
    "exceljs": "^4.4.0",
    "input-otp": "1.4.2",
    "lucide-react": "0.487.0",
    "motion": "12.23.24",
    "next-themes": "0.4.6",
    "pdfjs-dist": "^5.7.284",
    "react-day-picker": "8.10.1",
    "react-dnd": "16.0.1",
    "react-dnd-html5-backend": "16.0.1",
    "react-hook-form": "7.55.0",
    "react-popper": "2.3.0",
    "react-resizable-panels": "2.1.7",
    "react-responsive-masonry": "2.7.1",
    "react-router": "7.13.0",
    "react-slick": "0.31.0",
    "recharts": "2.15.2",
    "sonner": "2.0.3",
    "tailwind-merge": "3.2.0",
    "tw-animate-css": "1.3.8",
    "vaul": "1.1.2"
  },
  "devDependencies": {
    "@tailwindcss/vite": "4.1.12",
    "@types/react": "18.3.12",
    "@types/react-dom": "18.3.1",
    "@vitejs/plugin-react": "4.7.0",
    "tailwindcss": "4.1.12",
    "typescript": "5.5.4",
    "vite": "6.3.5"
  },
  "peerDependencies": {
    "react": "18.3.1",
    "react-dom": "18.3.1"
  },
  "peerDependenciesMeta": {
    "react": {
      "optional": true
    },
    "react-dom": {
      "optional": true
    }
  },
  "pnpm": {
    "overrides": {
      "vite": "6.3.5",
      "@types/react": "18.3.12",
      "@types/react-dom": "18.3.1",
      "@types/react-dom>@types/react": "18.3.12"
    }
  }
}
```

### Backend/requirements.txt

```
# --- HTTP API layer (new) ---
fastapi==0.115.0
uvicorn[standard]==0.31.0
python-dotenv==1.0.1
python-multipart==0.0.9

# --- Frontend & UI (legacy Streamlit; kept for shim/compat) ---
streamlit==1.42.0
graphviz==0.20.1
beautifulsoup4==4.12.3
lxml==5.3.0

# --- AI & LLM (Conflict-Matched Versions) ---
langchain==0.3.0
langchain-core==0.3.0
langchain-openai==0.2.0
langchain-anthropic==0.2.0
langchain-ollama==0.2.0
langchain-google-genai
openai==1.52.2
anthropic==0.34.1
ollama==0.3.3
google-generativeai

# --- Data & Document Processing ---
pandas==2.2.2
biopython>=1.85
pypdf==4.3.1
pymupdf==1.27.2.3
python-docx==1.1.2
numpy==1.26.4
scipy==1.14.1
opencv-python-headless==4.10.0.84
ImageHash==4.3.1

# --- Critical Dependency Fixes ---
packaging==24.1
requests==2.32.3
urllib3==1.26.19
certifi==2024.8.30
pydantic==2.9.2
# --- Browser fetch (Browserbase) ---
browserbase==1.13.0
playwright==1.60.0

# --- AuData storage ---
redis==8.0.0
ImageHash>=4.3.1

# --- Sponsor integrations ---
sentry-sdk[fastapi]>=2.0.0          # error monitoring (set SENTRY_DSN)
uagents>=0.13.0                     # Fetch.ai agent layer (python -m audata.agents)
# sentence-transformers>=2.2.0      # OPTIONAL, large (~2GB w/ torch): enables Redis image vector search
langcache>=0.1.0                    # Redis LangCache semantic cache (set LANGCACHE_SERVER_URL + LANGCACHE_CACHE_ID)
agent-memory-client>=0.1.0         # Redis Agent Memory Server (set AGENT_MEMORY_BASE_URL)
band-sdk[anthropic]>=1.0.0        # Band agent-to-agent mesh (python -m audata.band_agent)

```

### src/main.tsx

```typescript
import { createRoot } from "react-dom/client";
import * as Sentry from "@sentry/react";
import App from "./app/App.tsx";
import { initSentry } from "./app/lib/sentry";
import "./styles/index.css";

initSentry();

createRoot(document.getElementById("root")!).render(
  <Sentry.ErrorBoundary fallback={<p style={{ padding: 24 }}>Something went wrong. The error has been reported.</p>}>
    <App />
  </Sentry.ErrorBoundary>,
);

```

### auditor/cli.py

```python
from __future__ import annotations

import argparse
import json
from pathlib import Path

from .core import audit_pdf, write_html_report, write_json_report, write_markdown_report


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Minimal PDF statistical auditor.")
    parser.add_argument("pdf", help="Path to the PDF to audit.")
    parser.add_argument(
        "--out-dir",
        default="auditor_output",
        help="Directory for JSON, Markdown, and HTML reports.",
    )
    parser.add_argument(
        "--tolerance",
        type=float,
        default=0.001,
        help="Absolute tolerance for exact reported p-values.",
    )
    return parser


def main(argv: list[str] | None = None) -> int:
    args = build_parser().parse_args(argv)
    pdf_path = Path(args.pdf)
    if not pdf_path.exists():
        raise SystemExit(f"PDF not found: {pdf_path}")

    out_dir = Path(args.out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)

    result = audit_pdf(pdf_path, tolerance=args.tolerance)
    write_json_report(result, out_dir / "findings.json")
    write_markdown_report(result, out_dir / "report.md")
    write_html_report(result, out_dir / "report.html")

    print(json.dumps(result, indent=2))
    print(f"\nWrote reports to {out_dir}")
    return 0

```

### src/app/App.tsx

```typescript
import { Component, ReactNode, useEffect, useState } from "react";
import { Sidebar } from "./components/Sidebar";
import { Toaster } from "./components/ui/sonner";
import { StoreProvider, useStore } from "./lib/store";
import { AuthProvider } from "./lib/auth";
import { UserMenu } from "./components/UserMenu";
import { NumericalPage } from "./pages/NumericalPage";
import { DashboardPage } from "./pages/DashboardPage";
import { AuditsPage } from "./pages/AuditsPage";
import { IngestPage } from "./pages/IngestPage";
import { ReferenceIntegrityPage } from "./pages/ReferenceIntegrityPage";
import { MethodsClaimsPage } from "./pages/MethodsClaimsPage";
import { ImageForensicsPage } from "./pages/ImageForensicsPage";
import { IngestService, AuditStore } from "./lib/apiClient";
import { RecomputePage } from "./pages/RecomputePage";
import { ReportPage } from "./pages/ReportPage";
import { LandingPage } from "./pages/LandingPage";
import { LayoutDashboard, Upload, Calculator, Hash, Image as ImageIcon, GitCompare, BookMarked, Gauge, ShieldCheck, FileText, Users } from "lucide-react";

const PAGE_META: Record<string, { title: string; subtitle: string; icon: any }> = {
  dashboard: { title: "AuData", subtitle: "Biomedical research-integrity auditor — overview", icon: LayoutDashboard },
  audits: { title: "Audits", subtitle: "Manage paper-under-audit projects, versions, and shared review", icon: Users },
  ingest: { title: "Ingest", subtitle: "Parse the paper: structure, statistics, tables, figures, references, versions", icon: Upload },
  recompute: { title: "Statistical Recompute", subtitle: "Recompute reported statistics and flag mismatches", icon: Calculator },
  numerical: { title: "Numerical Consistency", subtitle: "Cross-check internal numbers, totals, percentages, and tables", icon: Hash },
  imaging: { title: "Image Forensics", subtitle: "Screen figures for manipulation, duplication, and AI generation", icon: ImageIcon },
  methods: { title: "Methods ↔ Claims", subtitle: "Check that conclusions are supported by methods and results", icon: GitCompare },
  references: { title: "Reference Integrity", subtitle: "Resolve, verify, and retraction-check citations", icon: BookMarked },
  report: { title: "Audit Report", subtitle: "Consolidated findings across every detector for this study", icon: FileText },
};

function Shell() {
  const s = useStore();
  const meta = PAGE_META[s.page];
  const Icon = meta.icon;

  // Keep-alive routing: mount each page the first time it is visited, then keep
  // it mounted and just toggle visibility. Switching tabs becomes instant (no
  // remount, no re-fetch, no re-render of big lists) and per-tab state survives.
  const [visited, setVisited] = useState<Set<string>>(() => new Set([s.page]));
  useEffect(() => {
    setVisited((prev) => (prev.has(s.page) ? prev : new Set(prev).add(s.page)));
  }, [s.page]);
  const PAGES: { id: string; node: ReactNode }[] = [
    { id: "dashboard", node: <DashboardPage /> },
    { id: "audits", node: <AuditsPage /> },
    { id: "ingest", node: <IngestPage /> },
    { id: "recompute", node: <RecomputePage /> },
    { id: "numerical", node: <NumericalPage /> },
    { id: "imaging", node: <ImageForensicsPage /> },
    { id: "methods", node: <MethodsClaimsPage /> },
    { id: "references", node: <ReferenceIntegrityPage /> },
    { id: "report", node: <ReportPage /> },
  ];
  // If we landed on a /?invite=TOKEN URL, route to the Audits page so the
  // user sees the accept-invite banner. AuditsPage owns the actual accept flow.
  useEffect(() => {
    const url = new URL(window.location.href);
    if (url.searchParams.has("invite") && s.page !== "audits") {
      s.setPage("audits");
    }
    // We only want this to run once on mount; the audits page handles
    // subsequent state.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // On load, if no paper is in the store yet, restore the last one from the
  // server session (Redis) — so a refresh on any tab brings the paper back.
  useEffect(() => {
    if (s.paperUnderAudit) return;
    IngestService.restoreSession().then((p) => { if (p) s.setPaperUnderAudit(p); });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Whenever the paper under audit changes (open / restore), pull ALL of its
  // saved detection results from the server so every tab (incl. Dashboard)
  // shows them without having to visit each detector first.
  const paperId = s.paperUnderAudit?.id;
  useEffect(() => {
    if (!paperId) return;
    let cancelled = false;
    AuditStore.getAll(paperId).then((a) => {
      if (cancelled) return;
      if (a.references && !s.refAudits[paperId]) s.setRefAudits({ ...s.refAudits, [paperId]: a.references });
      if (a.methods && !s.methodsAudits[paperId]) s.setMethodsAudits({ ...s.methodsAudits, [paperId]: a.methods });
      if (a.meta && !s.metaAudits[paperId]) s.setMetaAudits({ ...s.metaAudits, [paperId]: a.meta });
      if (a.images && !s.imageAudits[paperId]) s.setImageAudits({ ...s.imageAudits, [paperId]: a.images });
      if (a.numerical && !s.numericalAudits[paperId]) s.setNumericalAudits({ ...s.numericalAudits, [paperId]: a.numerical });
      if (a.statcheck && !s.statcheckAudits[paperId]) s.setStatcheckAudits({ ...s.statcheckAudits, [paperId]: a.statcheck });
    });
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [paperId]);
  return (
    <div className="flex min-h-screen bg-background text-foreground">
      <Toaster richColors position="top-right" />
      <Sidebar />
      <main className="flex-1 overflow-x-clip">
        <header className="border-b bg-card/50 backdrop-blur sticky top-0 z-20 px-6 py-4">
          <div className="max-w-6xl mx-auto flex items-center gap-3">
            <Icon className="size-6 text-primary" />
            <div className="flex-1 min-w-0">
              <h1>{meta.title}</h1>
              <p className="text-sm text-muted-fo
[truncated — 2575 more characters]
```

### supabase/functions/make-server-7e4eb0f2/index.ts

```typescript
// @ts-nocheck — Deno edge function: Deno + npm:/jsr: specifiers are not
// resolvable by the project's browser-targeted tsconfig. Supabase compiles
// this file natively at deploy time.
import { Hono } from "npm:hono";
import { cors } from "npm:hono/cors";
import { logger } from "npm:hono/logger";
import { createClient } from "jsr:@supabase/supabase-js@2";
import * as kv from "./kv_store.ts";

const app = new Hono();
app.use("*", logger(console.log));
app.use(
  "/*",
  cors({
    origin: "*",
    allowHeaders: ["Content-Type", "Authorization"],
    allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    exposeHeaders: ["Content-Length"],
    maxAge: 600,
  }),
);

const supabaseAdmin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

async function authedUserId(c: any): Promise<string | null> {
  const token = c.req.header("Authorization")?.split(" ")[1];
  if (!token) return null;
  const { data, error } = await supabaseAdmin.auth.getUser(token);
  if (error || !data.user) return null;
  return data.user.id;
}

app.get("/make-server-7e4eb0f2/health", (c) => c.json({ status: "ok" }));

// Sign up
app.post("/make-server-7e4eb0f2/signup", async (c) => {
  try {
    const { email, password, name } = await c.req.json();
    if (!email || !password) return c.json({ error: "email and password required" }, 400);
    const { data, error } = await supabaseAdmin.auth.admin.createUser({
      email,
      password,
      user_metadata: { name: name || "" },
      // Auto-confirm since no email server is configured in this environment
      email_confirm: true,
    });
    if (error) {
      console.log(`Signup error for ${email}: ${error.message}`);
      return c.json({ error: error.message }, 400);
    }
    return c.json({ user: { id: data.user.id, email: data.user.email } });
  } catch (e) {
    console.log(`Signup unexpected error: ${e}`);
    return c.json({ error: `Signup failed: ${e}` }, 500);
  }
});

// List a user's saved sessions (metadata only)
app.get("/make-server-7e4eb0f2/sessions", async (c) => {
  const uid = await authedUserId(c);
  if (!uid) return c.json({ error: "Unauthorized" }, 401);
  try {
    const items = await kv.getByPrefix(`session:${uid}:`);
    const meta = (items || []).map((s: any) => ({
      id: s.id,
      title: s.title,
      updated_at: s.updated_at,
      created_at: s.created_at,
    })).sort((a: any, b: any) => (b.updated_at || "").localeCompare(a.updated_at || ""));
    return c.json({ sessions: meta });
  } catch (e) {
    console.log(`List sessions error for ${uid}: ${e}`);
    return c.json({ error: `Failed to list sessions: ${e}` }, 500);
  }
});

// Load a single session
app.get("/make-server-7e4eb0f2/sessions/:id", async (c) => {
  const uid = await authedUserId(c);
  if (!uid) return c.json({ error: "Unauthorized" }, 401);
  try {
    const id = c.req.param("id");
    const session = await kv.get(`session:${uid}:${id}`);
    if (!session) return c.json({ error: "Not found" }, 404);
    return c.json({ session });
  } catch (e) {
    console.log(`Load session error: ${e}`);
    return c.json({ error: `Failed to load session: ${e}` }, 500);
  }
});

// Save / update a session
app.put("/make-server-7e4eb0f2/sessions/:id", async (c) => {
  const uid = await authedUserId(c);
  if (!uid) return c.json({ error: "Unauthorized" }, 401);
  try {
    const id = c.req.param("id");
    const body = await c.req.json();
    const now = new Date().toISOString();
    const existing = await kv.get(`session:${uid}:${id}`);
    const session = {
      id,
      title: body.title || existing?.title || "Untitled session",
      data: body.data ?? {},
      created_at: existing?.created_at || now,
      updated_at: now,
    };
    await kv.set(`session:${uid}:${id}`, session);
    return c.json({ session });
  } catch (e) {
    console.log(`Save session error: ${e}`);
    return c.json({ error: `Failed to save session: ${e}` }, 500);
  }
});

// Delete a session
app.delete("/make-server-7e4eb0f2/sessions/:id", async (c) => {
  const uid = await authedUserId(c);
  if (!uid) return c.json({ error: "Unauthorized" }, 401);
  try {
    const id = c.req.param("id");
    await kv.del(`session:${uid}:${id}`);
    return c.json({ ok: true });
  } catch (e) {
    console.log(`Delete session error: ${e}`);
    return c.json({ error: `Failed to delete session: ${e}` }, 500);
  }
});

// =========================================================================
// Multi-reviewer projects
// -------------------------------------------------------------------------
// KV layout:
//   project:{pid}                                       → project metadata
//   project_member:{pid}:{uid}                          → role + joined_at
//   user_project:{uid}:{pid}                            → backlink (fast list-by-user)
//   project_papers:{pid}                                → array of paper records
//   decision:{pid}:{stage}:{paperId}:{uid}              → one reviewer's decision
//   adjudication:{pid}:{stage}:{paperId}                → adjudicator final call
//   invite:{token}                                      → invite record
// =========================================================================

type Role = "lead" | "reviewer" | "adjudicator" | "viewer";

async function getRole(pid: string, uid: string): Promise<Role | null> {
  const m = await kv.get(`project_member:${pid}:${uid}`);
  return m ? (m.role as Role) : null;
}

async function requireRole(c: any, pid: string, allowed: Role[]): Promise<{ uid: string; role: Role } | Response> {
  const uid = await authedUserId(c);
  if (!uid) return c.json({ error: "Unauthorized" }, 401);
  const role = await getRole(pid, uid);
  if (!role) return c.json({ error: "Not a project member" }, 403);
  if (!allowed.includes(role)) return c.json({ error: `Role '${role}' cannot perform this action` }, 403);
  return { uid, role };
}

function newId(prefix: string): string {
  const r =
[truncated — 18474 more characters]
```

### Backend/audata/main.py

```python
"""AuData FastAPI service — standalone, separate from Evidence Engine's api.py.

Endpoints:
  GET  /api/health                 — service + redis + db + browserbase status
  GET  /api/models/local           — local Ollama models (for the sidebar)
  POST /api/ingest/search          — Crossref search by name/title
  POST /api/ingest/fetch           — pull by DOI/title (+ Unpaywall, then Browserbase)
  POST /api/ingest/url             — fetch any URL via Browserbase
  POST /api/ingest/pdf             — parse an uploaded PDF
  GET  /api/session/{sid}/paper    — restore the paper under audit (Redis)
  GET  /api/audits                 — list persisted papers (SQLite)
Every ingest persists to SQLite (long-term) and, when a session id is given,
caches the paper under audit in Redis (short-term).
"""

from __future__ import annotations

import json as _json
import queue
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import re
import sys
from typing import Any, Dict, List, Optional

import requests
from fastapi import FastAPI, HTTPException, UploadFile, File, Header, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
from pydantic import BaseModel

from . import settings, ingest, browserbase_fetch, storage, fulltext, llm, dataset_audit
from . import reference_integrity as refint
from . import methods_claims as mc
from . import imageforensicsagents as imgforensics
from . import meta_analysis as ma
from . import numerical as nu
from . import report_doc
from . import band
from . import langcache
from . import agent_memory

# Sentry — full observability (errors + performance tracing + profiling + logs).
# Active only when SENTRY_DSN is set (no-op otherwise). PII stays off (we audit
# public papers, not personal data); tracing/profiling do not need it.
import os as _os
if _os.getenv("SENTRY_DSN"):
    try:
        import logging as _logging
        import sentry_sdk
        from sentry_sdk.integrations.logging import LoggingIntegration
        sentry_sdk.init(
            dsn=_os.getenv("SENTRY_DSN"),
            environment=_os.getenv("SENTRY_ENVIRONMENT", "demo"),
            release=_os.getenv("SENTRY_RELEASE", "audata@0.1.0"),
            # Performance: capture every transaction (route) and profile it.
            traces_sample_rate=float(_os.getenv("SENTRY_TRACES_SAMPLE_RATE", "1.0")),
            profiles_sample_rate=float(_os.getenv("SENTRY_PROFILES_SAMPLE_RATE", "1.0")),
            send_default_pii=False,
            attach_stacktrace=True,
            max_request_body_size="small",
            # Breadcrumbs for all INFO logs, Sentry issues for ERROR+ logs.
            integrations=[LoggingIntegration(level=_logging.INFO, event_level=_logging.ERROR)],
            # Sentry Structured Logs (sentry_sdk.logger.*).
            _experiments={"enable_logs": True},
        )
        sentry_sdk.set_tag("service", "audata-backend")
        print("[audata] Sentry initialized (tracing + profiling + logs).")
    except Exception as e:
        print(f"[audata] Sentry init failed: {e}")

app = FastAPI(title="AuData API", version="0.1.0")

_repo_root = str(Path(__file__).resolve().parents[2])
if _repo_root not in sys.path:
    sys.path.insert(0, _repo_root)

_origins = ["http://localhost:5173", "http://localhost:4173", "http://127.0.0.1:5173"] + settings.CORS_ORIGINS
app.add_middleware(
    CORSMiddleware, allow_origins=_origins, allow_credentials=True,
    allow_methods=["*"], allow_headers=["*"],
)


def _image_forensics_module():
    try:
        from . import imageforensicsagents
        return imageforensicsagents
    except ImportError as e:
        raise HTTPException(
            status_code=503,
            detail=f"Image forensics dependencies are not installed: {e}",
        )


@app.on_event("startup")
def _startup():
    storage.init_db()


# ── status ────────────────────────────────────────────────────────────────────

@app.get("/api/health")
def health():
    return {
        "ok": True, "service": "audata",
        "redis": storage.redis_status(),
        "db": storage.db_status(),
        "browserbase": {"configured": browserbase_fetch.available()},
    }


@app.get("/api/integrations")
def integrations():
    """Which sponsor integrations are active (configured) right now."""
    def _mod(name):
        import importlib.util
        return importlib.util.find_spec(name) is not None
    redis_st = storage.redis_status()
    return {
        "redis": {"active": redis_st.get("connected", False), **redis_st},
        "redis_vector": {"active": bool(_os.getenv("REDIS_URL") and _os.getenv("AUDATA_IMAGE_VECTORS")),
                          "deps": _mod("sentence_transformers"), "note": "needs REDIS_URL + sentence-transformers + AUDATA_IMAGE_VECTORS=1"},
        "anthropic": {"active": bool(_os.getenv("ANTHROPIC_API_KEY"))},
        "browserbase": {"active": browserbase_fetch.available()},
        "sentry": {"active": bool(_os.getenv("SENTRY_DSN")), "installed": _mod("sentry_sdk")},
        "token_router": {"active": bool(_os.getenv("TOKENROUTER_API_KEY") and _os.getenv("TOKENROUTER_BASE_URL")),
                         "key_set": bool(_os.getenv("TOKENROUTER_API_KEY")),
                         "note": "needs TOKENROUTER_BASE_URL (the OpenAI-compatible endpoint)"},
        "fetch_uagents": {"installed": _mod("uagents"), "active": bool(_os.getenv("FETCH_AGENT_MAILBOX")),
                          "note": "run `python -m audata.agents`; set FETCH_AGENT_MAILBOX for ASI:One"},
        "band": {"active": band.available(), "installed": _mod("band"),
                 "note": "register an agent at app.band.ai/agents; set BAND_AGENT_ID+BAND_API_KEY; run python -m audata.band_agent"},
        "langcache": {"active": langcache.available(), "installed": _mod("langcache"),
                      "note": "needs REDIS_LANGCACHE_API_KEY + LANGCACHE_SERVER_URL + LANGCACHE_CACHE_ID"},
   
[truncated — 39371 more characters]
```

### pnpm-workspace.yaml

```yaml
packages:
  - '.'
allowBuilds:
  '@tailwindcss/oxide': true
  esbuild: true

```

[134 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]