docs(claude): document Odin AI assistant + correct stale counts
- Add "AI Assistant — Odin" section (v2.1.5): scope (invoice-only guard), SDK/model, backend/data/frontend layout, gross-VAT received-invoices, Phase 2 pointer. - Add AI row to the tech stack. - Correct counts: 49→52 Prisma models, ~14→17 test files / 152→195 tests, ~105→114 admin .tsx; note odin/ components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
CLAUDE.md
48
CLAUDE.md
@@ -7,18 +7,19 @@ Handles attendance, invoicing, leave/trips, projects, vehicles, and HR operation
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
| -------------- | ------------------------------------------------------------- |
|
||||
| Runtime | Node.js, TypeScript 5.9.3 (strict) |
|
||||
| HTTP Framework | Fastify 5.8.2 |
|
||||
| ORM | Prisma 6.19.2 → MySQL |
|
||||
| Auth | JWT (HS256, 15 min) + TOTP 2FA (RFC 6238, otpauth) + bcryptjs |
|
||||
| Validation | Zod 4.3.6 |
|
||||
| Frontend | React 18.3.1 + Vite 8.0.0 + Material UI v7 (Emotion) |
|
||||
| Testing | Vitest 4.1.0 + Supertest |
|
||||
| PDF | Puppeteer 24.x |
|
||||
| Email | nodemailer 8.x |
|
||||
| Cron | node-cron 4.x |
|
||||
| Layer | Technology |
|
||||
| -------------- | -------------------------------------------------------------- |
|
||||
| Runtime | Node.js, TypeScript 5.9.3 (strict) |
|
||||
| HTTP Framework | Fastify 5.8.2 |
|
||||
| ORM | Prisma 6.19.2 → MySQL |
|
||||
| Auth | JWT (HS256, 15 min) + TOTP 2FA (RFC 6238, otpauth) + bcryptjs |
|
||||
| Validation | Zod 4.3.6 |
|
||||
| Frontend | React 18.3.1 + Vite 8.0.0 + Material UI v7 (Emotion) |
|
||||
| Testing | Vitest 4.1.0 + Supertest |
|
||||
| PDF | Puppeteer 24.x |
|
||||
| Email | nodemailer 8.x |
|
||||
| Cron | node-cron 4.x |
|
||||
| AI | @anthropic-ai/sdk 0.102 — Claude Sonnet 4.6 ("Odin" assistant) |
|
||||
|
||||
---
|
||||
|
||||
@@ -35,21 +36,21 @@ src/
|
||||
├── utils/ # totp.ts, pdf.ts, email.ts, audit.ts, formatters, etc.
|
||||
├── config/ # env.ts (config singleton, Date.toJSON override)
|
||||
├── types/ # index.ts (AuthData, JwtPayload, ApiResponse, re-exports from Prisma)
|
||||
├── admin/ # React 18 + Material UI frontend (~105 .tsx files)
|
||||
├── admin/ # React 18 + Material UI frontend (~114 .tsx files)
|
||||
│ ├── AdminApp.tsx # Router + lazy-loaded pages
|
||||
│ ├── theme.ts # MUI theme — light/dark color schemes, tokens, component defaults
|
||||
│ ├── GlobalStyles.tsx # App-wide global styles via MUI <GlobalStyles> (reset, typography, utilities)
|
||||
│ ├── ui/ # MUI component kit (AppShell, Button, DataTable, Modal, …) — pages import from here
|
||||
│ ├── context/ # AuthContext, AlertContext (ThemeContext lives in src/context/)
|
||||
│ ├── components/ # Non-page components: RichEditor (Quill), PlanGrid, file manager, dashboard/ + warehouse/ widgets
|
||||
│ ├── components/ # Non-page components: RichEditor (Quill), PlanGrid, file manager, dashboard/ + warehouse/ widgets, odin/ (AI chat)
|
||||
│ ├── pages/ # One file per page/feature
|
||||
│ ├── lib/ # React Query options & mutations (queries/) + shared label maps
|
||||
│ ├── hooks/ # usePaginatedQuery, useTableSort, useDebounce, useReducedMotion, …
|
||||
│ └── utils/ # api.ts (fetch wrapper with token refresh), formatters, helpers
|
||||
└── __tests__/ # Vitest tests (~14 files: auth, numbering, warehouse, plan, invoices, …)
|
||||
└── __tests__/ # Vitest tests (17 files: auth, numbering, warehouse, plan, invoices, ai/Odin, received-invoices VAT, …)
|
||||
|
||||
prisma/
|
||||
├── schema.prisma # 49 models, MySQL, snake_case columns
|
||||
├── schema.prisma # 52 models, MySQL, snake_case columns
|
||||
└── migrations/ # Applied migrations
|
||||
|
||||
dist/ # Compiled server (CommonJS, ES2022)
|
||||
@@ -240,7 +241,7 @@ if ("error" in body) return error(reply, body.error, 400);
|
||||
|
||||
Tests live in `src/__tests__/`. They use Vitest + Supertest against a real test database (`.env.test`).
|
||||
|
||||
- The suite spans ~14 files / 152 tests — auth, numbering, warehouse, plan, invoices, exchange-rates, schema coercion, NAS file manager, env, manual-create. Server-side only (no component tests yet).
|
||||
- The suite spans 17 files / 195 tests — auth, numbering, warehouse, plan, invoices, exchange-rates, schema coercion, NAS file manager, env, manual-create, AI/Odin (conversation CRUD, isolation, auto-title, cascade, budget), received-invoices VAT. Server-side only (no component tests yet).
|
||||
- Use `buildApp()` helper to spin up the Fastify instance for tests.
|
||||
- Tests use `vitest.config.ts` with `environment: 'node'` and 15s timeout.
|
||||
- **Do not mock Prisma** — tests hit a real database to catch schema/query bugs.
|
||||
@@ -305,6 +306,19 @@ The admin frontend is **fully on Material UI v7** (Emotion). The old ~7,100 line
|
||||
|
||||
---
|
||||
|
||||
## AI Assistant — "Odin" (shipped v2.1.5)
|
||||
|
||||
Admins-only Claude assistant on the `/odin` page (sidebar nav item "Odin"). **Phase-1 scope is invoice import ONLY** — general chat is guarded off in `OdinChat.submit` to avoid spending API credits: a text-only message gets a canned reply and makes NO AI call. Removing that one guard re-enables the `/chat` path for a later "general assistant" phase.
|
||||
|
||||
- **SDK / model:** `@anthropic-ai/sdk` → `claude-sonnet-4-6`. Server-side only — `ANTHROPIC_API_KEY` in `.env` (already set on prod; **don't touch env, the user manages it**). The whole feature self-hides when the key is absent (`/ai/usage` returns `configured:false`).
|
||||
- **Backend:** `src/services/ai.service.ts` (chat, `extractInvoice` [PDF→structured-output JSON], cost/budget tracking, conversation CRUD with server-side auto-title), `src/routes/admin/ai.ts` (`/api/admin/ai/*`: usage, budget, chat, extract-invoices, conversations[/:id/messages]), `src/schemas/ai.schema.ts`. Every route `requirePermission("ai.use")` (granted to **admin only** via migration).
|
||||
- **Data:** `ai_usage` (per-call token-cost ledger), `ai_conversations` + `ai_chat_messages` (per-user, FK cascade, auto-title from first message), `company_settings.ai_monthly_budget_usd` (default $50; `assertBudgetAvailable` → 402 at the cap).
|
||||
- **Frontend:** `src/admin/pages/Odin.tsx` → `src/admin/components/odin/`: `OdinChat` (orchestrator + state + the proven submit/extract/save logic), `OdinSidebar` (conversation list; inline ≥md, slide-in Drawer below md), `OdinThread` (messages, framer-motion entrance, Newsreader serif greeting hero), `OdinComposer` (claude-style rounded multiline pill, attach/send icons), `InvoiceReviewCard`, `OdinMark` (animated brand mark — CSS keyframes via `styled`, not inline style; opacity-glow fallback under reduced-motion), `types.ts`. Queries in `src/admin/lib/queries/ai.ts`. `Fraunces`→`Newsreader` font added to `index.html`.
|
||||
- **Invoice flow:** attach PDF(s) → `extract-invoices` → editable review cards → save to the EXISTING `POST /received-invoices`. **`received_invoices.amount` is GROSS (VAT-inclusive)** — VAT is back-calculated via `vatFromGross` in `received-invoices.ts` (`amount * rate/(100+rate)`), used by both the manual form and the AI import. The save button is gated on `invoices.create`.
|
||||
- **Phase 2 (general assistant — NOT built):** forward-looking design notes in `docs/superpowers/specs/2026-06-08-odin-phase2-general-assistant-notes.md` (tool-use over services, structured message-block storage, per-action authorization). Phase-1 spec/plan also under `docs/superpowers/`.
|
||||
|
||||
---
|
||||
|
||||
## Database Conventions
|
||||
|
||||
- All models use `snake_case` column names; Prisma maps to camelCase in TypeScript.
|
||||
|
||||
Reference in New Issue
Block a user