# MixLift Project Status

**Last updated:** 2026-03-04

---

## 1. Project Overview

**MixLift** is an MCP (Model Context Protocol) server that brings Bayesian Marketing Mix Modeling (MMM) to AI assistants. Users install MixLift as an MCP tool in Claude Desktop (or any MCP-compatible client), point it at a CSV of marketing spend data, and get back channel ROAS, budget optimization recommendations, and saturation curves -- all without writing a single line of code.

### Architecture

The system is split into two independently deployable components:

1. **MixLift MCP Server** -- Runs locally via stdio transport. Handles CSV ingestion, format auto-detection, Bayesian model fitting (PyMC-Marketing), and result formatting. Ships as a pip-installable Python package.

2. **MixLift License API Server** -- A remote FastAPI service that validates license keys against a SQLite database, receives Stripe webhooks for subscription lifecycle management, and (eventually) sends license key emails.

### Monetization

| Tier | Price | Channels | Rows | Notes |
|------|-------|----------|------|-------|
| Free | $0 | 3 | 1,000 | Bundled demo data (3 channels, 52 weeks) |
| Pro | $199/mo | 999 | 999,999 | Via Stripe Payment Link |

---

## 2. Component Status

### 2.1 MixLift MCP Server

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift/src/mixlift_mcp/` |
| **Entry point** | `mixlift_mcp.server:main` (registered as `mixlift-mcp` console script) |
| **Transport** | stdio |
| **Status** | Working |

**What it does:**
- Exposes a single MCP tool `mixlift_analyze(csv_path, license_key)`
- Auto-detects CSV format (wide, Meta, Google, TikTok, generic)
- Enforces tier limits (channel count + row count)
- Delegates to the modeling engine, optimizer, and saturation curve extractor
- Returns structured JSON with ROAS, contributions, optimization, recommendations, and saturation curves

**Key files:**
- `src/mixlift_mcp/server.py` -- MCP tool definition and pipeline orchestration
- `src/mixlift_mcp/license.py` -- License validation client (calls remote API)
- `src/mixlift_mcp/data/demo.csv` -- Bundled demo dataset (3 channels, 52 weeks)

**Tests:** 45 tests in `tests/test_mcp/` (test_server.py, test_license.py) -- all passing

**Path traversal protection:** Implemented via `validate_csv_path()` in `server.py`. Extension whitelist (.csv/.tsv/.txt), blocked sensitive path patterns (.ssh/, .env, credentials, secrets, .git/), and optional `MIXLIFT_ALLOWED_DIRS` environment variable for directory sandboxing. Audit logging on all rejections. 21 new tests added in `TestValidateCsvPath` and `TestMixliftAnalyzeSecurity`.

---

### 2.2 Modeling Engine

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift/mixlift/modeling/engine.py` |
| **Status** | Working |

**What it does:**
- Builds PyMC-Marketing MMM models with opinionated defaults (GeometricAdstock, LogisticSaturation)
- Fits via MCMC (2 chains, 1000 tune, 500 draws by default)
- Extracts: channel contributions, average ROAS, marginal ROAS, saturation params, adstock params, saturation curves
- Runs MCMC convergence diagnostics (R-hat > 1.05 warning, divergent transitions warning)

**Key submodules:**
- `engine.py` -- Model build/fit/extract + convergence checks + numpy implementations of saturation and adstock
- `optimizer.py` -- Budget optimizer (tries PyMC-Marketing's built-in, falls back to gradient-based)
- `preprocessing.py` -- Weekly aggregation, gap filling, pivot to wide format, control columns
- `defaults.py` -- Default MCMC config, adstock config, seasonality config
- `recommendations.py` -- Generates ranked plain-English budget recommendations
- `service.py` -- End-to-end pipeline orchestrator (used by the FastAPI web app, not the MCP server)
- `router.py` -- FastAPI router for the web app

**Tests:** 5 test files in `tests/test_modeling/`:
- `test_engine_unit.py` -- Saturation formula verification against PyMC-Marketing, adstock verification, convergence diagnostics
- `test_optimizer.py` -- Budget optimizer logic with mocked models
- `test_preprocessing.py` -- Preprocessing pipeline tests
- `test_recommendations.py` -- Recommendation generation tests
- `test_engine_slow.py` -- Integration tests requiring actual MCMC fitting (marked `@pytest.mark.slow`)

---

### 2.3 Data Ingestion

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift/mixlift/ingest/` |
| **Status** | Working |

**What it does:**
- Auto-detects ad platform from column names (Meta, Google, TikTok, generic)
- Parses platform-specific CSV exports into canonical long format (date, channel, spend, impressions, clicks, conversions)
- Validates data (no empty datasets, no negative spend, >= 2 channels, >= 180 days)

**Key files:**
- `service.py` -- Detection, parsing, validation, load_and_parse
- `templates.py` -- Platform column signatures for auto-detection
- `parsers/meta.py`, `parsers/google.py`, `parsers/tiktok.py`, `parsers/generic.py` -- Platform-specific parsers

**Tests:** `tests/test_ingest/test_parsers.py`

---

### 2.4 License API Server

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift-api/` |
| **Status** | Working |

**What it does:**
- FastAPI app with SQLite backend (via aiosqlite)
- `POST /v1/license/validate` -- Validates license keys (checks status, expiration)
- `POST /v1/webhooks/stripe` -- Handles Stripe webhook events:
  - `checkout.session.completed` -- Creates new license, deactivates old ones for same customer (idempotent)
  - `customer.subscription.deleted` -- Cancels license
  - `customer.subscription.updated` -- Maps Stripe status to license status (active/suspended/cancelled)
  - `invoice.payment_failed` -- Suspends license (grace period)
  - `invoice.payment_succeeded` -- Reactivates suspended license
- `GET /health` -- Health check

**Database schema:**
```sql
CREATE TABLE licenses (
    id INTEGER PRIMARY KEY,
    license_key TEXT UNIQUE NOT NULL,
    email TEXT NOT NULL,
    stripe_customer_id TEXT,
    stripe_subscription_id TEXT UNIQUE,  -- UNIQUE constraint for idempotency
    tier TEXT DEFAULT 'pro',
    status TEXT DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP
);
```

**Key files:**
- `src/mixlift_api/main.py` -- FastAPI app, webhook handlers
- `src/mixlift_api/database.py` -- SQLite CRUD operations
- `src/mixlift_api/models.py` -- Pydantic request/response models
- `src/mixlift_api/email_service.py` -- Resend integration (HTML template, fallback logging)
- `.env.example` -- Environment variable template

**Tests:** 23 tests in `tests/test_api.py` -- all passing

---

### 2.5 Synthetic Data Generator

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift/scripts/generate_synthetic.py` |
| **Status** | Working |

**What it does:**
- Generates synthetic marketing data with known ground truth ROAS per channel
- Configurable: n_weeks, seed, output_path
- Channels: google_search, meta_prospecting, tiktok, email (4 channels in generator, but demo data uses 3)
- Includes seasonality (Q4 boost, Q1 dip), spend trends, saturating response functions

**Demo data:**
- `src/mixlift_mcp/data/demo.csv` -- 52 weeks, 3 channels (google_search, meta_prospecting, tiktok), bundled with MCP server
- `data/demo_data.csv` -- 104 weeks, 4 channels, used by web app

---

### 2.6 Web App (FastAPI + Streamlit Frontend)

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift/mixlift/` (backend) + `/Users/felix/software-projects/mixlift/frontend/` (Streamlit) |
| **Status** | Partially working (not primary focus -- MCP server is the product) |

**Backend components:** Auth (JWT), Projects (CRUD), Ingest (upload), Modeling (trigger/status), Results (view), Billing (Stripe checkout)

**Frontend (Streamlit):** Dashboard, Upload, Training, Results, Recommendations, Billing, Auth pages

**Infrastructure:** PostgreSQL (via SQLAlchemy + asyncpg), Alembic migrations, Docker Compose, Render deploy config

---

### 2.7 Landing Page

| Property | Value |
|----------|-------|
| **Status** | Complete (deployed on Netlify) |

The landing page is live with product description, pricing, and Stripe Payment Link integration.

---

### 2.8 Email Delivery

| Property | Value |
|----------|-------|
| **Location** | `/Users/felix/software-projects/mixlift-api/src/mixlift_api/email_service.py` |
| **Status** | Working |

Implemented via Resend (resend.com). Sends a professional HTML email with the license key and MCP setup instructions after Stripe checkout. Requires `RESEND_API_KEY` in environment; falls back to logging without it (no crash). 4 new tests cover: Resend called when key is set, fallback log when no key, Resend error does not propagate, checkout webhook sends email with correct args.

---

## 3. Recent Changes Log

Based on git diff analysis (uncommitted changes in mixlift repo):

### Modeling Engine (`engine.py`)
- **Saturation function fix** -- Corrected logistic saturation formula to match PyMC-Marketing: `(1 - exp(-lam * x)) / (1 + exp(-lam * x))`
- **MCMC convergence diagnostics** -- Added `check_convergence()` function that checks R-hat > 1.05 and divergent transitions
- **Marginal ROAS** -- Added `extract_marginal_roas()` via finite-difference approximation
- **Adstock NumPy implementation** -- Added `geometric_adstock_np()` for use in optimizer
- **Saturation curve extraction** -- Added `extract_saturation_curves()` with proper beta scaling

### Optimizer (`optimizer.py`)
- **Adstock fix** -- Optimizer now applies geometric adstock before computing saturation ratio, matching what the model does internally during fitting
- **Performance fix** -- Hoisted expensive `compute_channel_contribution_original_scale()` call outside the optimization loop; pre-computes `sat_params` and `adstock_params` once
- **Correct response estimation** -- `_estimate_response_from_array()` now creates constant-spend time series and adstocks them properly

### Demo Data & Tier Limits
- **Demo data reduced to 3 channels** -- Bundled `demo.csv` uses google_search, meta_prospecting, tiktok (fits free tier)
- **Tier limits aligned** -- Free tier: 3 channels, 1,000 rows (consistent between `license.py`, `config.py`, and MCP server)
- **Double seasonality fix in generator** -- Fixed `generate_synthetic.py` to apply seasonality correctly (was being double-applied)

### License API Server (mixlift-api -- new repo, all untracked)
- **Webhook idempotency** -- `_handle_checkout_completed` checks for existing license by subscription_id before creating
- **Subscription reactivation handlers** -- Added `customer.subscription.updated` handler that maps Stripe statuses to license statuses
- **Old license deactivation on re-subscribe** -- `deactivate_customer_licenses()` cancels previous active licenses when customer re-subscribes
- **Webhook secret fail-fast** -- Returns HTTP 500 immediately if `STRIPE_WEBHOOK_SECRET` is not configured
- **UNIQUE constraint on `subscription_id`** -- Prevents duplicate licenses at the database level
- **Payment succeeded handler** -- Reactivates suspended licenses on successful payment
- **Email delivery (Resend)** -- `email_service.py` now sends license key via Resend API; professional HTML template with setup instructions; graceful fallback to log-only when `RESEND_API_KEY` is unset; 4 new tests

### MCP Server Security (mixlift)
- **Path traversal protection** -- `validate_csv_path()` added to `server.py`: extension whitelist (.csv/.tsv/.txt), sensitive path blocklist (.ssh/, .env, credentials, secrets, .git/), optional `MIXLIFT_ALLOWED_DIRS` directory sandboxing, audit logging on all rejections; 21 new tests

### Other Changes
- `pyproject.toml` -- Added `mcp` dependency, `mixlift-mcp` console script, MCP data bundling config
- `conftest.py` -- Added API test infrastructure (fake users, mock sessions, auth headers)
- `config.py` -- Aligned tier limits
- `frontend/app.py` and `frontend/results.py` -- Minor updates

---

## 4. QA Audit Findings

### Critical Issues

| # | Issue | Status | Notes |
|---|-------|--------|-------|
| C1 | Saturation function formula was wrong (not matching PyMC-Marketing) | Fixed | Corrected to `(1-exp(-lam*x))/(1+exp(-lam*x))`, verified with unit tests against PyMC-Marketing |
| C2 | Optimizer ignored adstock when estimating response | Fixed | Now applies `geometric_adstock_np()` to spend before computing saturation ratio |
| C3 | Webhook not idempotent (duplicate events create duplicate licenses) | Fixed | Added subscription_id lookup + UNIQUE constraint on `stripe_subscription_id` |

### Major Issues

| # | Issue | Status | Notes |
|---|-------|--------|-------|
| M1 | No MCMC convergence diagnostics (silent bad fits) | Fixed | Added R-hat and divergence checks in `check_convergence()` |
| M2 | No marginal ROAS (only average ROAS reported) | Fixed | Added `extract_marginal_roas()` via finite-difference |
| M3 | Optimizer was recomputing contributions every iteration (very slow) | Fixed | Hoisted computation outside loop, pre-computed sat/adstock params |
| M4 | Demo data had 4 channels (exceeded free tier limit of 3) | Fixed | Reduced demo.csv to 3 channels |
| M5 | Tier limits inconsistent across codebase | Fixed | Aligned: free = 3ch/1000rows in license.py, config.py, and MCP server |
| M6 | Synthetic data generator applied seasonality twice | Fixed | Single seasonality pass in `generate_synthetic.py` |
| M7 | No subscription reactivation on payment success | Fixed | Added `invoice.payment_succeeded` handler |
| M8 | Old licenses not cancelled when customer re-subscribes | Fixed | Added `deactivate_customer_licenses()` in checkout handler |
| M9 | Webhook accepted events even without secret configured | Fixed | Added fail-fast check returning HTTP 500 |
| M10 | `subscription.updated` event not handled | Fixed | Added handler mapping Stripe statuses to license statuses |
| M11 | Path traversal vulnerability in MCP server `csv_path` | Fixed | Extension whitelist, sensitive path blocklist, optional `MIXLIFT_ALLOWED_DIRS`, audit logging; 21 new tests |
| M12 | Email delivery not implemented | Fixed | Resend integration in `email_service.py`; HTML template with license key + setup instructions; graceful fallback to logging; 4 new tests |

### Minor Issues

| # | Issue | Status | Notes |
|---|-------|--------|-------|
| m1 | No Stripe Payment Link created | Fixed | Payment Link created and linked from landing page |
| m2 | No landing page | Fixed | Deployed on Netlify |
| m3 | No end-to-end integration test | Open | Need a test that exercises MCP server -> license API -> modeling pipeline |
| m4 | mixlift-api is not in git (all files untracked) | Fixed | Initial commit created |
| m5 | `convergence_warnings` not included in MCP server JSON output | Fixed | Added under `diagnostics.convergence_warnings` key |
| m6 | `marginal_roas_estimates` not included in MCP server JSON output | Fixed | Added under `marginal_roas` key with estimates and confidence intervals |

---

## 5. Test Status

### MixLift MCP Server + Modeling (`/Users/felix/software-projects/mixlift/`)

```
46 passed in 0.77s
```

Tests run with system Python 3.13 (`/opt/homebrew/bin/python3`). The project has no `.venv/` -- tests that only require stdlib/lightweight deps (test_mcp/) run fine; the heavier modeling tests (pymc-marketing, pandas) still need a venv with full deps.

**Test files present (18 files):**
- `tests/test_mcp/test_server.py` -- 37 tests (format detection, loading, tier limits, MCP tool integration, path traversal validation)
- `tests/test_mcp/test_license.py` -- 8 tests (free tier, license validation with mocks)
- `tests/test_modeling/test_engine_unit.py` -- 12 tests (saturation formula, adstock, convergence diagnostics)
- `tests/test_modeling/test_optimizer.py` -- 5 tests (response estimation, gradient optimizer)
- `tests/test_modeling/test_preprocessing.py` -- preprocessing pipeline tests
- `tests/test_modeling/test_recommendations.py` -- recommendation generation tests
- `tests/test_modeling/test_engine_slow.py` -- integration tests (marked slow, require MCMC)
- `tests/test_ingest/test_parsers.py` -- parser tests
- `tests/test_api/test_auth.py` -- auth API tests
- `tests/test_api/test_projects.py` -- projects API tests
- `tests/test_billing/test_billing.py` -- billing tests
- `tests/test_deps.py` -- dependency injection tests

### MixLift License API (`/Users/felix/software-projects/mixlift-api/`)

```
27 passed in 0.24s
```

All 27 tests passing:
- Health check (1)
- License validation: valid, invalid, cancelled, suspended, expired, not-yet-expired (6)
- Stripe webhooks: checkout completed, email fallback, idempotency, re-subscribe deactivation, subscription deleted, payment failed, payment failed (customer fallback), payment succeeded (reactivation), payment succeeded (no-change), subscription updated (active/past_due/canceled) (12)
- Error cases: invalid signature, invalid payload, missing secret (3)
- Database helpers: deactivate_customer_licenses (1)
- Email delivery: Resend called when API key set, fallback log when no key, Resend error does not propagate, checkout sends email with correct args (4)

---

## 6. Remaining Work (TODO)

### Must-Have Before Ship

| # | Task | Priority | Effort |
|---|------|----------|--------|
| 1 | **Landing page** -- Product description, pricing, install instructions, Stripe Payment Link | Done | Done |
| 2 | **Stripe Payment Link creation** -- Create in Stripe dashboard, configure webhook endpoint URL | Done | Done |
| 3 | **End-to-end integration test** -- Test MCP server -> license API -> modeling (with mocked MCMC) | Medium | Medium |
| 4 | **Include convergence warnings in MCP output** -- Pass `model_results.convergence_warnings` through `_build_result()` | Done | Done |
| 5 | **Include marginal ROAS in MCP output** -- Add `model_results.marginal_roas_estimates` and CIs to JSON response | Done | Done |
| 6 | **Set up mixlift venv** -- Create `.venv/` with all dependencies so full test suite can be run | Medium | Small |
| 7 | **Initial git commit for mixlift-api** -- All files are currently untracked | Done | Done |
| 8 | **Deploy license API** -- Render/Railway/Fly.io deployment for `api.mixlift.io` | High | Medium |

### Nice-to-Have

| # | Task | Priority |
|---|------|----------|
| 11 | Rate limiting on license validation endpoint | Low |
| 12 | License key rotation/regeneration endpoint | Low |
| 13 | Usage tracking (number of analyses per license) | Low |
| 14 | PyPI publication of MixLift MCP package | Medium |
| 15 | Documentation (README, install guide, API docs) | Medium |

---

## 7. Architecture Diagram

```
                           MixLift System Architecture
    ============================================================================

    USER'S MACHINE                          REMOTE SERVICES
    +--------------------------+            +----------------------------+
    |                          |            |                            |
    |  AI Assistant            |            |  api.mixlift.io           |
    |  (Claude Desktop, etc.)  |            |  (License API Server)      |
    |          |               |            |                            |
    |          | stdio         |            |  POST /v1/license/validate |
    |          v               |   HTTPS    |  POST /v1/webhooks/stripe  |
    |  +-------------------+  |  -------->  |  GET  /health              |
    |  | MixLift MCP       |  |  <--------  |          |                 |
    |  | Server            |  |             |          v                 |
    |  |                   |  |             |  +--------------------+    |
    |  | - mixlift_analyze |  |             |  | SQLite (licenses)  |    |
    |  | - CSV auto-detect |  |             |  +--------------------+    |
    |  | - tier enforcement|  |             |                            |
    |  +--------+----------+  |             +----------------------------+
    |           |              |
    |           v              |
    |  +-------------------+  |            +----------------------------+
    |  | Modeling Engine   |  |            |                            |
    |  |                   |  |            |  Stripe                    |
    |  | - PyMC-Marketing  |  |            |                            |
    |  | - MCMC fitting    |  |            |  - Payment Link            |
    |  | - ROAS extraction |  |            |  - Subscription mgmt      |
    |  | - Budget optimize |  |   Webhook  |  - Webhook events          |
    |  | - Saturation crv  |  |            |          |                 |
    |  +-------------------+  |            +----------+-----------------+
    |                          |                       |
    +--------------------------+                       v
                                            +----------------------------+
                                            |  License API Webhook       |
                                            |  Handler                   |
                                            |                            |
                                            |  checkout.completed        |
                                            |    -> create license       |
                                            |    -> send email (Resend)  |
                                            |  subscription.deleted      |
                                            |    -> cancel license       |
                                            |  subscription.updated      |
                                            |    -> update status        |
                                            |  payment.failed            |
                                            |    -> suspend license      |
                                            |  payment.succeeded         |
                                            |    -> reactivate license   |
                                            +----------------------------+

    PURCHASE FLOW:
    ============================================================================

    User --> Landing Page --> Stripe Payment Link --> Stripe Checkout
         --> Webhook: checkout.session.completed
         --> License API: create license + email key
         --> User configures MCP server with license key
```

---

## 8. File Index

### MixLift MCP Server (`/Users/felix/software-projects/mixlift/`)

```
src/mixlift_mcp/
  server.py          # MCP tool definition
  license.py         # License validation client
  data/demo.csv      # Bundled demo dataset (3ch, 52wk)

mixlift/
  config.py          # App config (env vars, tier limits)
  main.py            # FastAPI web app entry point
  db.py              # Database setup
  deps.py            # Dependency injection
  auth/              # JWT auth (service, router, schemas)
  projects/          # Project CRUD (models, router, schemas)
  ingest/            # CSV ingestion (service, parsers, templates)
  modeling/          # MMM engine (engine, optimizer, preprocessing, etc.)
  results/           # Results viewing (router, schemas)
  billing/           # Stripe billing (service, router)

frontend/            # Streamlit web UI
scripts/             # Data generation utilities
tests/               # Test suite (18 files)
```

### MixLift License API (`/Users/felix/software-projects/mixlift-api/`)

```
src/mixlift_api/
  main.py            # FastAPI app + webhook handlers
  database.py        # SQLite CRUD
  models.py          # Pydantic models
  email_service.py   # Resend integration (HTML template, fallback logging)

tests/
  test_api.py        # 23 tests (all passing)
```
