# Claude Guidelines for CannaiQ ## CURRENT ENVIRONMENT: PRODUCTION **We are working in PRODUCTION only.** All database queries and API calls should target the remote production environment, not localhost. Use kubectl port-forward or remote DB connections as needed. ## PERMANENT RULES (NEVER VIOLATE) ### 1. NO DELETE Never delete data, files, images, logs, or database rows. CannaiQ is a historical analytics system. ### 2. NO KILL Never run `pkill`, `kill`, `killall`, or similar. Say "Please run `./stop-local.sh`" instead. ### 3. NO MANUAL STARTUP Never start servers manually. Say "Please run `./setup-local.sh`" instead. ### 4. DEPLOYMENT AUTH REQUIRED Never deploy unless user explicitly says: "CLAUDE — DEPLOYMENT IS NOW AUTHORIZED." ### 5. DB POOL ONLY Never import `src/db/migrate.ts` at runtime. Use `src/db/pool.ts` for DB access. ### 6. K8S POD LIMITS — CRITICAL **EXACTLY 8 PODS** for `scraper-worker` deployment. NEVER CHANGE THIS. **Replica Count is LOCKED:** - Always 8 replicas — no more, no less - NEVER scale down (even temporarily) - NEVER scale up beyond 8 - If pods are not 8, restore to 8 immediately **Pods vs Workers:** - **Pod** = Kubernetes container instance (ALWAYS 8) - **Worker** = Concurrent task runner INSIDE a pod (controlled by `MAX_CONCURRENT_TASKS` env var) - Formula: `8 pods × MAX_CONCURRENT_TASKS = 24 total concurrent workers` **Browser Task Memory Limits:** - Each Puppeteer/Chrome browser uses ~400 MB RAM - Pod memory limit is 2 GB - **MAX_CONCURRENT_TASKS=3** is the safe maximum for browser tasks - More than 3 concurrent browsers per pod = OOM crash | Browsers | RAM Used | Status | |----------|----------|--------| | 3 | ~1.3 GB | Safe (recommended) | | 4 | ~1.7 GB | Risky | | 5+ | >2 GB | OOM crash | **To increase throughput:** Add more pods (up to 8), NOT more concurrent tasks per pod. ```bash # CORRECT - scale pods (up to 8) kubectl scale deployment/scraper-worker -n dispensary-scraper --replicas=8 # WRONG - will cause OOM crashes kubectl set env deployment/scraper-worker -n dispensary-scraper MAX_CONCURRENT_TASKS=10 ``` **If K8s API returns ServiceUnavailable:** STOP IMMEDIATELY. Do not retry. The cluster is overloaded. ### 7. K8S REQUIRES EXPLICIT PERMISSION **NEVER run kubectl commands without explicit user permission.** Before running ANY `kubectl` command (scale, rollout, set env, delete, apply, etc.): 1. Tell the user what you want to do 2. Wait for explicit approval 3. Only then execute the command This applies to ALL kubectl operations - even read-only ones like `kubectl get pods`. --- ## Quick Reference ### Database Tables | USE THIS | NOT THIS | |----------|----------| | `dispensaries` | `stores` (empty) | | `store_products` | `products` (empty) | | `store_product_snapshots` | `dutchie_product_snapshots` | ### Key Files | Purpose | File | |---------|------| | Dutchie client | `src/platforms/dutchie/client.ts` | | DB pool | `src/db/pool.ts` | | Payload fetch | `src/tasks/handlers/payload-fetch.ts` | | Product refresh | `src/tasks/handlers/product-refresh.ts` | ### Dutchie GraphQL - **Endpoint**: `https://dutchie.com/api-3/graphql` - **Hash (FilteredProducts)**: `ee29c060826dc41c527e470e9ae502c9b2c169720faa0a9f5d25e1b9a530a4a0` - **CRITICAL**: Use `Status: 'Active'` (not `null`) ### Frontends | Folder | Domain | Build | |--------|--------|-------| | `cannaiq/` | cannaiq.co | Vite | | `findadispo/` | findadispo.com | CRA | | `findagram/` | findagram.co | CRA | | `frontend/` | DEPRECATED | - | --- ## Deprecated Code **DO NOT USE** anything in `src/_deprecated/`: - `hydration/` - Use `src/tasks/handlers/` - `scraper-v2/` - Use `src/platforms/dutchie/` - `canonical-hydration/` - Merged into tasks **DO NOT USE** `src/dutchie-az/db/connection.ts` - Use `src/db/pool.ts` --- ## Local Development ```bash ./setup-local.sh # Start all services ./stop-local.sh # Stop all services ``` | Service | URL | |---------|-----| | API | http://localhost:3010 | | Admin | http://localhost:8080/admin | | PostgreSQL | localhost:54320 | --- ## WordPress Plugin (ACTIVE) ### Plugin Files | File | Purpose | |------|---------| | `wordpress-plugin/cannaiq-menus.php` | Main plugin (CannaIQ brand) | | `wordpress-plugin/crawlsy-menus.php` | Legacy plugin (Crawlsy brand) | | `wordpress-plugin/VERSION` | Version tracking | ### API Routes (Backend) - `GET /api/v1/wordpress/dispensaries` - List dispensaries - `GET /api/v1/wordpress/dispensary/:id/menu` - Get menu data - Route file: `backend/src/routes/wordpress.ts` ### Versioning Bump `wordpress-plugin/VERSION` on changes: - Minor (x.x.N): bug fixes - Middle (x.N.0): new features - Major (N.0.0): breaking changes (user must request) --- ## Puppeteer Scraping (Browser-Based) ### Age Gate Bypass Most dispensary sites require age verification. The browser scraper handles this automatically: **Utility File**: `src/utils/age-gate.ts` **Key Functions**: - `setAgeGateCookies(page, url, state)` - Set cookies BEFORE navigation to prevent gate - `hasAgeGate(page)` - Detect if page shows age verification - `bypassAgeGate(page, state)` - Click through age gate if displayed - `detectStateFromUrl(url)` - Extract state from URL (e.g., `-az-` → Arizona) **Cookie Names Set**: - `age_gate_passed: 'true'` - `selected_state: ''` - `age_verified: 'true'` **Bypass Methods** (tried in order): 1. Custom dropdown (shadcn/radix style) - Curaleaf pattern 2. Standard `