Files
cloud-orchestrator/CLAUDE.md
kappa 580cc1bbe2 feat: migrate pricing from legacy tables to anvil_pricing
- Replace pricing/instance_types/providers/regions with anvil_* tables
- Add real-time USD→KRW exchange rate conversion (open.er-api.com)
- Korean users (lang=ko) see KRW prices, others see USD
- Remove provider_filter parameter (now single provider: Anvil)
- Add ExchangeRateCache interface with 1-hour KV caching
- Update CLAUDE.md with new table structure and exchange rate docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 01:05:44 +09:00

219 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Cloudflare Worker-based AI server recommendation service. Uses OpenAI GPT-4o-mini (via AI Gateway), D1 database, KV cache, and VPS benchmark data to recommend cost-effective servers based on natural language requirements.
**Production URL**: `https://server-recommend.kappa-d8e.workers.dev`
## Commands
```bash
# Development
npm run dev # Start local development server (wrangler dev)
npm run deploy # Deploy to Cloudflare Workers
npm run typecheck # TypeScript type checking
# Database operations (D1)
npx wrangler d1 execute cloud-instances-db --file=schema.sql # Apply schema
npx wrangler d1 execute cloud-instances-db --file=seed.sql # Seed data
npx wrangler d1 execute cloud-instances-db --file=fix-tech-specs.sql # Update tech specs
npx wrangler d1 execute cloud-instances-db --command="SELECT ..." # Ad-hoc queries
# View logs
npx wrangler tail
```
## Architecture
```
src/
├── index.ts # Main router, CORS, request handling
├── config.ts # Configuration constants
├── types.ts # TypeScript type definitions
├── utils.ts # Utilities (bandwidth, response, AI, benchmarks, candidates, techSpecs)
└── handlers/
├── health.ts # GET /api/health
├── servers.ts # GET /api/servers - List servers with filtering
└── recommend.ts # POST /api/recommend - AI-powered recommendations
```
### Key Data Flow
1. User sends request (`tech_stack`, `expected_users`, `use_case`, `region_preference`)
2. Tech specs calculation with **DB workload multiplier** based on use_case
3. Candidate filtering with **flexible region matching**
4. VPS benchmarks retrieval (Geekbench 6), **prioritizing same provider**
5. AI analysis returns 3 tiers: Budget, Balanced, Premium
6. Results cached in KV (5 min TTL, empty results not cached)
### D1 Database Tables (cloud-instances-db)
**Primary tables (Anvil pricing)**:
- `anvil_instances` - Anvil server specifications (vcpus, memory_gb, disk_gb, etc.)
- `anvil_regions` - Anvil data center regions (name, display_name, country_code)
- `anvil_pricing` - Anvil pricing data (monthly_price in USD)
**Support tables**:
- `tech_specs` - Resource requirements per technology (vcpu_per_users, min_memory_mb)
- `vps_benchmarks` - Geekbench 6 benchmark data (269 records)
- `benchmark_results` / `benchmark_types` / `processors` - Phoronix benchmark data
**Legacy tables (no longer used)**:
- `providers`, `instance_types`, `pricing`, `regions` - Old Linode/Vultr data
## Key Implementation Details
### DB Workload Multiplier (`recommend.ts`)
Database resource requirements vary by workload type, not just user count:
| Workload Type | Multiplier | Example Use Cases |
|---------------|------------|-------------------|
| Heavy | 0.3x | analytics, log server, reporting, dashboard |
| Medium-Heavy | 0.5x | e-commerce, ERP, CRM, community forum |
| Medium | 0.7x | API, SaaS, app backend |
| Light | 1.0x | blog, portfolio, documentation, wiki |
**Example**: PostgreSQL (vcpu_per_users: 200) with 1000 users
- Analytics dashboard: 1000 / (200 × 0.3) = 17 vCPU
- E-commerce: 1000 / (200 × 0.5) = 10 vCPU
- Personal blog: 1000 / (200 × 1.0) = 5 vCPU
### Bandwidth Estimation (`bandwidth.ts`)
Estimates monthly bandwidth based on use_case patterns:
| Pattern | Page Size | Pages/Day | Active Ratio |
|---------|-----------|-----------|--------------|
| E-commerce | 2.5MB | 20 | 40% |
| Streaming | 50MB | 5 | 20% |
| Analytics | 0.7MB | 30 | 50% |
| Blog/Content | 1.5MB | 4 | 30% |
Heavy bandwidth (>1TB/month) prefers Linode for included bandwidth.
### Flexible Region Matching
Both `/api/recommend` and `/api/servers` use `buildFlexibleRegionConditionsAnvil()` for anvil_regions:
```sql
LOWER(ar.name) = ? OR
LOWER(ar.name) LIKE ? OR
LOWER(ar.display_name) LIKE ? OR
LOWER(ar.country_code) = ?
```
Valid inputs: `"korea"`, `"KR"`, `"seoul"`, `"tokyo"`, `"japan"`, `"ap-northeast-2"`, `"icn"`
Country names are auto-expanded via `COUNTRY_NAME_TO_REGIONS` mapping.
### Exchange Rate Handling (`utils.ts`)
- Korean users (lang=ko) see prices in KRW
- Exchange rate fetched from open.er-api.com with 1-hour KV cache
- Fallback rate: 1450 KRW/USD if API unavailable
### AI Prompt Strategy (`recommend.ts`)
- Uses OpenAI GPT-4o-mini via Cloudflare AI Gateway (bypasses regional restrictions)
- Server list format: `[server_id=XXXX] Provider Name...` for accurate ID extraction
- Scoring: Cost efficiency (40%) + Capacity fit (30%) + Scalability (30%)
- Capacity response in Korean for Korean users
- **Prompt injection protection**: User inputs sanitized via `sanitizeForAIPrompt()`
- **Token optimization**: Candidates limited to top 15 by price
### Security Features
- **Input validation**: Comprehensive checks with length limits (tech_stack ≤20, use_case ≤500 chars)
- **Rate limiting**: 60 req/min per IP with in-memory fallback when KV unavailable
- **SQL injection prevention**: All queries use parameterized statements
- **Security headers**: CSP, HSTS, X-Frame-Options, X-Content-Type-Options
- **API key protection**: Keys never logged, sanitized from error messages
### Configuration (`config.ts`)
Centralized limits and constants:
```typescript
LIMITS = {
MAX_REQUEST_BODY_BYTES: 10240, // 10KB
CACHE_TTL_SECONDS: 300, // 5 minutes
RATE_LIMIT_MAX_REQUESTS: 60, // per minute
MAX_AI_CANDIDATES: 15, // reduce API cost
MAX_TECH_STACK: 20,
MAX_USE_CASE_LENGTH: 500,
}
```
## Bindings (wrangler.toml)
```toml
[[kv_namespaces]]
binding = "CACHE"
id = "c68cdb477022424cbe4594f491390c8a"
[[d1_databases]]
binding = "DB"
database_name = "cloud-instances-db"
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
[vars]
OPENAI_API_KEY = "sk-..." # Set via wrangler secret
```
## Testing
**Note**: Use single-line curl commands. Backslash line continuation (`\`) may not work in some environments.
```bash
# Health check
curl -s https://server-recommend.kappa-d8e.workers.dev/api/health | jq .
# Recommendation - nodejs/redis real-time chat (Japan)
curl -s -X POST https://server-recommend.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nodejs","redis"],"expected_users":1000,"use_case":"real-time chat","region_preference":["japan"]}' | jq .
# Recommendation - php/mysql community forum (Korea)
curl -s -X POST https://server-recommend.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["php","mysql"],"expected_users":800,"use_case":"community forum","region_preference":["korea"]}' | jq .
# Recommendation - analytics dashboard (heavier DB workload)
curl -s -X POST https://server-recommend.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["postgresql"],"expected_users":500,"use_case":"analytics dashboard","region_preference":["japan"]}' | jq .
# Server list with filters (supports flexible region: korea, seoul, tokyo, etc.)
curl -s "https://server-recommend.kappa-d8e.workers.dev/api/servers?region=korea&minCpu=4" | jq .
```
## Recent Changes
### Anvil Pricing Migration (Latest)
- **New tables**: Migrated from `pricing` to `anvil_pricing` tables
- **Provider**: Now uses "Anvil" as single provider (previously Linode/Vultr)
- **Exchange rate**: Real-time USD→KRW conversion via open.er-api.com
- **Removed**: `provider_filter` parameter no longer supported
- **Currency handling**: Korean users see KRW, others see USD
### Architecture
- **Modular architecture**: Split from single 2370-line file into organized modules
- **Centralized config**: All magic numbers moved to `LIMITS` in config.ts
- **Type safety**: `parseAIResponse(unknown)` with proper type guards
### Features
- **DB workload multiplier**: Database resource calculation based on use_case
- **Bandwidth estimation**: Automatic bandwidth category detection for provider filtering
- **Tech specs update**: Realistic vcpu_per_users values for 150+ technologies
### Security
- **Prompt injection protection**: `sanitizeForAIPrompt()` filters malicious patterns
- **Rate limiting fallback**: In-memory Map when KV unavailable
- **Input sanitization**: All user inputs validated and length-limited
### Performance
- **O(1) VPS lookup**: Map-based benchmark matching (was O(n×m))
- **AI token optimization**: Candidates limited to 15 (was 50)
- **KV caching**: 5-minute TTL, empty results not cached
- **Parallel queries**: Promise.all for independent DB operations
### Code Quality
- **Dead code removed**: Unused `queryVPSBenchmarks` function deleted
- **Flexible region matching**: Both endpoints support country/city/code inputs (korea, seoul, icn)