# Project export: Veritas: Misinformation Detection

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: CruzHacks 2026
- Tagline: Veritas: Misinformation Detector is an AI-driven Google Chrome extension that analyzes webpages you're reading and identifies misinformation wherever they exist by one press of a button.
- Devpost: https://devpost.com/software/vertias-misinformation-detection
- GitHub: https://github.com/aram-ap/veritas-detect
- Demo: http://veritas-detect.vercel.app/
- Video: https://www.youtube.com/embed/KeHcvOCt3nA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Aram Aprahamian (62 commits), Claude Sonnet 4.5 (6 commits)

## Devpost submission (written by the team)

### Inspiration

The name Veritas comes from the Roman goddess of truth. I wanted to build an extension that tackles identifying misinformation in all shapes and forms and shows the truth. I took inspiration from Grammarly, the Grammar checking tool that many people have come to love. However, instead of grammar, my tool tackles media information. I specifically chose this thinking that it's a tool that could be highly effective for the people I care about and myself.

### What it does

Veritas identifys misleading or false claims in media, cross referencing with reputable sources, and provides a "Trust Score", a score of how trustworthy an article. The program highlights areas with misinformation or with concerning phrasing. The "Trust Score" is measured uupon misinformation, logical fallacies, bias, and more to give you a strong insight of how much to trust what you're reading. The misinformation algorithm inside highlights questionable or wrong sentences, explains why its wrong or provides insight into how it can mislead the reader.

### How we built it

Veritas is built upon Next.js and Vercel for the front end, React & Vue for the Extension, and Digital Ocean's database and web service for the backend and processing the misinformation detection api. For authentication we utilized Auth0 for its ease of use and built in google auth. For payment systems I implemented stripe (in sandbox mode). Initially, Veritas was going to use a combination of a predictive model based on a True/Fake news dataset with the help of Gemini, however that solution was not effective. Our program identifys misinformation by using gemini for identifying high impact information, i.e., informational claims that should be fact checked, strong language, and misleading terminology, in addition to a url database lookup to see if the information is from a known biased site. Gemini analyzes the flagged content for: Alongside gemini we implemented two systems for identifying factuality. The system triages each verifiable claim into two routes: Route A: Historical Facts This route applies for articles with no recent keywords (i.e., 'Today,', 'Last Month', etc) and for older publishing dates. Uses Google Fact Check API Searches databases (Snopes, Politifact, FactCheck.org, etc.) Returns: Verified | False | Misleading | Unverified Route B: Recent Facts/Breaking news Indicators: "today", "breaking", "just now", current year mentions Uses Google Custom Search API Searches trusted news sources (Reuters, AP, BBC, NPR, etc.) Cross checks article information with trusted sources. Calculates credibility score based on consensus: 3+ trusted sources = 0.9 (Verified) 2 sources = 0.7 (Moderate) 1 source = 0.4 (Single Source) 0 sources = 0.1 (Unsubstantiated) 3+ trusted sources = 0.9 (Verified) 2 sources = 0.7 (Moderate) 1 source = 0.4 (Single Source) 0 sources = 0.1 (Unsubstantiated) After this stage, the algorithm calculates penalties as such: Penalty Tiers: False claims: -25 points (scaled), label -> "Likely Fake" Misleading: -15 points per claim, label -> "Suspicious" Unsubstantiated: -8 points per claim (warning only) Verified claims: Boost to 80+ if many verified After analysis of the article is completed, the data is cached and stored so reopening the webpage reopens the previously analyzed information. Recalculating everything every time you open the page would have been very annoying and be terrible for the environment. Additionally, as you may have assumed with the inclusion of Auth0 and Stripe, I implemented a payment model to match the type of application you would likely see in the real world. I believed a subscription model fit best for this applciation. However, don't worry about paying if you would like to try this out. I'll leave a discount code valid for a month and 20 articles a day. Just send me a quick email and say hi! :)

### Challenges we ran into

During this hackathon I kept a log of the multiple problems I faced when developing this application. Many problems I had no idea I would be a problem. The first major problem I faced was just with the sheer scope of my program. I knew that I wanted to do a chrome extension misinformation ideintification tool, and with it I would need a backend for processing, a front end for web services, and more. I've never built an application with as wide of a scope as this. I think I could definitely say this was full-stack. The second major problem occured with my initial development towards a predictive fake news model and Google Gemini 3 Fast. I had forgotten that AI models usually are a year or more back in training data - these algorithms have no idea what is happening currently. Due to this, any event in an article past mid 2024 would cause the LLM to throw a fit, saying 'Thats fake' to anything past its latest training data. This was especially true when it came to analyzing news articles. When it came to the current sitting president and it being 2026, the LLM would throw an admittedly silly tantrum marking everything relating to it as misinformation. With LLM's being a popular tool in this hackathon (I used Gemini 3 Pro and Claude 4.5 Sonnet quite liberally), I found that a large scale project like this overloaded the chatbots progressively, forcing me to switch my methidology to tackling one part at a time and keeping track of the architecture + problems in a notepad. The predictive model was not that effective either. While it still exists in the current program as a fallback, its only good for giving a general sense of an article being 'Fake'. It definitely doesn't identify false information.

### Accomplishments we're proud of

I'm quite proud of how well the extension integrates into chrome and general web browsing. It has identified misinformation, propaganda, and logical fallacies (as well a wording mistake in a BBC news article). Additionally, I'm very happy with the UI+UX Design of this application. I spent much of my time tuning user interactions with this and it payed off. On top of those, I'm somewhat happy with myself for being able to put together all an application with as many services as it has.

### What we learned

When working with LLM technology, work modularly, small parts at a time to build up the whole Keep track of your system architecture, spend a lot of time initially building a strong understanding of the program's purpose. Equal time needs to be put into the UI/UX of the application as the backend. Its intimidating to be solo and a first time hackathon participant, but its doable, and I personally learned a lot through being solo. Albeit, next time I definitely do NOT want to be solo again.

### What's next

Google Chrome full release Improving the Misinformation algorithm to be faster and more effective at identifying misinformation. Social media implementation to see when someone's posting misinformation. Especially with X, Reddit, and Facebook.

## README (from the GitHub repository)

# Veritas: Misinformation Detection Platform

A hybrid architecture platform for identifying misinformation and bias in web content.

## Architecture

- **Extension**: Chrome Extension (Manifest V3, React + Vite) - `apps/extension`
- **Web**: Next.js Dashboard & API Gateway - `apps/web`
- **ML Core**: Python FastAPI Service - `services/ml-core`

## Getting Started

### Prerequisites

- Node.js 18+
- Python 3.10+
- Docker (optional, for ML service)

### 1. Setup ML Service

```bash
cd services/ml-core
pip install -r requirements.txt
uvicorn main:app --reload
# Service runs on http://localhost:8000
```

### 2. Setup Web Platform

```bash
cd apps/web
cp .env.example .env.local
# Fill in Auth0 credentials in .env.local
npm install
npm run dev
# Web runs on http://localhost:3000
```

### 3. Setup Extension

1. Build the extension:
   ```bash
   cd apps/extension
   npm install
   npm run build
   ```
2. Open Chrome -> Extensions (`chrome://extensions/`)
3. Enable "Developer mode"
4. Click "Load unpacked" and select `apps/extension/dist`

## Development

- The Extension communicates with the Web Platform (`localhost:3000`) for authentication and analysis proxying.
- The Web Platform proxies requests to the ML Service (`localhost:8000`).

## Environment Variables

See `apps/web/.env.example` for required keys.

## Detected evidence (automated analysis)

Indexed codebase: 88 recognized source files, 496 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- PostgreSQL (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — 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
- Vercel (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 (117 of 117)

```
.gitignore
.vercel/project.json
.vercel/README.txt
apps/extension/.env.development
apps/extension/.env.example
apps/extension/.env.production
apps/extension/.gitignore
apps/extension/build-for-store.sh
apps/extension/CHANGES.md
apps/extension/eslint.config.js
apps/extension/index.html
apps/extension/package.json
apps/extension/postcss.config.js
apps/extension/public/icons/create_icons.html
apps/extension/public/manifest.json
apps/extension/README.md
apps/extension/SETUP_API_URL.md
apps/extension/src/App.css
apps/extension/src/App.tsx
apps/extension/src/background.ts
apps/extension/src/components/FlaggedContent.tsx
apps/extension/src/components/LoadingAnalysis.tsx
apps/extension/src/components/TrustDial.tsx
apps/extension/src/config.ts
apps/extension/src/content.ts
apps/extension/src/index.css
apps/extension/src/main.tsx
apps/extension/src/utils/sse-parser.ts
apps/extension/tailwind.config.js
apps/extension/tsconfig.app.json
apps/extension/tsconfig.json
apps/extension/tsconfig.node.json
apps/extension/vite.config.ts
apps/web/.gitignore
apps/web/download-ca-cert.sh
apps/web/env.example
apps/web/eslint.config.mjs
apps/web/migrate-env.sh
apps/web/next.config.ts
apps/web/package.json
apps/web/postcss.config.mjs
apps/web/prisma.config.ts
apps/web/prisma/migrations/20260117132103_init/migration.sql
apps/web/prisma/migrations/migration_lock.toml
apps/web/prisma/schema.prisma
apps/web/proxy.ts
apps/web/README.md
apps/web/setup-database.sh
apps/web/src/app/api/analyze/route.ts
apps/web/src/app/api/analyze/stream/route.ts
apps/web/src/app/api/auth/[...auth0]/route.ts
apps/web/src/app/api/auth/extension-token/route.ts
apps/web/src/app/api/beta/redeem/route.ts
apps/web/src/app/api/billing-portal/route.ts
apps/web/src/app/api/checkout/route.ts
apps/web/src/app/api/deep-dive/route.ts
apps/web/src/app/api/health/route.ts
apps/web/src/app/api/history/clear/route.ts
apps/web/src/app/api/stats/route.ts
apps/web/src/app/api/stripe/webhook/route.ts
apps/web/src/app/api/usage/route.ts
apps/web/src/app/auth/[...auth0]/route.ts
apps/web/src/app/beta/page.tsx
apps/web/src/app/dashboard/page.tsx
apps/web/src/app/globals.css
apps/web/src/app/layout.tsx
apps/web/src/app/page.tsx
apps/web/src/components/LoginButton.tsx
apps/web/src/components/LogoutButton.tsx
apps/web/src/components/Profile.tsx
apps/web/src/lib/auth0.ts
apps/web/src/lib/prisma.ts
apps/web/src/middleware.ts
apps/web/SSL_FIX.md
apps/web/tsconfig.json
apps/web/vercel-ignore-build-step.sh
CHANGELOG.md
demo/FILE_URL_SETUP.md
demo/high-trust.html
demo/index.html
demo/low-trust.html
demo/mixed-trust.html
demo/README.md
DEVPOST.md
INFO.md
package.json
README.md
services/ml-core/.digitalocean-ignore-build
services/ml-core/.env.example
services/ml-core/.gitattributes
services/ml-core/.gitignore
services/ml-core/app-spec.yaml
services/ml-core/deploy-to-digitalocean.sh
services/ml-core/docker-compose.yml
services/ml-core/Dockerfile
services/ml-core/Dockerfile.external-model
services/ml-core/download_model.py
services/ml-core/fix-git-history.sh
services/ml-core/models/misinfo_model.pkl
services/ml-core/pre-deploy-check.sh
services/ml-core/README.md
services/ml-core/requirements.txt
services/ml-core/scripts/prepare_dataset.py
services/ml-core/setup-git-lfs.sh
services/ml-core/setup.sh
services/ml-core/src/__init__.py
services/ml-core/src/bias_data.py
services/ml-core/src/cache.py
services/ml-core/src/claim_validator.py
services/ml-core/src/fact_checker.py
services/ml-core/src/gemini_explainer.py
services/ml-core/src/inference.py
services/ml-core/src/main.py
services/ml-core/src/preprocessing.py
services/ml-core/src/training.py
services/ml-core/src/web_search.py
services/ml-core/test_claim_validator.py
```

### Dependencies

- apps/extension/package.json: @eslint/js@^9.39.1, @tailwindcss/postcss@^4.1.18, @types/chrome@^0.1.33, @types/node@^24.10.1, @types/react@^19.2.5, @types/react-dom@^19.2.3, @vitejs/plugin-react@^5.1.1, autoprefixer@^10.4.23, eslint@^9.39.1, eslint-plugin-react-hooks@^7.0.1, eslint-plugin-react-refresh@^0.4.24, globals@^16.5.0, postcss@^8.5.6, react@^19.2.0, react-dom@^19.2.0, tailwindcss@^4.1.18, typescript@~5.9.3, typescript-eslint@^8.46.4, vite@^7.2.4
- apps/web/package.json: @auth0/nextjs-auth0@^4.14.0, @prisma/adapter-pg@^7.2.0, @prisma/client@^7.2.0, @tailwindcss/postcss@^4, @types/node@^20, @types/pg@^8.16.0, @types/react@^19, @types/react-dom@^19, dotenv@^16.6.1, eslint@^9, eslint-config-next@16.1.3, next@16.1.3, pg@^8.17.1, prisma@^7.2.0, react@19.2.3, react-dom@19.2.3, recharts@^3.6.0, stripe@^20.2.0, tailwindcss@^4, typescript@^5
- package.json: @prisma/client@^7.2.0, dotenv@^17.2.3, prisma@^7.2.0, recharts@^3.6.0, turbo@latest
- services/ml-core/requirements.txt: fastapi@==0.109.0, google-genai@>=0.2.0, httpx@==0.26.0, joblib@==1.3.2, nltk@==3.8.1, numpy@==1.26.3, pandas@==2.2.0, pydantic@==2.6.0, python-dotenv@==1.0.0, python-multipart@==0.0.9, requests@==2.31.0, scikit-learn@==1.4.0, uvicorn[standard]@==0.27.0

### Recent commits (newest first)

- Uploading the zip compress
- Fixing matching algorithm
- Model tuning
- Model tuning
- Model tuning
- Merge branch 'main' of https://github.com/aram-ap/Veritas-Web
- Tuning misinformation identifying backend
- Clarify Veritas functionality in DEVPOST.md
- filled out dev post
- removed extra fluff
- increase analyysis depth
- added demo pages
- Fixed issue that caused the click-to-navigate function to stop working
- Fixed misinformation tracker on dashboard
- Fixed issue that phrases are flagged in article but not shown in the flagged content panel
- Enhance analysis result handling and UI responsiveness. Added result URL tracking in App component, improved snippet auto-expansion in FlaggedContent, and introduced animated text effects in LoadingAnalysis. Enhanced logging for source verification in inference.py.
- race case fixes
- If this works im gonna go ape shit lol
- Fixed ui stuff
- Added grayed out button when limit reached

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

### INFO.md

```markdown
Here is the `INFO.md` file. You can save this in the root of your repository. It is structured to provide immediate, high-density context to any future AI agent or developer working on this project.

```markdown
# Project Veritas: Misinformation Detection Platform - Master Reference

## 1. Project Overview
**Veritas** is a multi-platform system designed to identify misinformation, political bias, and factual errors in web articles.
* **Core Function:** Users view a website, and a Chrome Extension analyzes the text in real-time.
* **Deep Dive:** Users can request a detailed analysis using LLMs (Gemini) to cross-reference claims with live search data.
* **Architecture Strategy:** Hybrid.
    * **Frontend/Gateway:** Hosted on Vercel (Serverless).
    * **Compute/ML:** Hosted on DigitalOcean (Persistent Server).

## 2. Technical Architecture

### High-Level Data Flow
1.  **Chrome Extension:** Scrapes `<article>` text from the DOM.
2.  **Auth Layer:** Extension includes Auth0 session cookies in the request.
3.  **Gateway (Next.js):** Receives request at `/api/analyze`. Verifies Auth0 session.
4.  **Proxy:** Next.js proxies valid requests to the DigitalOcean Python Backend (hidden from client).
5.  **Inference (Python):** Backend cleans text, vectorizes, runs ML model, returns Score/Bias/Highlights.
6.  **Deep Dive (Optional):** Next.js calls Google Gemini API for detailed reasoning and source checking.

### The Stack
| Component | Technology | Hosting |
| :--- | :--- | :--- |
| **Extension** | React, Vite, Manifest V3 | Chrome Web Store |
| **Web Dashboard** | Next.js 14+ (App Router), Tailwind | Vercel |
| **Auth** | Auth0 (Shared Session) | Auth0 Cloud |
| **ML Backend** | Python 3.10+, FastAPI, Docker | DigitalOcean (App Platform/Droplet) |
| **Database** | Vercel Postgres or Supabase | Vercel/External |
| **AI/LLM** | Google Gemini (Deep Dive), Scikit-Learn (Quick Score) | -- |

## 3. Monorepo Structure

```text
/veritas-platform (Root)
├── /apps
│   ├── /web                  # Next.js Gateway & Dashboard
│   │   ├── /app/api/analyze  # Proxies to Python Backend
│   │   └── /app/api/deep-dive# Calls Google Gemini
│   │
│   └── /extension            # Chrome Extension (React)
│       ├── manifest.json     # Permissions: sidePanel, activeTab, scripting, cookies
│       └── /src/sidepanel    # Main UI
│
├── /services
│   └── /ml-core              # Python FastAPI Service
│       ├── /data             # Training data (gitignored)
│       ├── /models           # Serialized models (.pkl)
│       ├── main.py           # API Entry
│       ├── training.py       # Model training script
│       └── Dockerfile        # DigitalOcean deployment config

```

## 4. Machine Learning & Backend Specifications

### The Dataset

* **Source:** [Kaggle: Misinformation Fake News Text Dataset (79k)](https://www.kaggle.com/datasets/stevenpeutz/misinformation-fake-news-text-dataset-79k)
* **Labels:** Fake vs. True.

### The Algorithm (Python Service)

* **Framework:** FastA
[truncated — 2075 more characters]
```

### CHANGELOG.md

```markdown
# Changelog - Gemini Integration & UI Improvements

## Summary of Changes

This update significantly enhances the Veritas misinformation detection system by integrating Google Gemini AI for more intelligent content analysis and improving the user interface with interactive flagged content displays.

## Key Improvements

### 1. Gemini AI Integration (Always Enabled)
- **Removed toggle**: Gemini is now always enabled for all scans, no longer an optional "Deep Dive" feature
- **Sentence-level flagging**: Gemini now analyzes articles and flags complete sentences/phrases instead of single words
- **Smart categorization**: Flagged content is categorized as:
  - **MISINFORMATION**: False or inaccurate information shared without intent to deceive
  - **DISINFORMATION**: Deliberately false information intended to mislead
  - **PROPAGANDA**: Biased or misleading information used to promote a particular viewpoint
- **AI-powered explanations**: Gemini generates comprehensive explanations for trust scores
- **Automatic fact-checking**: Key claims are identified and verified with each scan

### 2. Interactive Flagged Content UI
- **Expandable boxes**: Click on any flagged item to see detailed explanation
- **Color-coded by type**:
  - Amber: Misinformation
  - Red: Disinformation
  - Purple: Propaganda
- **Confidence indicators**: Shows confidence percentage for each flag
- **Full sentences**: Displays meaningful context instead of single words

### 3. Improved Error Handling
- **Better page validation**: Clear error messages for unsupported pages
- **Specific guidance**: Tells users exactly what types of pages can be analyzed
- **Content length validation**: Ensures sufficient text is available for analysis
- **Auto-retry logic**: Automatically injects content script if needed

### 4. Backend Improvements
- **Fallback system**: If Gemini is unavailable, falls back to ML model-based flagging
- **Cached predictors**: ML models and Gemini clients are cached to avoid reloading
- **Enhanced API responses**: All responses now include type, reason, and confidence for flagged items

## Files Modified

### Frontend (Extension)
- `apps/extension/src/App.tsx`
  - Removed deep_dive toggle
  - Added expandable flagged content UI
  - Improved error handling with specific messages
  - Added color-coded flag type styling

### Backend (ML Service)
- `services/ml-core/src/gemini_explainer.py`
  - Added `flag_suspicious_sentences()` method for sentence-level flagging
  - Enhanced prompts for better categorization
  - Added `_parse_flagged_items()` parser

- `services/ml-core/src/inference.py`
  - Updated `predict_full_analysis()` to use Gemini by default
  - Added fallback to ML model if Gemini unavailable
  - Integrated sentence flagging and fact-checking

- `services/ml-core/src/main.py`
  - Updated `FlaggedSnippet` model to include `type` field
  - Removed `deep_dive` parameter (now always enabled)
  - Updated API documentation examples

### API
- `apps/web/src/app/api/analy
[truncated — 2956 more characters]
```

### package.json

```
{
  "name": "Veritas: Misinformation Platform",
  "version": "0.1.0",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "dev:web": "npm run dev --workspace=apps/web",
    "dev:ext": "npm run dev --workspace=apps/extension",
    "build": "npm run build --workspaces"
  },
  "devDependencies": {
    "turbo": "latest"
  },
  "dependencies": {
    "@prisma/client": "^7.2.0",
    "dotenv": "^17.2.3",
    "prisma": "^7.2.0",
    "recharts": "^3.6.0"
  }
}

```

### services/ml-core/requirements.txt

```
# FastAPI and Web Server
fastapi==0.109.0
uvicorn[standard]==0.27.0
pydantic==2.6.0
python-multipart==0.0.9

# Machine Learning Core
scikit-learn==1.4.0
pandas==2.2.0
numpy==1.26.3
joblib==1.3.2

# NLP and Text Processing
nltk==3.8.1

# HTTP and Networking
requests==2.31.0
httpx==0.26.0

# AI/ML Services
google-genai>=0.2.0  # New package (replaces deprecated google-generativeai)

# Utilities
python-dotenv==1.0.0

# Optional: HuggingFace Transformers (for advanced bias detection)
# Uncomment if you want to use transformer models
# transformers==4.36.2
# torch==2.1.2

```

### apps/web/package.json

```
{
  "name": "web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "prisma generate && next build",
    "start": "next start",
    "lint": "eslint",
    "postinstall": "prisma generate"
  },
  "dependencies": {
    "@auth0/nextjs-auth0": "^4.14.0",
    "@prisma/adapter-pg": "^7.2.0",
    "@prisma/client": "^7.2.0",
    "next": "16.1.3",
    "pg": "^8.17.1",
    "prisma": "^7.2.0",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "recharts": "^3.6.0",
    "stripe": "^20.2.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/pg": "^8.16.0",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "dotenv": "^16.6.1",
    "eslint": "^9",
    "eslint-config-next": "16.1.3",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### apps/extension/package.json

```
{
  "name": "extension",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.39.1",
    "@tailwindcss/postcss": "^4.1.18",
    "@types/chrome": "^0.1.33",
    "@types/node": "^24.10.1",
    "@types/react": "^19.2.5",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^5.1.1",
    "autoprefixer": "^10.4.23",
    "eslint": "^9.39.1",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-react-refresh": "^0.4.24",
    "globals": "^16.5.0",
    "postcss": "^8.5.6",
    "tailwindcss": "^4.1.18",
    "typescript": "~5.9.3",
    "typescript-eslint": "^8.46.4",
    "vite": "^7.2.4"
  }
}

```

### services/ml-core/docker-compose.yml

```yaml
version: '3.8'

services:
  ml-core:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: ml-core
    ports:
      - "8000:8000"
    volumes:
      # Mount source code for hot-reload in development
      - ./src:/app/src
      # Mount models directory to persist trained models
      - ./models:/app/models
      # Mount data directory for training
      - ./data:/app/data
      # Mount logs directory
      - ./logs:/app/logs
    environment:
      - PYTHONUNBUFFERED=1
      - LOG_LEVEL=info
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - ml-network

  # Optional: Add a simple nginx reverse proxy
  # nginx:
  #   image: nginx:alpine
  #   container_name: ml-core-nginx
  #   ports:
  #     - "80:80"
  #   volumes:
  #     - ./nginx.conf:/etc/nginx/nginx.conf:ro
  #   depends_on:
  #     - ml-core
  #   networks:
  #     - ml-network

networks:
  ml-network:
    driver: bridge

volumes:
  models:
  data:
  logs:

```

### services/ml-core/Dockerfile

```
###############################################################################
# Multi-stage Dockerfile for Misinformation Detection ML Service
# 
# This Dockerfile builds a production-ready container with:
# - Optimized Python 3.10 runtime
# - Pre-trained ML model
# - FastAPI service
#
# Build: docker build -t ml-core:latest .
# Run:   docker run -p 8000:8000 ml-core:latest
###############################################################################

# Stage 1: Builder stage for dependencies
FROM python:3.10-slim as builder

# Set environment variables
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    gcc \
    g++ \
    && rm -rf /var/lib/apt/lists/*

# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# Copy requirements and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt


# Stage 2: Runtime stage
FROM python:3.10-slim

# Set environment variables
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PATH="/opt/venv/bin:$PATH"

# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv

# Create app user for security (don't run as root)
RUN useradd -m -u 1000 appuser && \
    mkdir -p /app/data /app/models /app/logs && \
    chown -R appuser:appuser /app

# Set working directory
WORKDIR /app

# Copy application code
COPY --chown=appuser:appuser ./src ./src
COPY --chown=appuser:appuser ./requirements.txt .

# Copy the dataset if it exists (optional - can also be mounted as volume)
# COPY --chown=appuser:appuser ./data/dataset.csv ./data/dataset.csv

# Copy pre-trained model for deployment
# ⚠️ IMPORTANT: Model is 381MB and exceeds GitHub's 100MB limit!
# 
# Choose ONE of these options:
#
# Option 1: Use Git LFS (Recommended)
#   - Run: ./setup-git-lfs.sh
#   - Uncomment the line below
#   - Git LFS will handle the large file
#
# Option 2: External Storage (Production)
#   - Use Dockerfile.external-model instead
#   - Store model in DigitalOcean Spaces/S3
#   - Download at runtime
#
# Option 3: Local Development Only
#   - Comment out COPY line below
#   - Mount model as volume: -v $(pwd)/models:/app/models
#
# Using Git LFS - model will be pulled during build:
COPY --chown=appuser:appuser ./models/misinfo_model.pkl ./models/misinfo_model.pkl

# Switch to non-root user
USER appuser

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:8000/health')"

# Run the application
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]

###############################################################################
# Build Instructions:
# 
# 1. Download the Kaggle dataset:
#    - Go to: https://www.kaggle.com/datasets/stevenpeutz/misinformation-fake-news-text-dataset-79k
#    - Download and extract to: ./data/dataset.csv
#
# 2. Train the model locally (before building Docker image):
#    pip install -r requirements.txt
#    python src/training.py
#
# 3. Build the Docker image:
#    docker build -t ml-core:latest .
#
# 4. Run the container:
#    docker run -d -p 8000:8000 \
#      -v $(pwd)/models:/app/models \
#      --name ml-core \
#      ml-core:latest
#
# 5. Alternatively, train inside container:
#    docker run -it -v $(pwd)/data:/app/data -v $(pwd)/models:/app/models ml-core python src/training.py
#
###############################################################################

```

### apps/extension/src/main.tsx

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### services/ml-core/src/main.py

```python
"""
FastAPI service for misinformation detection and bias analysis.

This service exposes REST endpoints for analyzing news articles
to detect misinformation and political bias.

Usage:
    uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload
"""

import os
import sys
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
import logging
from dotenv import load_dotenv

# Load environment variables from .env file
from pathlib import Path
env_path = Path(__file__).parent.parent / '.env'
load_dotenv(dotenv_path=env_path)

# Add src to path for imports
sys.path.append(os.path.dirname(__file__))

# Debug logging for API Key
api_key = os.getenv("GEMINI_API_KEY")
if api_key:
    masked_key = f"{api_key[:5]}...{api_key[-5:]}" if len(api_key) > 10 else "***"
    print(f"DEBUG: Found GEMINI_API_KEY: {masked_key}")
else:
    print("DEBUG: GEMINI_API_KEY not found in environment")
from inference import predict_full_analysis, predict_full_analysis_streaming

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Initialize FastAPI app
app = FastAPI(
    title="Misinformation Detection API",
    description="ML-powered API for detecting misinformation and political bias in news articles",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

# Configure CORS for Next.js frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",  # Local development
        "http://localhost:3001",
        "https://*.vercel.app",   # Vercel deployments
        "https://*.digitalocean.app",  # DigitalOcean deployments
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# Pydantic models for request/response validation
class PredictRequest(BaseModel):
    """Request model for prediction endpoint."""
    text: str = Field(
        ...,
        description="Full article text to analyze",
        min_length=50,
        max_length=50000
    )
    title: Optional[str] = Field(
        None,
        description="Article title (optional)",
        max_length=500
    )
    url: Optional[str] = Field(
        None,
        description="Article URL (optional, for bias database)",
        max_length=2000
    )
    article_date: Optional[str] = Field(
        None,
        description="Article publication date (optional, ISO format or common date string)",
        max_length=100
    )
    force_refresh: bool = Field(
        False,
        description="Whether to force a new analysis (ignoring cache)"
    )

    model_config = {
        "json_schema_extra": {
            "example": {
                "text": "This is a sample news article text that needs to be analyzed for misinformation...",
                "title": "Breaking: Important News Story",
                "article_date": "2026-01-13"
            }
        }
    }


class FlaggedSnippet(BaseModel):
    """Model for a flagged suspicious text snippet."""
    text: str = Field(..., description="The suspicious text snippet (full sentence or phrase)")
    type: Optional[str] = Field(
        None,
        description="Type of flag: MISINFORMATION, DISINFORMATION, or PROPAGANDA"
    )
    index: Optional[List[int]] = Field(None, description="[start, end] character indices")
    reason: str = Field(..., description="Reason why this snippet is flagged")
    confidence: Optional[float] = Field(None, description="Confidence score (0-1)")
    is_quote: Optional[bool] = Field(None, description="Whether this is a direct quote from a source")
    article_supports_quote: Optional[bool] = Field(None, description="Whether the article endorses/supports this quote (vs challenging it)")


class Explanation(BaseModel):
    """Model for explanation of the trust score."""
    summary: str = Field(..., description="Comprehensive explanation of the score and analysis")
    generated_by: str = Field(..., description="Source of explanation: 'gemini' or 'rule-based'")


class FactCheckedClaim(BaseModel):
    """Model for a fact-checked claim."""
    claim: str = Field(..., description="The factual claim identified")
    status: str = Field(..., description="Verification status: Verified, Unverified, Misleading, or False")
    explanation: str = Field(..., description="Brief explanation of the verification")


class PredictionResponse(BaseModel):
    """Response model for prediction endpoint."""
    trust_score: int = Field(
        ...,
        description="Trust score from 0-100 (100 = trustworthy, 0 = fake)",
        ge=0,
        le=100
    )
    label: str = Field(
        ...,
        description="Classification label: 'Likely True', 'Suspicious', or 'Likely Fake'"
    )
    bias: str = Field(
        ...,
        description="Political bias: 'Left', 'Left-Center', 'Center', 'Right-Center', or 'Right'"
    )
    explanation: Explanation = Field(
        ...,
        description="Detailed explanation of why the article received this score"
    )
    flagged_snippets: List[FlaggedSnippet] = Field(
        default=[],
        description="List of suspicious text snippets with highlighting info"
    )
    fact_checked_claims: Optional[List[FactCheckedClaim]] = Field(
        default=None,
        description="Optional fact-checked claims (only when deep_dive=true)"
    )
    
    model_config = {
        "json_schema_extra": {
            "example": {
                "trust_score": 85,
                "label": "Likely True",
                "bias": "Left-Center",
                "explanation": {
                    "summary": "This article received a high trust score because it uses balanced language...",
                    "generated_by": "gemini"
                },
                "flagged_snippets": [
        
[truncated — 8891 more characters]
```

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