# Ask QIEN

An AI-augmented support ticket system that connects factory production line managers with domain-expert consultants. Line managers submit structured issues via a web form; a pipeline of AI agents triages each submission before it lands in the expert's inbox. Experts review AI-generated draft responses, capture structured resolution metadata, and send their response back. Each resolved ticket feeds a growing knowledge base so that over time the AI can suggest increasingly accurate responses with decreasing manual effort.

**Live at:** [quality-intelligence.co](https://quality-intelligence.co)

**Key features:**

- Magic-link authentication and Google SSO (no passwords)
- Invite-only access with a waitlist for unknown sign-ins
- Structured intake form with nine fields; reference numbers include issue category abbreviation (e.g. `AQ-VAL-0042`)
- Full ticket lifecycle: submit → AI triage → expert review → response → follow-up → close
- AI processing at every stage (completeness check → summarization → KB-backed draft → KB update proposal), powered by Inngest
- Expert inbox with filter/sort by project, category, priority, and status
- Expert resolution fields (severity, concern type, resolution summary) captured on every response
- Knowledge base with semantic search, expert-managed entries, and AI-proposed updates
- Multi-tenant architecture with full tenant isolation, membership-based RBAC, and tenant switching for multi-tenant users
- Tenant admin tools for configuration, users, and project management
- Responsive design — works on mobile and desktop

---

## Deploying to production

See [docs/deployment.md](docs/deployment.md) for a step-by-step guide covering Vercel, Vercel Postgres, Inngest Cloud, Resend, and Google OAuth setup.

Additional project docs:

- [Technical spec](docs/technical_spec.md)
- [Roadmap](docs/roadmap.md)
- [Project pre-mortem](docs/project_pre_mortem.md)
- [Pre-deployment checklist](docs/pre_deployment.md)
- [Multi-tenancy onboarding](docs/multi_tenancy_onboarding.md)

---

## Getting Started (local development)

### Prerequisites

- Node.js 22 recommended, pnpm
- Docker (for the devcontainer, recommended) — or a local PostgreSQL instance with pgvector

The project ships a VS Code Dev Container (`.devcontainer/`) that sets up Postgres + pgvector automatically. Open the project in VS Code and click **Reopen in Container** when prompted.

On Windows, the recommended setup is WSL 2 + VS Code Dev Containers. Clone the repo inside the Linux filesystem (for example `~/src/ask-qien`, not `/mnt/c/...`) for much better file-watch and install performance, then open it from WSL with `code .` and reopen in the container. Docker Desktop with WSL integration works well; a native Docker Engine inside WSL is also fine if your machine is already configured that way.

### First-time setup

```bash
pnpm install
cp .env.example .env.local
pnpm db:migrate
pnpm db:seed
```

Copy `.env.example` to `.env.local` and fill in the required variables (see comments in the file). For local dev you only need `DATABASE_URL`, `AUTH_SECRET`, and `ANTHROPIC_API_KEY` — email is replaced by the dev magic-link button.

When using the devcontainer, `DATABASE_URL` is injected automatically as `postgresql://postgres:postgres@db:5432/askqien`; keep the same value in `.env.local` unless you are intentionally pointing at a different database. Generate `AUTH_SECRET` with:

```bash
openssl rand -base64 32
```

If you are moving from an existing trusted development machine, copy local secrets across the private network instead of regenerating them. For example, from the new WSL checkout you can pull the local env files from the old machine with `scp` or `rsync`:

```bash
scp user@old-machine:/path/to/ask-qien/.env.local .env.local
scp user@old-machine:/path/to/ask-qien/.env.production.local .env.production.local
```

Keep these files uncommitted. They are intentionally ignored by git.

By default, `pnpm db:seed` creates the tenant, config, users, memberships, and projects, but skips demo tickets, knowledge base entries, and AI usage logs. To seed realistic demo tickets, matched and unmatched KB entries, AI artifacts, and sample AI usage/cost data in a local database, run:

```bash
SEED_DEMO_DATA=true pnpm db:seed
```

### Start the app

```bash
# Terminal 1 — Next.js dev server
pnpm dev

# Terminal 2 — Inngest job runner (required for AI triage to fire)
pnpm dev:inngest
```

Then open [http://localhost:3000](http://localhost:3000).

The dev server is pinned to `0.0.0.0:3000` so browser forwarding stays stable in the devcontainer. If `pnpm dev` says port 3000 is already in use, stop the older Next.js process rather than switching ports — Inngest is configured to reach the app on that fixed port.

For lower-memory environments, `pnpm dev:turbo` starts the Turbopack dev server with a smaller Node heap. In the devcontainer, Mailpit is available at [http://localhost:8025](http://localhost:8025) if you switch `AUTH_DEV_EMAIL_MODE` to `smtp`.

---

## Logging in (local dev)

Go to `/login`, enter `expert@example.com`, and click **Send magic link**.

The page shows an **Open magic link** button directly — no email needed in dev (`AUTH_DEV_EMAIL_MODE=dev-link` is active). Click it and you're in.

---

## Project structure

```text
.
├── .devcontainer/        # VS Code Dev Container and local Postgres setup
├── assets/               # Source brand and product assets
├── docs/                 # Architecture, deployment, and planning docs
├── prompts/              # Prompt templates used by AI agents
├── public/               # Static files served by Next.js
├── scripts/              # Database, seed, and maintenance scripts
├── src/
│   ├── app/              # Next.js App Router pages, layouts, and API routes
│   ├── db/               # Drizzle schema, migrations, and database client
│   ├── inngest/          # Background AI agents and scheduled jobs
│   ├── lib/              # Shared domain logic, guards, emails, and utilities
│   └── types/            # Project-level TypeScript declarations
├── tests/                # Vitest unit/integration tests and Playwright e2e specs
├── tenant.config.yaml    # Tenant-specific product, form, notification, and AI config
└── drizzle.config.ts     # Drizzle migration configuration
```

Most product workflows are split between `src/app/` route handlers/pages, shared logic in `src/lib/`, persisted models in `src/db/`, and asynchronous processing in `src/inngest/`.

---

## Key routes

| Route             | Who                | What it does                                                          |
| ----------------- | ------------------ | --------------------------------------------------------------------- |
| `/submit`         | Line managers      | Submit a structured support ticket                                    |
| `/tickets`        | Line managers      | Track all submitted tickets                                           |
| `/inbox`          | Experts            | Review incoming tickets with filter/sort controls                     |
| `/knowledge`      | Experts            | Browse, search, and create knowledge base entries                     |
| `/admin/config`   | Admins             | Manage tenant-specific intake, notification, and AI settings          |
| `/admin/users`    | Admins             | Invite users, change roles, disable users, and reactivate memberships |
| `/admin/projects` | Experts            | Manage the project list line managers can pick from                   |
| `/switch-tenant`  | Multi-tenant users | Switch the active tenant for the current browser session              |

Submit a ticket via `/submit`, then watch the Inngest terminal — the completeness agent, summarization, and draft-response generation chain automatically.

---

## How information moves through the app

The diagrams below show the production workflow. Purple boxes make an LLM call; blue boxes are asynchronous Inngest jobs; green boxes are persisted application data. Each ticket, message, artifact, knowledge entry, and AI-call record is tenant-scoped.

### Ticket lifecycle and AI triage

```mermaid
flowchart TD
  manager[Line manager] --> submit[Submit structured ticket]
  submit --> ticketDB[(Ticket and initial message)]
  ticketDB --> submitted{{ticket/submitted event}}
  submitted --> completeness[Completeness assessment<br/>LLM]
  completeness --> completeArtifact[(Completeness artifact and audit log)]
  completeness --> complete{Enough information?}
  complete -- No --> needsInfo[Request clarification]
  needsInfo --> ticketDB
  needsInfo --> manager
  complete -- Yes --> triaged{{ticket/triaged event}}

  triaged --> summary[Expert-facing summary<br/>LLM]
  triaged --> sentiment[Urgency and sentiment<br/>LLM]
  triaged --> tags[KB-grounded tag suggestions<br/>LLM]
  summary --> summaryArtifact[(Summary artifact)]
  sentiment --> enrichment[(Priority and sentiment fields)]
  tags --> enrichment
  summary --> draftRequested{{ticket/response_draft.requested event}}

  knowledge[(Knowledge base with embeddings)] --> retrieval[Semantic KB retrieval]
  ticketDB --> retrieval
  summaryArtifact --> retrieval
  draftRequested --> draft[Grounded response draft<br/>LLM]
  retrieval --> draft
  draft --> draftArtifact[(Suggested response artifact)]
  draftArtifact --> expert[Domain expert reviews and edits]
  enrichment --> expert
  expert --> response[Send expert response and resolution fields]
  response --> ticketDB
  response --> manager
  manager -- Follow-up --> ticketDB
  manager -- Clarification requested --> submitted

  classDef llm fill:#f3e8ff,stroke:#7e22ce,color:#3b0764;
  classDef event fill:#dbeafe,stroke:#1d4ed8,color:#172554;
  classDef store fill:#dcfce7,stroke:#15803d,color:#14532d;
  class completeness,summary,sentiment,tags,draft llm;
  class submitted,triaged,draftRequested event;
  class ticketDB,completeArtifact,summaryArtifact,enrichment,knowledge,draftArtifact store;
```

The completeness result can send the ticket back to the line manager for more information. Once it passes, Inngest runs summarization, urgency/sentiment assessment, and tag suggestion independently. The summary path then starts the response-draft job; an expert remains responsible for reviewing and sending the final answer.

### Knowledge feedback loop

```mermaid
flowchart LR
  expert[Expert response] --> responded{{ticket/responded event}}
  responded --> proposal[KB update proposal<br/>LLM]
  proposal --> proposed[(Proposed KB artifact)]
  proposed --> review[Expert accepts, edits, or rejects]
  review -- Accept or edit --> entry[(Verified knowledge entry)]

  author[Expert creates or updates KB entry] --> entry
  entry --> changed{{knowledge/entry.changed event}}
  changed --> index[Create local text embedding]
  index --> searchable[(Searchable tenant KB)]
  searchable --> retrieve[Semantic retrieval for response drafts]

  classDef llm fill:#f3e8ff,stroke:#7e22ce,color:#3b0764;
  classDef event fill:#dbeafe,stroke:#1d4ed8,color:#172554;
  classDef store fill:#dcfce7,stroke:#15803d,color:#14532d;
  class proposal llm;
  class responded,changed event;
  class proposed,entry,searchable store;
```

Knowledge-base indexing creates a deterministic local embedding; it is not an LLM call. The response-draft LLM receives the ticket context plus the retrieved KB entries, so approved expert knowledge informs later drafts.

### Other LLM-assisted paths

| Trigger | LLM output | Where it is used |
| --- | --- | --- |
| Expert requests a draft again | New KB-grounded response draft | Expert ticket view |
| Expert requests a response preview in another language | Translation preview | Expert review before sending |
| Expert generates a knowledge-entry translation | Spanish KB translation | Saved for review/approval |
| Translation backfill script | Ticket, response, artifact, and KB translations | Existing multilingual content |

All LLM calls go through the AI gateway, which records provider/model, token usage, latency, cost estimate, and failures in `ai_call_logs`. The current gateway implementation uses Anthropic through the Vercel AI SDK and blocks LLM calls when a tenant has not consented to data processing.

---

## Local Seed accounts

| Email                     | Role         | Access                                                               |
| ------------------------- | ------------ | -------------------------------------------------------------------- |
| `expert@example.com`      | expert       | Inbox, knowledge base, ticket detail, response drafting              |
| `admin@example.com`       | admin        | Admin tools, inbox, knowledge base, ticket detail, response drafting |
| `factorymgr1@example.com` | line_manager | Submit form, ticket tracker                                          |

Both use the same magic-link flow in dev — enter the email, click **Open magic link**, done. Users must belong to an active tenant membership before they can sign in; unknown addresses are added to the waitlist.

---

## Common commands

```bash
pnpm dev           # Next.js dev server
pnpm dev:turbo     # Turbopack dev server with smaller Node heap
pnpm dev:inngest   # Inngest local job runner
pnpm lint          # ESLint
pnpm build         # Production build
pnpm test          # Unit + integration tests (Vitest)
pnpm test:e2e      # Playwright end-to-end tests
```

Before the first e2e run in a fresh Linux/devcontainer environment, install the Chromium browser bundle:

```bash
pnpm exec playwright install --with-deps chromium
```

---

## Tech stack

Next.js · PostgreSQL + pgvector · Drizzle ORM · Inngest · Vercel AI SDK · Auth.js · Tailwind CSS · Vitest · Playwright
